From 789a91a487d9798c461c9f503e57aec4b76af253 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 25 May 2021 11:31:43 -0700 Subject: [PATCH 01/10] wip --- .../terminal/browser/terminalService.ts | 1 + .../contrib/terminal/common/terminal.ts | 30 +++++++++++++++++++ .../common/terminalExtensionPoints.ts | 21 ++++++++++--- .../extensions/common/extensionsRegistry.ts | 5 ++++ 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index c724ea70dcb..fef476e350e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -909,6 +909,7 @@ export class TerminalService implements ITerminalService { profile: contributed }); } + console.log('profiles', this._terminalContributionService.terminalProfiles); } if (autoDetectedProfiles.length > 0) { quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles.detected', "detected") }); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index a553bd3a868..c3dd052de87 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -574,7 +574,9 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ ]; export interface ITerminalContributions { + /** @deprecated */ types?: ITerminalTypeContribution[]; + profiles?: ITerminalProfileContribution[]; } export interface ITerminalTypeContribution { @@ -583,6 +585,12 @@ export interface ITerminalTypeContribution { icon?: string; } +export interface ITerminalProfileContribution { + title: string; + id: string; + icon?: string; +} + export const terminalContributionsDescriptor: IExtensionPointDescriptor = { extensionPoint: 'terminal', defaultExtensionKind: 'workspace', @@ -612,6 +620,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', + }, + }, + }, + }, }, }, }; diff --git a/src/vs/workbench/contrib/terminal/common/terminalExtensionPoints.ts b/src/vs/workbench/contrib/terminal/common/terminalExtensionPoints.ts index 37b88ccf787..c9ed249caae 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalExtensionPoints.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalExtensionPoints.ts @@ -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; + readonly terminalProfiles: ReadonlyArray; } export const ITerminalContributionService = createDecorator('terminalContributionsService'); @@ -23,10 +24,10 @@ export class TerminalContributionService implements ITerminalContributionService declare _serviceBrand: undefined; private _terminalTypes: ReadonlyArray = []; + get terminalTypes() { return this._terminalTypes; } - get terminalTypes() { - return this._terminalTypes; - } + private _terminalProfiles: ReadonlyArray = []; + 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; + }) || []; + })); }); } } diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 4ebd9782b43..1e91d9bdff2 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -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.'), From e29194ad0d3564307eb32655dcd9db67e88ef112 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 26 May 2021 07:41:16 -0700 Subject: [PATCH 02/10] Activation event, register api --- src/vs/vscode.proposed.d.ts | 19 ++++++ .../api/browser/mainThreadTerminalService.ts | 16 +++++ .../workbench/api/common/extHost.api.impl.ts | 7 ++- .../workbench/api/common/extHost.protocol.ts | 2 + .../api/common/extHostTerminalService.ts | 15 +++++ .../contrib/terminal/browser/terminal.ts | 6 ++ .../terminal/browser/terminalService.ts | 62 +++++++++++++++---- 7 files changed, 114 insertions(+), 13 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 94096b84ecf..b57531bf26d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -916,6 +916,25 @@ 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. + */ + export function registerTerminalProfileProvider(id: string, provider: TerminalProfileProvider): Disposable; + // TODO: id -> profileId, profileType? + } + + export interface TerminalProfileProvider { + /** + * Provide terminal profile options for the requested terminal. + */ + provideProfileOptions(token: CancellationToken): ProviderResult; + } + + //#endregion + // eslint-disable-next-line vscode-dts-region-comments //#region @jrieken -> exclusive document filters diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 56d4075670c..7db6d7492fb 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -33,6 +33,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private _extHostTerminalIds = new Map(); private readonly _toDispose = new DisposableStore(); private readonly _terminalProcessProxies = new Map(); + private readonly _profileProviders = new Map(); private _dataEventTracker: TerminalDataEventTracker | undefined; /** * A single shared terminal link provider for the exthost. When an ext registers a link @@ -200,6 +201,21 @@ 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, { + provideProfile: async () => { + console.log('provide profile', id); + return { name: 'My fake profile' }; + } + })); + } + + public $unregisterProfileProvider(id: string): void { + this._profileProviders.get(id)?.dispose(); + this._profileProviders.delete(id); + } + private _onActiveTerminalChanged(terminalId: number | null): void { this._proxy.$acceptActiveTerminalChanged(terminalId); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 9463dbd723f..d147dcb5aca 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -651,8 +651,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): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3bea115021f..3e39346d27d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -476,6 +476,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 diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index be6ce95e65f..d440b89ada0 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -42,6 +42,7 @@ 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; } @@ -296,6 +297,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I private readonly _bufferer: TerminalDataBufferer; private readonly _linkProviders: Set = new Set(); + private readonly _profileProviders: Map = new Map(); private readonly _terminalLinkCache: Map> = new Map(); private readonly _terminalLinkCancellationSource: Map = new Map(); @@ -564,6 +566,19 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I }); } + public registerProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable { + console.log('registerProfileProvider', id); + 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 $provideLinks(terminalId: number, line: string): Promise { const terminal = this._getTerminalById(terminalId); if (!terminal) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 00c2d9bbc06..5c1f3a4da31 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -198,6 +198,8 @@ export interface ITerminalService { */ registerLinkProvider(linkProvider: ITerminalExternalLinkProvider): IDisposable; + registerTerminalProfileProvider(id: string, profileProvider: ITerminalProfileProvider): IDisposable; + showProfileQuickPick(type: 'setDefault' | 'createInstance', cwd?: string | URI): Promise; getGroupForInstance(instance: ITerminalInstance): ITerminalGroup | undefined; @@ -221,6 +223,10 @@ export interface ITerminalExternalLinkProvider { provideLinks(instance: ITerminalInstance, line: string): Promise; } +export interface ITerminalProfileProvider { + provideProfile(): Promise; +} + export interface ITerminalLink { /** The startIndex of the link in the line. */ startIndex: number; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index fef476e350e..8ad6f68dc24 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -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'; @@ -20,12 +20,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILocalTerminalService, IOffProcessTerminalService, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState, ITerminalProfileProvider } 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 _profileProviders: Map = new Map(); private _linkProviders: Set = new Set(); private _linkProviderDisposables: Map = new Map(); private _processSupportContextKey: IContextKey; @@ -144,6 +147,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; @@ -776,6 +781,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); @@ -865,6 +875,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) : []; @@ -909,7 +922,14 @@ export class TerminalService implements ITerminalService { profile: contributed }); } - console.log('profiles', this._terminalContributionService.terminalProfiles); + console.log('this._terminalContributionService.terminalProfiles', this._terminalContributionService.terminalProfiles); + 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 + }); + } } if (autoDetectedProfiles.length > 0) { quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles.detected', "detected") }); @@ -926,16 +946,33 @@ export class TerminalService implements ITerminalService { 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._extensionService.activateByEvent(`onTerminalProfile:${value.profile.id}`); + const profileProvider = this._profileProviders.get(value.profile.id); + if (!profileProvider) { + this._notificationService.error(`No terminal profile provider registered for id "${value.profile.id}"`); + return; } + const slc = await profileProvider.provideProfile(); + if (keyMods?.alt && activeInstance) { + // create split, only valid if there's an active instance + instance = this.splitInstance(activeInstance, slc); + } else { + instance = this.createTerminal(slc); + } + 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); @@ -945,6 +982,9 @@ export class TerminalService implements ITerminalService { if ('command' in value.profile) { return; // Should never happen } + if ('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}`); @@ -1139,7 +1179,7 @@ export class TerminalService implements ITerminalService { } interface IProfileQuickPickItem extends IQuickPickItem { - profile: ITerminalProfile | ITerminalTypeContribution; + profile: ITerminalProfile | ITerminalTypeContribution | ITerminalProfileContribution; } interface IInstanceLocation { From bf5f7dd5ec19d9137733d3fdbbc5a8f9927cb9e0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 26 May 2021 11:34:11 -0700 Subject: [PATCH 03/10] Get split terminals working --- .../api/browser/mainThreadTerminalService.ts | 16 +++++++++----- .../workbench/api/common/extHost.protocol.ts | 2 ++ .../api/common/extHostTerminalService.ts | 22 +++++++++++++++++-- .../api/node/extHostTerminalService.ts | 4 +++- .../contrib/terminal/browser/terminal.ts | 2 +- .../terminal/browser/terminalService.ts | 17 +++++++------- 6 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index f04d2d7298e..46707154776 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -143,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); } @@ -205,10 +214,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape public $registerProfileProvider(id: string): void { // Proxy profile provider requests through the extension host this._profileProviders.set(id, this._terminalService.registerTerminalProfileProvider(id, { - provideProfile: async () => { - console.log('provide profile', id); - return { name: 'My fake profile' }; - } + createContributedTerminalProfile: async () => this._proxy.$createContributedProfileTerminal(id) })); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8de553dcc9b..0a14ba69172 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -464,6 +464,7 @@ export interface TerminalLaunchConfig { isFeatureTerminal?: boolean; isExtensionOwnedTerminal?: boolean; useShellEnvironment?: boolean; + isSplitTerminal?: boolean; } export interface MainThreadTerminalServiceShape extends IDisposable { @@ -1713,6 +1714,7 @@ export interface ExtHostTerminalServiceShape { $activateLink(id: number, linkId: number): void; $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; $acceptDefaultProfile(profile: ITerminalProfile, automationProfile: ITerminalProfile): void; + $createContributedProfileTerminal(id: string): Promise; } export interface ExtHostSCMShape { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 2d00e434763..ff084ae892d 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -49,6 +49,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID export interface ITerminalInternalOptions { isFeatureTerminal?: boolean; useShellEnvironment?: boolean; + isSplitTerminal?: boolean; } export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); @@ -130,12 +131,13 @@ export class ExtHostTerminal { hideFromUser?: boolean, isFeatureTerminal?: boolean, isExtensionOwnedTerminal?: boolean, - useShellEnvironment?: boolean + useShellEnvironment?: boolean, + isSplitTerminal?: boolean ): Promise { 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: this._name, shellPath, shellArgs, cwd, env, icon, initialText, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal, isExtensionOwnedTerminal, useShellEnvironment, isSplitTerminal }); } public async createExtensionTerminal(iconPath?: URI | { light: URI; dark: URI } | ThemeIcon): Promise { @@ -586,6 +588,22 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I }); } + public async $createContributedProfileTerminal(id: string): Promise { + // TODO: Use cancellation token + const options = await this._profileProviders.get(id)?.provideProfileOptions(new CancellationTokenSource().token); + if (!options) { + throw new Error(`No terminal profile options provided for id "${id}"`); + } + if ('pty' in options) { + // TODO: Pass in split terminal option + this.createExtensionTerminal(options); + } + // if (options.iconPath) { + // checkProposedApiEnabled(extension); + // } + this.createTerminalFromOptions(options, { isSplitTerminal: true }); + } + public async $provideLinks(terminalId: number, line: string): Promise { const terminal = this._getTerminalById(terminalId); if (!terminal) { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 8b70a2fb70f..a9f5afe1c64 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -27,6 +27,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { public createTerminalFromOptions(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name); this._terminals.push(terminal); + // TODO: Pass in options instead of individual props terminal.create( withNullAsUndefined(options.shellPath), withNullAsUndefined(options.shellArgs), @@ -39,7 +40,8 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { withNullAsUndefined(options.hideFromUser), withNullAsUndefined(internalOptions?.isFeatureTerminal), true, - withNullAsUndefined(internalOptions?.useShellEnvironment) + withNullAsUndefined(internalOptions?.useShellEnvironment), + withNullAsUndefined(internalOptions?.isSplitTerminal) ); return terminal.value; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 16802c1818e..061f8fa2afa 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -227,7 +227,7 @@ export interface ITerminalExternalLinkProvider { } export interface ITerminalProfileProvider { - provideProfile(): Promise; + createContributedTerminalProfile(isSplitTerminal: boolean): Promise; } export interface ITerminalLink { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 5c0aa7f802c..938662c8aa6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -962,7 +962,7 @@ 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); } @@ -977,13 +977,14 @@ export class TerminalService implements ITerminalService { this._notificationService.error(`No terminal profile provider registered for id "${value.profile.id}"`); return; } - const slc = await profileProvider.provideProfile(); - if (keyMods?.alt && activeInstance) { - // create split, only valid if there's an active instance - instance = this.splitInstance(activeInstance, slc); - } else { - instance = this.createTerminal(slc); - } + await profileProvider.createContributedTerminalProfile(!!(keyMods?.alt && activeInstance)); + // TODO: Pass in cwd here? cwd should probably still be inherited + // if (keyMods?.alt && activeInstance) { + // // create split, only valid if there's an active instance + // instance = this.splitInstance(activeInstance, slc); + // } else { + // instance = this.createTerminal(slc); + // } return; } else { if (keyMods?.alt && activeInstance) { From 51a5caf3f233941ee6be45bb676433a1ac1b19dc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 26 May 2021 11:36:28 -0700 Subject: [PATCH 04/10] Simplify terminal creation --- src/vs/workbench/api/node/extHostTerminalService.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index a9f5afe1c64..066f257fe84 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -5,6 +5,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; +import { TerminalLaunchConfig } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { BaseExtHostTerminalService, ExtHostTerminal, ITerminalInternalOptions } from 'vs/workbench/api/common/extHostTerminalService'; import type * as vscode from 'vscode'; @@ -18,10 +19,7 @@ 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 { From 08cf3df745775b4ed1668e3efa4e3dfb15359e17 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 26 May 2021 11:40:21 -0700 Subject: [PATCH 05/10] Pass object instead of unwrapped args for term create --- .../api/common/extHostTerminalService.ts | 34 +++++++++++-------- .../api/node/extHostTerminalService.ts | 19 +---------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index ff084ae892d..a668867287e 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -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 { @@ -120,24 +121,27 @@ 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, - isSplitTerminal?: boolean + options: vscode.TerminalOptions, + internalOptions?: ITerminalInternalOptions, ): Promise { 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, isSplitTerminal }); + 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 { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 066f257fe84..652223e1a45 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -3,9 +3,7 @@ * 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 { TerminalLaunchConfig } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { BaseExtHostTerminalService, ExtHostTerminal, ITerminalInternalOptions } from 'vs/workbench/api/common/extHostTerminalService'; import type * as vscode from 'vscode'; @@ -25,22 +23,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { public createTerminalFromOptions(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name); this._terminals.push(terminal); - // TODO: Pass in options instead of individual props - 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), - withNullAsUndefined(internalOptions?.isSplitTerminal) - ); + terminal.create(options, internalOptions); return terminal.value; } } From 5eddbd9d207b2b6307a58998b6b7f2e0a7c64a85 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 26 May 2021 11:48:32 -0700 Subject: [PATCH 06/10] Properly support splitting ext profiles --- .../api/browser/mainThreadTerminalService.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostTerminalService.ts | 19 ++++++++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 46707154776..7b52f5c75d1 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -214,7 +214,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape public $registerProfileProvider(id: string): void { // Proxy profile provider requests through the extension host this._profileProviders.set(id, this._terminalService.registerTerminalProfileProvider(id, { - createContributedTerminalProfile: async () => this._proxy.$createContributedProfileTerminal(id) + createContributedTerminalProfile: async (isSplitTerminal) => this._proxy.$createContributedProfileTerminal(id, isSplitTerminal) })); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0a14ba69172..10988c02e04 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1714,7 +1714,7 @@ export interface ExtHostTerminalServiceShape { $activateLink(id: number, linkId: number): void; $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; $acceptDefaultProfile(profile: ITerminalProfile, automationProfile: ITerminalProfile): void; - $createContributedProfileTerminal(id: string): Promise; + $createContributedProfileTerminal(id: string, isSplitTerminal: boolean): Promise; } export interface ExtHostSCMShape { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index a668867287e..ac5bd4c4e63 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -144,11 +144,16 @@ export class ExtHostTerminal { }); } - public async createExtensionTerminal(iconPath?: URI | { light: URI; dark: URI } | ThemeIcon): Promise { + public async createExtensionTerminal(isSplitTerminal?: boolean, iconPath?: URI | { light: URI; dark: URI } | ThemeIcon): Promise { 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'); @@ -361,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; }); @@ -592,7 +597,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I }); } - public async $createContributedProfileTerminal(id: string): Promise { + public async $createContributedProfileTerminal(id: string, isSplitTerminal: boolean): Promise { // TODO: Use cancellation token const options = await this._profileProviders.get(id)?.provideProfileOptions(new CancellationTokenSource().token); if (!options) { @@ -600,12 +605,12 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } if ('pty' in options) { // TODO: Pass in split terminal option - this.createExtensionTerminal(options); + this.createExtensionTerminal(options, { isSplitTerminal }); } // if (options.iconPath) { // checkProposedApiEnabled(extension); // } - this.createTerminalFromOptions(options, { isSplitTerminal: true }); + this.createTerminalFromOptions(options, { isSplitTerminal }); } public async $provideLinks(terminalId: number, line: string): Promise { From 14a013e7f3c2f6b1b70bb79d89f57969a56ed5e3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 27 May 2021 12:27:57 -0700 Subject: [PATCH 07/10] Support contributed profiles in dropdown --- .../api/common/extHostTerminalService.ts | 7 +++++-- .../contrib/terminal/browser/terminal.ts | 2 ++ .../terminal/browser/terminalService.ts | 18 +++++++++++------- .../contrib/terminal/browser/terminalView.ts | 5 +++++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index ac5bd4c4e63..0816dc881b4 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -598,8 +598,11 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public async $createContributedProfileTerminal(id: string, isSplitTerminal: boolean): Promise { - // TODO: Use cancellation token - const options = await this._profileProviders.get(id)?.provideProfileOptions(new CancellationTokenSource().token); + 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}"`); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 061f8fa2afa..84856ddaff5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -141,6 +141,8 @@ export interface ITerminalService { */ createTerminal(profile: ITerminalProfile): ITerminalInstance; + createContributedTerminalProfile(id: string, isSplitTerminal: boolean): Promise; + /** * Creates a raw terminal instance, this should not be used outside of the terminal part. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 938662c8aa6..0fada1f6cbb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -971,13 +971,7 @@ export class TerminalService implements ITerminalService { let instance; if ('id' in value.profile) { - await this._extensionService.activateByEvent(`onTerminalProfile:${value.profile.id}`); - const profileProvider = this._profileProviders.get(value.profile.id); - if (!profileProvider) { - this._notificationService.error(`No terminal profile provider registered for id "${value.profile.id}"`); - return; - } - await profileProvider.createContributedTerminalProfile(!!(keyMods?.alt && activeInstance)); + await this.createContributedTerminalProfile(value.profile.id, !!(keyMods?.alt && activeInstance)); // TODO: Pass in cwd here? cwd should probably still be inherited // if (keyMods?.alt && activeInstance) { // // create split, only valid if there's an active instance @@ -1027,6 +1021,16 @@ export class TerminalService implements ITerminalService { return undefined; } + async createContributedTerminalProfile(id: string, isSplitTerminal: boolean): Promise { + 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), diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 48b13c22fde..050406da5cf 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -224,6 +224,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()); From e037348d272511df13d51d155989c18c361d4a0f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 27 May 2021 15:17:59 -0700 Subject: [PATCH 08/10] Support launching contributed profiles from dropdown --- src/vs/platform/terminal/common/terminal.ts | 5 ++ .../terminal/browser/terminalService.ts | 82 +++++++++++-------- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 6557e21eb52..b59e48e7986 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -569,6 +569,11 @@ export interface ITerminalProfile { icon?: ThemeIcon | URI | { light: URI, dark: URI }; } +export interface ITerminalContributedProfile { + id: string; + icon?: string; +} + export interface ITerminalDimensionsOverride extends Readonly { /** * indicate that xterm must receive these exact dimensions, even if they overflow the ui! diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 0fada1f6cbb..bc88ba2054f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -58,7 +58,7 @@ export class TerminalService implements ITerminalService { private _findState: FindReplaceState; private _activeGroupIndex: number; private _activeInstanceIndex: number; - private _profileProviders: Map = new Map(); + private readonly _profileProviders: Map = new Map(); private _linkProviders: Set = new Set(); private _linkProviderDisposables: Map = new Map(); private _processSupportContextKey: IContextKey; @@ -352,18 +352,23 @@ export class TerminalService implements ITerminalService { @throttle(2000) private async _refreshAvailableProfiles(): Promise { 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 { const offProcService = this._offProcessTerminalService; if (!offProcService) { @@ -933,17 +938,12 @@ 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) { - const icon = contributed.icon ? (iconRegistry.get(contributed.icon) || Codicon.terminal) : Codicon.terminal; - quickPickItems.push({ - label: `$(${icon.id}) ${contributed.title}`, - profile: contributed - }); + if (this._terminalContributionService.terminalProfiles.length > 0 || this._terminalContributionService.terminalTypes.length > 0) { + quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles.contributed', "contributed") }); } - console.log('this._terminalContributionService.terminalProfiles', this._terminalContributionService.terminalProfiles); for (const contributed of this._terminalContributionService.terminalProfiles) { const icon = contributed.icon ? (iconRegistry.get(contributed.icon) || Codicon.terminal) : Codicon.terminal; quickPickItems.push({ @@ -951,7 +951,19 @@ export class TerminalService implements ITerminalService { 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))); @@ -998,25 +1010,27 @@ export class TerminalService implements ITerminalService { if ('command' in value.profile) { return; // Should never happen } - if ('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}`); - if (typeof profilesConfig === 'object') { - const newProfile: ITerminalProfileObject = { - path: value.profile.path - }; - if (value.profile.args) { - newProfile.args = value.profile.args; - } - (profilesConfig as { [key: string]: ITerminalProfileObject })[value.profile.profileName] = newProfile; - } - await this._configurationService.updateValue(`terminal.integrated.profiles.${platformKey}`, profilesConfig, ConfigurationTarget.USER); - } + // Set the default profile - await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.profileName, ConfigurationTarget.USER); + if ('id' in value.profile) { + await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.title, ConfigurationTarget.USER); + } else { + // Add the profile to settings if necessary + if (value.profile.isAutoDetected) { + const profilesConfig = await this._configurationService.getValue(`terminal.integrated.profiles.${platformKey}`); + if (typeof profilesConfig === 'object') { + const newProfile: ITerminalProfileObject = { + path: value.profile.path + }; + if (value.profile.args) { + newProfile.args = value.profile.args; + } + (profilesConfig as { [key: string]: ITerminalProfileObject })[value.profile.profileName] = newProfile; + } + await this._configurationService.updateValue(`terminal.integrated.profiles.${platformKey}`, profilesConfig, ConfigurationTarget.USER); + } + await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.profileName, ConfigurationTarget.USER); + } } return undefined; } From e8758933d80da1a51f16ba62e0c21ce7343da5cb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 27 May 2021 15:20:36 -0700 Subject: [PATCH 09/10] Disallow custom profiles as default --- .../terminal/browser/terminalService.ts | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index bc88ba2054f..c5103367220 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -1007,30 +1007,26 @@ export class TerminalService implements ITerminalService { return instance; } } else { // setDefault - if ('command' in value.profile) { + if ('command' in value.profile || 'id' in value.profile) { return; // Should never happen } - // Set the default profile - if ('id' in value.profile) { - await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.title, ConfigurationTarget.USER); - } else { - // Add the profile to settings if necessary - if (value.profile.isAutoDetected) { - const profilesConfig = await this._configurationService.getValue(`terminal.integrated.profiles.${platformKey}`); - if (typeof profilesConfig === 'object') { - const newProfile: ITerminalProfileObject = { - path: value.profile.path - }; - if (value.profile.args) { - newProfile.args = value.profile.args; - } - (profilesConfig as { [key: string]: ITerminalProfileObject })[value.profile.profileName] = newProfile; + // Add the profile to settings if necessary + if (value.profile.isAutoDetected) { + const profilesConfig = await this._configurationService.getValue(`terminal.integrated.profiles.${platformKey}`); + if (typeof profilesConfig === 'object') { + const newProfile: ITerminalProfileObject = { + path: value.profile.path + }; + if (value.profile.args) { + newProfile.args = value.profile.args; } - await this._configurationService.updateValue(`terminal.integrated.profiles.${platformKey}`, profilesConfig, ConfigurationTarget.USER); + (profilesConfig as { [key: string]: ITerminalProfileObject })[value.profile.profileName] = newProfile; } - await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.profileName, ConfigurationTarget.USER); + await this._configurationService.updateValue(`terminal.integrated.profiles.${platformKey}`, profilesConfig, ConfigurationTarget.USER); } + // Set the default profile + await this._configurationService.updateValue(`terminal.integrated.defaultProfile.${platformKey}`, value.profile.profileName, ConfigurationTarget.USER); } return undefined; } From 9b609ab1e11ba6aae955ed96a140585ec3b8a7a6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 27 May 2021 15:27:38 -0700 Subject: [PATCH 10/10] Clean up profile provider api --- src/vs/platform/terminal/common/terminal.ts | 5 ----- src/vs/vscode.proposed.d.ts | 4 +++- src/vs/workbench/api/common/extHostTerminalService.ts | 5 ----- .../workbench/contrib/terminal/browser/terminalService.ts | 7 ------- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index b59e48e7986..6557e21eb52 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -569,11 +569,6 @@ export interface ITerminalProfile { icon?: ThemeIcon | URI | { light: URI, dark: URI }; } -export interface ITerminalContributedProfile { - id: string; - icon?: string; -} - export interface ITerminalDimensionsOverride extends Readonly { /** * indicate that xterm must receive these exact dimensions, even if they overflow the ui! diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index dfa1b9d3d95..a1ccee65806 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -921,14 +921,16 @@ declare module 'vscode' { 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; - // TODO: id -> profileId, profileType? } 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; } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 0816dc881b4..dbf762e23b2 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -585,7 +585,6 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public registerProfileProvider(id: string, provider: vscode.TerminalProfileProvider): vscode.Disposable { - console.log('registerProfileProvider', id); if (this._profileProviders.has(id)) { throw new Error(`Terminal profile provider "${id}" already registered`); } @@ -607,12 +606,8 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I throw new Error(`No terminal profile options provided for id "${id}"`); } if ('pty' in options) { - // TODO: Pass in split terminal option this.createExtensionTerminal(options, { isSplitTerminal }); } - // if (options.iconPath) { - // checkProposedApiEnabled(extension); - // } this.createTerminalFromOptions(options, { isSplitTerminal }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index c5103367220..43faf1bef9e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -984,13 +984,6 @@ export class TerminalService implements ITerminalService { if ('id' in value.profile) { await this.createContributedTerminalProfile(value.profile.id, !!(keyMods?.alt && activeInstance)); - // TODO: Pass in cwd here? cwd should probably still be inherited - // if (keyMods?.alt && activeInstance) { - // // create split, only valid if there's an active instance - // instance = this.splitInstance(activeInstance, slc); - // } else { - // instance = this.createTerminal(slc); - // } return; } else { if (keyMods?.alt && activeInstance) {