Merge pull request #124754 from microsoft/tyriar/profile_api
Terminal profile contributions
This commit is contained in:
commit
29cad8fb5e
12 changed files with 249 additions and 66 deletions
21
src/vs/vscode.proposed.d.ts
vendored
21
src/vs/vscode.proposed.d.ts
vendored
|
@ -916,6 +916,27 @@ declare module 'vscode' {
|
|||
|
||||
//#endregion
|
||||
|
||||
//#region Terminal profile provider https://github.com/microsoft/vscode/issues/120369
|
||||
|
||||
export namespace window {
|
||||
/**
|
||||
* Registers a provider for a contributed terminal profile.
|
||||
* @param id The ID of the contributed terminal profile.
|
||||
* @param provider The terminal profile provider.
|
||||
*/
|
||||
export function registerTerminalProfileProvider(id: string, provider: TerminalProfileProvider): Disposable;
|
||||
}
|
||||
|
||||
export interface TerminalProfileProvider {
|
||||
/**
|
||||
* Provide terminal profile options for the requested terminal.
|
||||
* @param token A cancellation token that indicates the result is no longer needed.
|
||||
*/
|
||||
provideProfileOptions(token: CancellationToken): ProviderResult<TerminalOptions | ExtensionTerminalOptions>;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
// eslint-disable-next-line vscode-dts-region-comments
|
||||
//#region @jrieken -> exclusive document filters
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
private _extHostTerminalIds = new Map<string, number>();
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private readonly _terminalProcessProxies = new Map<number, ITerminalProcessExtHostProxy>();
|
||||
private readonly _profileProviders = new Map<string, IDisposable>();
|
||||
private _dataEventTracker: TerminalDataEventTracker | undefined;
|
||||
/**
|
||||
* A single shared terminal link provider for the exthost. When an ext registers a link
|
||||
|
@ -142,7 +143,16 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal,
|
||||
useShellEnvironment: launchConfig.useShellEnvironment
|
||||
};
|
||||
const terminal = this._terminalService.createTerminal(shellLaunchConfig);
|
||||
let terminal: ITerminalInstance | undefined;
|
||||
if (launchConfig.isSplitTerminal) {
|
||||
const activeInstance = this._terminalService.getActiveInstance();
|
||||
if (activeInstance) {
|
||||
terminal = withNullAsUndefined(this._terminalService.splitInstance(activeInstance, shellLaunchConfig));
|
||||
}
|
||||
}
|
||||
if (!terminal) {
|
||||
terminal = this._terminalService.createTerminal(shellLaunchConfig);
|
||||
}
|
||||
this._extHostTerminalIds.set(extHostTerminalId, terminal.instanceId);
|
||||
}
|
||||
|
||||
|
@ -201,6 +211,18 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
|||
this._terminalService.registerProcessSupport(isSupported);
|
||||
}
|
||||
|
||||
public $registerProfileProvider(id: string): void {
|
||||
// Proxy profile provider requests through the extension host
|
||||
this._profileProviders.set(id, this._terminalService.registerTerminalProfileProvider(id, {
|
||||
createContributedTerminalProfile: async (isSplitTerminal) => this._proxy.$createContributedProfileTerminal(id, isSplitTerminal)
|
||||
}));
|
||||
}
|
||||
|
||||
public $unregisterProfileProvider(id: string): void {
|
||||
this._profileProviders.get(id)?.dispose();
|
||||
this._profileProviders.delete(id);
|
||||
}
|
||||
|
||||
private _onActiveTerminalChanged(terminalId: number | null): void {
|
||||
this._proxy.$acceptActiveTerminalChanged(terminalId);
|
||||
}
|
||||
|
|
|
@ -661,8 +661,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
}
|
||||
return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs);
|
||||
},
|
||||
registerTerminalLinkProvider(handler: vscode.TerminalLinkProvider): vscode.Disposable {
|
||||
return extHostTerminalService.registerLinkProvider(handler);
|
||||
registerTerminalLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable {
|
||||
return extHostTerminalService.registerLinkProvider(provider);
|
||||
},
|
||||
registerTerminalProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable {
|
||||
return extHostTerminalService.registerProfileProvider(id, provider);
|
||||
},
|
||||
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
|
||||
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension);
|
||||
|
|
|
@ -472,6 +472,7 @@ export interface TerminalLaunchConfig {
|
|||
isFeatureTerminal?: boolean;
|
||||
isExtensionOwnedTerminal?: boolean;
|
||||
useShellEnvironment?: boolean;
|
||||
isSplitTerminal?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadTerminalServiceShape extends IDisposable {
|
||||
|
@ -485,6 +486,8 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
|
|||
$startLinkProvider(): void;
|
||||
$stopLinkProvider(): void;
|
||||
$registerProcessSupport(isSupported: boolean): void;
|
||||
$registerProfileProvider(id: string): void;
|
||||
$unregisterProfileProvider(id: string): void;
|
||||
$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void;
|
||||
|
||||
// Process
|
||||
|
@ -1721,6 +1724,7 @@ export interface ExtHostTerminalServiceShape {
|
|||
$activateLink(id: number, linkId: number): void;
|
||||
$initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void;
|
||||
$acceptDefaultProfile(profile: ITerminalProfile, automationProfile: ITerminalProfile): void;
|
||||
$createContributedProfileTerminal(id: string, isSplitTerminal: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostSCMShape {
|
||||
|
|
|
@ -18,9 +18,10 @@ import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/ter
|
|||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IProcessReadyEvent, IShellLaunchConfigDto, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
|
||||
export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable {
|
||||
|
||||
|
@ -42,12 +43,14 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID
|
|||
getDefaultShell(useAutomationShell: boolean): string;
|
||||
getDefaultShellArgs(useAutomationShell: boolean): string[] | string;
|
||||
registerLinkProvider(provider: vscode.TerminalLinkProvider): vscode.Disposable;
|
||||
registerProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable;
|
||||
getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection;
|
||||
}
|
||||
|
||||
export interface ITerminalInternalOptions {
|
||||
isFeatureTerminal?: boolean;
|
||||
useShellEnvironment?: boolean;
|
||||
isSplitTerminal?: boolean;
|
||||
}
|
||||
|
||||
export const IExtHostTerminalService = createDecorator<IExtHostTerminalService>('IExtHostTerminalService');
|
||||
|
@ -118,30 +121,39 @@ export class ExtHostTerminal {
|
|||
}
|
||||
|
||||
public async create(
|
||||
shellPath?: string,
|
||||
shellArgs?: string[] | string,
|
||||
cwd?: string | URI,
|
||||
env?: ITerminalEnvironment,
|
||||
icon?: URI | { light: URI; dark: URI } | ThemeIcon,
|
||||
initialText?: string,
|
||||
waitOnExit?: boolean,
|
||||
strictEnv?: boolean,
|
||||
hideFromUser?: boolean,
|
||||
isFeatureTerminal?: boolean,
|
||||
isExtensionOwnedTerminal?: boolean,
|
||||
useShellEnvironment?: boolean
|
||||
options: vscode.TerminalOptions,
|
||||
internalOptions?: ITerminalInternalOptions,
|
||||
): Promise<void> {
|
||||
if (typeof this._id !== 'string') {
|
||||
throw new Error('Terminal has already been created');
|
||||
}
|
||||
await this._proxy.$createTerminal(this._id, { name: this._name, shellPath, shellArgs, cwd, env, icon, initialText, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal, isExtensionOwnedTerminal, useShellEnvironment });
|
||||
await this._proxy.$createTerminal(this._id, {
|
||||
name: options.name,
|
||||
shellPath: withNullAsUndefined(options.shellPath),
|
||||
shellArgs: withNullAsUndefined(options.shellArgs),
|
||||
cwd: withNullAsUndefined(options.cwd),
|
||||
env: withNullAsUndefined(options.env),
|
||||
icon: withNullAsUndefined(options.iconPath),
|
||||
initialText: withNullAsUndefined(options.message),
|
||||
strictEnv: withNullAsUndefined(options.strictEnv),
|
||||
hideFromUser: withNullAsUndefined(options.hideFromUser),
|
||||
isFeatureTerminal: withNullAsUndefined(internalOptions?.isFeatureTerminal),
|
||||
isExtensionOwnedTerminal: true,
|
||||
useShellEnvironment: withNullAsUndefined(internalOptions?.useShellEnvironment),
|
||||
isSplitTerminal: withNullAsUndefined(internalOptions?.isSplitTerminal)
|
||||
});
|
||||
}
|
||||
|
||||
public async createExtensionTerminal(iconPath?: URI | { light: URI; dark: URI } | ThemeIcon): Promise<number> {
|
||||
public async createExtensionTerminal(isSplitTerminal?: boolean, iconPath?: URI | { light: URI; dark: URI } | ThemeIcon): Promise<number> {
|
||||
if (typeof this._id !== 'string') {
|
||||
throw new Error('Terminal has already been created');
|
||||
}
|
||||
await this._proxy.$createTerminal(this._id, { name: this._name, isExtensionCustomPtyTerminal: true, icon: iconPath });
|
||||
await this._proxy.$createTerminal(this._id, {
|
||||
name: this._name,
|
||||
isExtensionCustomPtyTerminal: true,
|
||||
icon: iconPath,
|
||||
isSplitTerminal
|
||||
});
|
||||
// At this point, the id has been set via `$acceptTerminalOpened`
|
||||
if (typeof this._id === 'string') {
|
||||
throw new Error('Terminal creation failed');
|
||||
|
@ -302,6 +314,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
|||
|
||||
private readonly _bufferer: TerminalDataBufferer;
|
||||
private readonly _linkProviders: Set<vscode.TerminalLinkProvider> = new Set();
|
||||
private readonly _profileProviders: Map<string, vscode.TerminalProfileProvider> = new Map();
|
||||
private readonly _terminalLinkCache: Map<number, Map<number, ICachedLinkEntry>> = new Map();
|
||||
private readonly _terminalLinkCancellationSource: Map<number, CancellationTokenSource> = new Map();
|
||||
|
||||
|
@ -353,10 +366,10 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
|||
return profile?.args || [];
|
||||
}
|
||||
|
||||
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal {
|
||||
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
|
||||
const p = new ExtHostPseudoterminal(options.pty);
|
||||
terminal.createExtensionTerminal(options.iconPath).then(id => {
|
||||
terminal.createExtensionTerminal(internalOptions?.isSplitTerminal, options.iconPath).then(id => {
|
||||
const disposable = this._setupExtHostProcessListeners(id, p);
|
||||
this._terminalProcessDisposables[id] = disposable;
|
||||
});
|
||||
|
@ -571,6 +584,33 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
|
|||
});
|
||||
}
|
||||
|
||||
public registerProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable {
|
||||
if (this._profileProviders.has(id)) {
|
||||
throw new Error(`Terminal profile provider "${id}" already registered`);
|
||||
}
|
||||
this._profileProviders.set(id, provider);
|
||||
this._proxy.$registerProfileProvider(id);
|
||||
return new VSCodeDisposable(() => {
|
||||
this._profileProviders.delete(id);
|
||||
this._proxy.$unregisterProfileProvider(id);
|
||||
});
|
||||
}
|
||||
|
||||
public async $createContributedProfileTerminal(id: string, isSplitTerminal: boolean): Promise<void> {
|
||||
const token = new CancellationTokenSource().token;
|
||||
const options = await this._profileProviders.get(id)?.provideProfileOptions(token);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
if (!options) {
|
||||
throw new Error(`No terminal profile options provided for id "${id}"`);
|
||||
}
|
||||
if ('pty' in options) {
|
||||
this.createExtensionTerminal(options, { isSplitTerminal });
|
||||
}
|
||||
this.createTerminalFromOptions(options, { isSplitTerminal });
|
||||
}
|
||||
|
||||
public async $provideLinks(terminalId: number, line: string): Promise<ITerminalLinkDto[]> {
|
||||
const terminal = this._getTerminalById(terminalId);
|
||||
if (!terminal) {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { BaseExtHostTerminalService, ExtHostTerminal, ITerminalInternalOptions } from 'vs/workbench/api/common/extHostTerminalService';
|
||||
|
@ -18,29 +17,13 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
|
|||
}
|
||||
|
||||
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), { name, shellPath, shellArgs }, name);
|
||||
this._terminals.push(terminal);
|
||||
terminal.create(shellPath, shellArgs);
|
||||
return terminal.value;
|
||||
return this.createTerminalFromOptions({ name, shellPath, shellArgs });
|
||||
}
|
||||
|
||||
public createTerminalFromOptions(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal {
|
||||
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
|
||||
this._terminals.push(terminal);
|
||||
terminal.create(
|
||||
withNullAsUndefined(options.shellPath),
|
||||
withNullAsUndefined(options.shellArgs),
|
||||
withNullAsUndefined(options.cwd),
|
||||
withNullAsUndefined(options.env),
|
||||
withNullAsUndefined(options.iconPath),
|
||||
withNullAsUndefined(options.message),
|
||||
/*options.waitOnExit*/ undefined,
|
||||
withNullAsUndefined(options.strictEnv),
|
||||
withNullAsUndefined(options.hideFromUser),
|
||||
withNullAsUndefined(internalOptions?.isFeatureTerminal),
|
||||
true,
|
||||
withNullAsUndefined(internalOptions?.useShellEnvironment)
|
||||
);
|
||||
terminal.create(options, internalOptions);
|
||||
return terminal.value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,8 @@ export interface ITerminalService {
|
|||
*/
|
||||
createTerminal(profile: ITerminalProfile): ITerminalInstance;
|
||||
|
||||
createContributedTerminalProfile(id: string, isSplitTerminal: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates a raw terminal instance, this should not be used outside of the terminal part.
|
||||
*/
|
||||
|
@ -199,6 +201,8 @@ export interface ITerminalService {
|
|||
*/
|
||||
registerLinkProvider(linkProvider: ITerminalExternalLinkProvider): IDisposable;
|
||||
|
||||
registerTerminalProfileProvider(id: string, profileProvider: ITerminalProfileProvider): IDisposable;
|
||||
|
||||
showProfileQuickPick(type: 'setDefault' | 'createInstance', cwd?: string | URI): Promise<ITerminalInstance | undefined>;
|
||||
|
||||
getGroupForInstance(instance: ITerminalInstance): ITerminalGroup | undefined;
|
||||
|
@ -224,6 +228,10 @@ export interface ITerminalExternalLinkProvider {
|
|||
provideLinks(instance: ITerminalInstance, line: string): Promise<ITerminalLink[] | undefined>;
|
||||
}
|
||||
|
||||
export interface ITerminalProfileProvider {
|
||||
createContributedTerminalProfile(isSplitTerminal: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ITerminalLink {
|
||||
/** The startIndex of the link in the line. */
|
||||
startIndex: number;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { AutoOpenBarrier, timeout } from 'vs/base/common/async';
|
||||
import { debounce, throttle } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isMacintosh, isWeb, isWindows, OperatingSystem, OS } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
|
||||
|
@ -19,13 +19,13 @@ import { IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickPi
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ILocalTerminalService, IOffProcessTerminalService, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalSettingId, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState, ITerminalProfileProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IEditableData, IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
|
||||
import { TerminalGroup } from 'vs/workbench/contrib/terminal/browser/terminalGroup';
|
||||
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
|
||||
import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalProcessExtHostProxy, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, TERMINAL_VIEW_ID, KEYBINDING_CONTEXT_TERMINAL_COUNT, ITerminalTypeContribution, KEYBINDING_CONTEXT_TERMINAL_TABS_MOUSE, KEYBINDING_CONTEXT_TERMINAL_GROUP_COUNT } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalProcessExtHostProxy, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, TERMINAL_VIEW_ID, KEYBINDING_CONTEXT_TERMINAL_COUNT, ITerminalTypeContribution, KEYBINDING_CONTEXT_TERMINAL_TABS_MOUSE, KEYBINDING_CONTEXT_TERMINAL_GROUP_COUNT, ITerminalProfileContribution } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ILifecycleService, ShutdownReason, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
@ -41,6 +41,8 @@ import { VirtualWorkspaceContext } from 'vs/workbench/browser/contextkeys';
|
|||
import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { registerTerminalDefaultProfileConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class TerminalService implements ITerminalService {
|
||||
declare _serviceBrand: undefined;
|
||||
|
@ -56,6 +58,7 @@ export class TerminalService implements ITerminalService {
|
|||
private _findState: FindReplaceState;
|
||||
private _activeGroupIndex: number;
|
||||
private _activeInstanceIndex: number;
|
||||
private readonly _profileProviders: Map<string, ITerminalProfileProvider> = new Map();
|
||||
private _linkProviders: Set<ITerminalExternalLinkProvider> = new Set();
|
||||
private _linkProviderDisposables: Map<ITerminalExternalLinkProvider, IDisposable[]> = new Map();
|
||||
private _processSupportContextKey: IContextKey<boolean>;
|
||||
|
@ -146,6 +149,8 @@ export class TerminalService implements ITerminalService {
|
|||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@ITerminalContributionService private readonly _terminalContributionService: ITerminalContributionService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@optional(ILocalTerminalService) localTerminalService: ILocalTerminalService
|
||||
) {
|
||||
this._localTerminalService = localTerminalService;
|
||||
|
@ -343,18 +348,23 @@ export class TerminalService implements ITerminalService {
|
|||
@throttle(2000)
|
||||
private async _refreshAvailableProfiles(): Promise<void> {
|
||||
const result = await this._detectProfiles();
|
||||
if (!equals(result, this._availableProfiles)) {
|
||||
const profilesChanged = !equals(result, this._availableProfiles);
|
||||
if (profilesChanged) {
|
||||
this._availableProfiles = result;
|
||||
this._onDidChangeAvailableProfiles.fire(this._availableProfiles);
|
||||
this._profilesReadyBarrier.open();
|
||||
const env = await this._remoteAgentService.getEnvironment();
|
||||
registerTerminalDefaultProfileConfiguration({
|
||||
os: env?.os || OS,
|
||||
profiles: this._availableProfiles
|
||||
});
|
||||
await this._refreshPlatformConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private async _refreshPlatformConfig() {
|
||||
const env = await this._remoteAgentService.getEnvironment();
|
||||
registerTerminalDefaultProfileConfiguration({
|
||||
os: env?.os || OS,
|
||||
profiles: this._availableProfiles!
|
||||
});
|
||||
}
|
||||
|
||||
private async _detectProfiles(includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]> {
|
||||
const offProcService = this._offProcessTerminalService;
|
||||
if (!offProcService) {
|
||||
|
@ -793,6 +803,11 @@ export class TerminalService implements ITerminalService {
|
|||
};
|
||||
}
|
||||
|
||||
registerTerminalProfileProvider(id: string, profileProvider: ITerminalProfileProvider): IDisposable {
|
||||
this._profileProviders.set(id, profileProvider);
|
||||
return toDisposable(() => this._profileProviders.delete(id));
|
||||
}
|
||||
|
||||
private _setInstanceLinkProviders(instance: ITerminalInstance): void {
|
||||
for (const linkProvider of this._linkProviders) {
|
||||
const disposables = this._linkProviderDisposables.get(linkProvider);
|
||||
|
@ -882,6 +897,9 @@ export class TerminalService implements ITerminalService {
|
|||
if ('command' in context.item.profile) {
|
||||
return;
|
||||
}
|
||||
if ('id' in context.item.profile) {
|
||||
return;
|
||||
}
|
||||
const configKey = `terminal.integrated.profiles.${platformKey}`;
|
||||
const configProfiles = this._configurationService.getValue<{ [key: string]: ITerminalProfileObject }>(configKey);
|
||||
const existingProfiles = configProfiles ? Object.keys(configProfiles) : [];
|
||||
|
@ -916,17 +934,32 @@ export class TerminalService implements ITerminalService {
|
|||
quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles', "profiles") });
|
||||
quickPickItems.push(...configProfiles.map(e => this._createProfileQuickPickItem(e)));
|
||||
}
|
||||
// Add contributed profiles, these cannot be defaults
|
||||
|
||||
// Add contributed profiles
|
||||
if (type === 'createInstance') {
|
||||
quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles.contributed', "contributed") });
|
||||
for (const contributed of this._terminalContributionService.terminalTypes) {
|
||||
if (this._terminalContributionService.terminalProfiles.length > 0 || this._terminalContributionService.terminalTypes.length > 0) {
|
||||
quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles.contributed', "contributed") });
|
||||
}
|
||||
for (const contributed of this._terminalContributionService.terminalProfiles) {
|
||||
const icon = contributed.icon ? (iconRegistry.get(contributed.icon) || Codicon.terminal) : Codicon.terminal;
|
||||
quickPickItems.push({
|
||||
label: `$(${icon.id}) ${contributed.title}`,
|
||||
profile: contributed
|
||||
});
|
||||
}
|
||||
|
||||
// Add contributed types (legacy), these cannot be defaults
|
||||
if (type === 'createInstance') {
|
||||
for (const contributed of this._terminalContributionService.terminalTypes) {
|
||||
const icon = contributed.icon ? (iconRegistry.get(contributed.icon) || Codicon.terminal) : Codicon.terminal;
|
||||
quickPickItems.push({
|
||||
label: `$(${icon.id}) ${contributed.title}`,
|
||||
profile: contributed
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (autoDetectedProfiles.length > 0) {
|
||||
quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles.detected', "detected") });
|
||||
quickPickItems.push(...autoDetectedProfiles.map(e => this._createProfileQuickPickItem(e)));
|
||||
|
@ -937,30 +970,36 @@ export class TerminalService implements ITerminalService {
|
|||
return;
|
||||
}
|
||||
if (type === 'createInstance') {
|
||||
// TODO: How to support alt here?
|
||||
// Legacy implementation - remove when js-debug adopts new
|
||||
if ('command' in value.profile) {
|
||||
return this._commandService.executeCommand(value.profile.command);
|
||||
}
|
||||
|
||||
let instance;
|
||||
const activeInstance = this.getActiveInstance();
|
||||
if (keyMods?.alt && activeInstance) {
|
||||
// create split, only valid if there's an active instance
|
||||
if (activeInstance) {
|
||||
instance = this.splitInstance(activeInstance, value.profile, cwd);
|
||||
}
|
||||
let instance;
|
||||
|
||||
if ('id' in value.profile) {
|
||||
await this.createContributedTerminalProfile(value.profile.id, !!(keyMods?.alt && activeInstance));
|
||||
return;
|
||||
} else {
|
||||
instance = this.createTerminal(value.profile, cwd);
|
||||
if (keyMods?.alt && activeInstance) {
|
||||
// create split, only valid if there's an active instance
|
||||
instance = this.splitInstance(activeInstance, value.profile, cwd);
|
||||
} else {
|
||||
instance = this.createTerminal(value.profile, cwd);
|
||||
}
|
||||
}
|
||||
|
||||
if (instance) {
|
||||
this.showPanel(true);
|
||||
this.setActiveInstance(instance);
|
||||
return instance;
|
||||
}
|
||||
} else { // setDefault
|
||||
if ('command' in value.profile) {
|
||||
if ('command' in value.profile || 'id' in value.profile) {
|
||||
return; // Should never happen
|
||||
}
|
||||
|
||||
// Add the profile to settings if necessary
|
||||
if (value.profile.isAutoDetected) {
|
||||
const profilesConfig = await this._configurationService.getValue(`terminal.integrated.profiles.${platformKey}`);
|
||||
|
@ -981,6 +1020,16 @@ export class TerminalService implements ITerminalService {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
async createContributedTerminalProfile(id: string, isSplitTerminal: boolean): Promise<void> {
|
||||
await this._extensionService.activateByEvent(`onTerminalProfile:${id}`);
|
||||
const profileProvider = this._profileProviders.get(id);
|
||||
if (!profileProvider) {
|
||||
this._notificationService.error(`No terminal profile provider registered for id "${id}"`);
|
||||
return;
|
||||
}
|
||||
await profileProvider.createContributedTerminalProfile(isSplitTerminal);
|
||||
}
|
||||
|
||||
private _createProfileQuickPickItem(profile: ITerminalProfile): IProfileQuickPickItem {
|
||||
const buttons: IQuickInputButton[] = [{
|
||||
iconClass: ThemeIcon.asClassName(configureTerminalProfileIcon),
|
||||
|
@ -1155,7 +1204,7 @@ export class TerminalService implements ITerminalService {
|
|||
}
|
||||
|
||||
interface IProfileQuickPickItem extends IQuickPickItem {
|
||||
profile: ITerminalProfile | ITerminalTypeContribution;
|
||||
profile: ITerminalProfile | ITerminalTypeContribution | ITerminalProfileContribution;
|
||||
}
|
||||
|
||||
interface IInstanceLocation {
|
||||
|
|
|
@ -229,6 +229,11 @@ export class TerminalViewPane extends ViewPane {
|
|||
dropdownActions.push(new MenuItemAction({ id: contributed.command, title: contributed.title, category: TerminalTabContextMenuGroup.Profile }, undefined, undefined, this._contextKeyService, this._commandService));
|
||||
}
|
||||
|
||||
for (const contributed of this._terminalContributionService.terminalProfiles) {
|
||||
dropdownActions.push(new Action(TerminalCommandId.NewWithProfile, contributed.title, undefined, true, () => this._terminalService.createContributedTerminalProfile(contributed.id, false)));
|
||||
submenuActions.push(new Action(TerminalCommandId.NewWithProfile, contributed.title, undefined, true, () => this._terminalService.createContributedTerminalProfile(contributed.id, true)));
|
||||
}
|
||||
|
||||
if (dropdownActions.length > 0) {
|
||||
dropdownActions.push(new SubmenuAction('split.profile', 'Split...', submenuActions));
|
||||
dropdownActions.push(new Separator());
|
||||
|
|
|
@ -575,7 +575,9 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
|
|||
];
|
||||
|
||||
export interface ITerminalContributions {
|
||||
/** @deprecated */
|
||||
types?: ITerminalTypeContribution[];
|
||||
profiles?: ITerminalProfileContribution[];
|
||||
}
|
||||
|
||||
export interface ITerminalTypeContribution {
|
||||
|
@ -584,6 +586,12 @@ export interface ITerminalTypeContribution {
|
|||
icon?: string;
|
||||
}
|
||||
|
||||
export interface ITerminalProfileContribution {
|
||||
title: string;
|
||||
id: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export const terminalContributionsDescriptor: IExtensionPointDescriptor = {
|
||||
extensionPoint: 'terminal',
|
||||
defaultExtensionKind: 'workspace',
|
||||
|
@ -613,6 +621,28 @@ export const terminalContributionsDescriptor: IExtensionPointDescriptor = {
|
|||
},
|
||||
},
|
||||
},
|
||||
profiles: {
|
||||
type: 'array',
|
||||
description: nls.localize('vscode.extension.contributes.terminal.profiles', "Defines additional terminal profiles that the user can create."),
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['id', 'title'],
|
||||
properties: {
|
||||
command: {
|
||||
description: nls.localize('vscode.extension.contributes.terminal.profiles.id', "The ID of the terminal profile provider."),
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
description: nls.localize('vscode.extension.contributes.terminal.profiles.title', "Title for this terminal profile."),
|
||||
type: 'string',
|
||||
},
|
||||
icon: {
|
||||
description: nls.localize('vscode.extension.contributes.terminal.profiles.icon', "A codicon to associate with this terminal profile."),
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ITerminalTypeContribution, ITerminalContributions, terminalContributionsDescriptor } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalTypeContribution, ITerminalContributions, terminalContributionsDescriptor, ITerminalProfileContribution } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
|
@ -15,6 +15,7 @@ export interface ITerminalContributionService {
|
|||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly terminalTypes: ReadonlyArray<ITerminalTypeContribution>;
|
||||
readonly terminalProfiles: ReadonlyArray<ITerminalProfileContribution>;
|
||||
}
|
||||
|
||||
export const ITerminalContributionService = createDecorator<ITerminalContributionService>('terminalContributionsService');
|
||||
|
@ -23,10 +24,10 @@ export class TerminalContributionService implements ITerminalContributionService
|
|||
declare _serviceBrand: undefined;
|
||||
|
||||
private _terminalTypes: ReadonlyArray<ITerminalTypeContribution> = [];
|
||||
get terminalTypes() { return this._terminalTypes; }
|
||||
|
||||
get terminalTypes() {
|
||||
return this._terminalTypes;
|
||||
}
|
||||
private _terminalProfiles: ReadonlyArray<ITerminalProfileContribution> = [];
|
||||
get terminalProfiles() { return this._terminalProfiles; }
|
||||
|
||||
constructor() {
|
||||
terminalsExtPoint.setHandler(contributions => {
|
||||
|
@ -46,6 +47,18 @@ export class TerminalContributionService implements ITerminalContributionService
|
|||
return e;
|
||||
}) || [];
|
||||
}));
|
||||
this._terminalProfiles = flatten(contributions.filter(c => c.description.enableProposedApi).map(c => {
|
||||
return c.value?.profiles?.map(e => {
|
||||
// Only support $(id) for now, without that it should point to a path to be
|
||||
// consistent with other icon APIs
|
||||
if (e.icon && e.icon.startsWith('$(') && e.icon.endsWith(')')) {
|
||||
e.icon = e.icon.substr(2, e.icon.length - 3);
|
||||
} else {
|
||||
e.icon = undefined;
|
||||
}
|
||||
return e;
|
||||
}) || [];
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -325,6 +325,11 @@ export const schema: IJSONSchema = {
|
|||
description: nls.localize('vscode.extension.activationEvents.onRenderer', 'An activation event emitted whenever a notebook output renderer is used.'),
|
||||
body: 'onRenderer:${11:rendererId}'
|
||||
},
|
||||
{
|
||||
label: 'onTerminalProfile',
|
||||
body: 'onTerminalProfile:${1:terminalType}',
|
||||
description: nls.localize('vscode.extension.activationEvents.onTerminalProfile', 'An activation event emitted when a specific terminal profile is launched.'),
|
||||
},
|
||||
{
|
||||
label: '*',
|
||||
description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'),
|
||||
|
|
Loading…
Reference in a new issue