diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 45a060e6eacce..3ae78ec6ef729 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -392,13 +392,17 @@ export interface IShellLaunchConfig { } export interface ICreateContributedTerminalProfileOptions { - target?: TerminalLocation; icon?: URI | string | { light: URI, dark: URI }; color?: string; - isSplitTerminal?: boolean; + splitActiveTerminal?: boolean; } -export const enum TerminalLocation { +export enum TerminalLocation { + Panel = 0, + Editor = 1 +} + +export const enum TerminalLocationString { TerminalView = 'view', Editor = 'editor' } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 779844f73f9f6..7b3ee63dcaa97 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -917,6 +917,56 @@ declare module 'vscode' { readonly state: TerminalState; } + export interface TerminalOptions { + location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; + } + + export interface ExtensionTerminalOptions { + location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; + } + + export enum TerminalLocation { + Panel = 0, + Editor = 1, + } + + export interface TerminalEditorLocationOptions { + /** + * A view column in which the {@link Terminal terminal} should be shown in the editor area. + * Use {@link ViewColumn.Active active} to open in the active editor group, other values are + * adjusted to be `Min(column, columnCount + 1)`, the + * {@link ViewColumn.Active active}-column is not adjusted. Use + * {@linkcode ViewColumn.Beside} to open the editor to the side of the currently active one. + */ + viewColumn: ViewColumn; + /** + * An optional flag that when `true` will stop the {@link Terminal} from taking focus. + */ + preserveFocus?: boolean; + } + + export interface TerminalSplitLocationOptions { + /** + * The parent terminal to split this terminal beside. This works whether the parent terminal + * is in the panel or the editor area. + */ + parentTerminal: Terminal; + } + + /** + * An event representing a change in a {@link Terminal.state terminal's state}. + */ + export interface TerminalStateChangeEvent { + /** + * The {@link Terminal} this event occurred on. + */ + readonly terminal: Terminal; + /** + * The {@link Terminal.state current state} of the {@link Terminal}. + */ + readonly state: TerminalState; + } + export namespace window { /** * An {@link Event} which fires when a {@link Terminal.state terminal's state} has changed. diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 54703b9df717b..7029bcd385506 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, TerminalLaunchConfig, ITerminalDimensionsDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, TerminalLaunchConfig, ITerminalDimensionsDto, ExtHostTerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -20,6 +20,7 @@ import { IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy, ITerminal import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { withNullAsUndefined } from 'vs/base/common/types'; import { OperatingSystem, OS } from 'vs/base/common/platform'; +import { TerminalEditorLocationOptions } from 'vscode'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -110,7 +111,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._proxy.$acceptDefaultProfile(...await Promise.all([defaultProfile, defaultAutomationProfile])); } - private async _getTerminalInstance(id: TerminalIdentifier): Promise { + private async _getTerminalInstance(id: ExtHostTerminalIdentifier): Promise { if (typeof id === 'string') { return this._extHostTerminals.get(id); } @@ -137,27 +138,26 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape extHostTerminalId, isFeatureTerminal: launchConfig.isFeatureTerminal, isExtensionOwnedTerminal: launchConfig.isExtensionOwnedTerminal, - useShellEnvironment: launchConfig.useShellEnvironment + useShellEnvironment: launchConfig.useShellEnvironment, }; this._extHostTerminals.set(extHostTerminalId, new Promise(async r => { - let terminal: ITerminalInstance | undefined; - if (launchConfig.isSplitTerminal) { - const activeInstance = this._terminalService.getInstanceHost(launchConfig.target).activeInstance; - if (activeInstance) { - terminal = withNullAsUndefined(await this._terminalService.createTerminal({ instanceToSplit: activeInstance, config: shellLaunchConfig })); - } - } - if (!terminal) { - terminal = await this._terminalService.createTerminal({ - config: shellLaunchConfig, - target: launchConfig.target - }); - } + const terminal = await this._terminalService.createTerminal({ + config: shellLaunchConfig, + location: await this._deserializeParentTerminal(launchConfig.location) + }); r(terminal); })); } - public async $show(id: TerminalIdentifier, preserveFocus: boolean): Promise { + private async _deserializeParentTerminal(location?: TerminalLocation | TerminalEditorLocationOptions | { parentTerminal: ExtHostTerminalIdentifier } | { splitActiveTerminal: boolean }): Promise { + if (typeof location === 'object' && 'parentTerminal' in location) { + const parentTerminal = await this._extHostTerminals.get(location.parentTerminal.toString()); + return parentTerminal ? { parentTerminal } : undefined; + } + return location; + } + + public async $show(id: ExtHostTerminalIdentifier, preserveFocus: boolean): Promise { const terminalInstance = await this._getTerminalInstance(id); if (terminalInstance) { this._terminalService.setActiveInstance(terminalInstance); @@ -169,7 +169,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } - public async $hide(id: TerminalIdentifier): Promise { + public async $hide(id: ExtHostTerminalIdentifier): Promise { const instanceToHide = await this._getTerminalInstance(id); const activeInstance = this._terminalService.activeInstance; if (activeInstance && activeInstance.instanceId === instanceToHide?.instanceId && activeInstance.target !== TerminalLocation.Editor) { @@ -177,11 +177,11 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } - public async $dispose(id: TerminalIdentifier): Promise { + public async $dispose(id: ExtHostTerminalIdentifier): Promise { (await this._getTerminalInstance(id))?.dispose(); } - public async $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): Promise { + public async $sendText(id: ExtHostTerminalIdentifier, text: string, addNewLine: boolean): Promise { const instance = await this._getTerminalInstance(id); await instance?.sendText(text, addNewLine); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b093f1fdf84f2..bf22402b631fc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -646,6 +646,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { if (typeof nameOrOptions === 'object') { + if ('location' in nameOrOptions) { + checkProposedApiEnabled(extension); + } if ('pty' in nameOrOptions) { return extHostTerminalService.createExtensionTerminal(nameOrOptions); } @@ -1234,6 +1237,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TaskRevealKind: extHostTypes.TaskRevealKind, TaskScope: extHostTypes.TaskScope, TerminalLink: extHostTypes.TerminalLink, + TerminalLocation: extHostTypes.TerminalLocation, TerminalProfile: extHostTypes.TerminalProfile, TextDocumentSaveReason: extHostTypes.TextDocumentSaveReason, TextEdit: extHostTypes.TextEdit, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 3bebe24cb2ce4..e523906939081 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -467,7 +467,7 @@ export interface MainThreadProgressShape extends IDisposable { * All other terminals (that are not created on the extension host side) always * use the numeric id. */ -export type TerminalIdentifier = number | string; +export type ExtHostTerminalIdentifier = number | string; export interface TerminalLaunchConfig { name?: string; @@ -485,16 +485,15 @@ export interface TerminalLaunchConfig { isFeatureTerminal?: boolean; isExtensionOwnedTerminal?: boolean; useShellEnvironment?: boolean; - isSplitTerminal?: boolean; - target?: TerminalLocation; + location?: TerminalLocation | { viewColumn: number, preserveFocus?: boolean } | { parentTerminal: ExtHostTerminalIdentifier } | { splitActiveTerminal: boolean }; } export interface MainThreadTerminalServiceShape extends IDisposable { $createTerminal(extHostTerminalId: string, config: TerminalLaunchConfig): Promise; - $dispose(id: TerminalIdentifier): void; - $hide(id: TerminalIdentifier): void; - $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void; - $show(id: TerminalIdentifier, preserveFocus: boolean): void; + $dispose(id: ExtHostTerminalIdentifier): void; + $hide(id: ExtHostTerminalIdentifier): void; + $sendText(id: ExtHostTerminalIdentifier, text: string, addNewLine: boolean): void; + $show(id: ExtHostTerminalIdentifier, preserveFocus: boolean): void; $startSendingDataEvents(): void; $stopSendingDataEvents(): void; $startLinkProvider(): void; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 6693ea0088456..f78b6ec3ac38d 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -5,7 +5,7 @@ import type * as vscode from 'vscode'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, ITerminalDimensionsDto, ITerminalLinkDto, ExtHostTerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; @@ -51,8 +51,8 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID export interface ITerminalInternalOptions { isFeatureTerminal?: boolean; useShellEnvironment?: boolean; - isSplitTerminal?: boolean; - target?: TerminalLocation; + resolvedExtHostIdentifier?: ExtHostTerminalIdentifier; + splitActiveTerminal?: boolean; } export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); @@ -72,7 +72,7 @@ export class ExtHostTerminal { constructor( private _proxy: MainThreadTerminalServiceShape, - public _id: TerminalIdentifier, + public _id: ExtHostTerminalIdentifier, private readonly _creationOptions: vscode.TerminalOptions | vscode.ExtensionTerminalOptions, private _name?: string, ) { @@ -147,12 +147,12 @@ export class ExtHostTerminal { isFeatureTerminal: withNullAsUndefined(internalOptions?.isFeatureTerminal), isExtensionOwnedTerminal: true, useShellEnvironment: withNullAsUndefined(internalOptions?.useShellEnvironment), - isSplitTerminal: internalOptions?.isSplitTerminal, - target: internalOptions?.target + location: this._serializeParentTerminal(options.location, internalOptions?.resolvedExtHostIdentifier, internalOptions?.splitActiveTerminal) }); } - public async createExtensionTerminal(isSplitTerminal?: boolean, target?: TerminalLocation, iconPath?: TerminalIcon, color?: ThemeColor): Promise { + + public async createExtensionTerminal(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions, parentTerminal?: ExtHostTerminalIdentifier, iconPath?: TerminalIcon, color?: ThemeColor): Promise { if (typeof this._id !== 'string') { throw new Error('Terminal has already been created'); } @@ -161,8 +161,7 @@ export class ExtHostTerminal { isExtensionCustomPtyTerminal: true, icon: iconPath, color: ThemeColor.isThemeColor(color) ? color.id : undefined, - isSplitTerminal, - target + location: this._serializeParentTerminal(location, parentTerminal) }); // At this point, the id has been set via `$acceptTerminalOpened` if (typeof this._id === 'string') { @@ -171,6 +170,15 @@ export class ExtHostTerminal { return this._id; } + private _serializeParentTerminal(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions, parentTerminal?: ExtHostTerminalIdentifier, splitActiveTerminal?: boolean): TerminalLocation | vscode.TerminalEditorLocationOptions | { parentTerminal: ExtHostTerminalIdentifier } | { splitActiveTerminal: boolean } | undefined { + if (typeof location === 'object' && 'parentTerminal' in location) { + return parentTerminal ? { parentTerminal } : undefined; + } else if (splitActiveTerminal) { + return { splitActiveTerminal: true }; + } + return location; + } + private _checkDisposed() { if (this._disposed) { throw new Error('Terminal has already been disposed'); @@ -393,7 +401,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I 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(internalOptions?.isSplitTerminal, internalOptions?.target, asTerminalIcon(options.iconPath), asTerminalColor(options.color)).then(id => { + terminal.createExtensionTerminal(this._resolveLocation(options.location), internalOptions?.resolvedExtHostIdentifier, asTerminalIcon(options.iconPath), asTerminalColor(options.color)).then(id => { const disposable = this._setupExtHostProcessListeners(id, p); this._terminalProcessDisposables[id] = disposable; }); @@ -401,6 +409,13 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I return terminal.value; } + private _resolveLocation(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions): undefined | TerminalLocation | vscode.TerminalEditorLocationOptions { + if (typeof location === 'object' && 'parentTerminal' in location) { + return undefined; + } + return location; + } + public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void { const terminal = this._getTerminalById(id); if (!terminal) { @@ -636,6 +651,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I if (!profile || !('options' in profile)) { throw new Error(`No terminal profile options provided for id "${id}"`); } + if ('pty' in profile.options) { this.createExtensionTerminal(profile.options, options); return; @@ -739,7 +755,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I return index !== null ? array[index] : null; } - private _getTerminalObjectIndexById(array: T[], id: TerminalIdentifier): number | null { + private _getTerminalObjectIndexById(array: T[], id: ExtHostTerminalIdentifier): number | null { let index: number | null = null; array.some((item, i) => { const thisId = item._id; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index e1940feef8f37..6aaa5cb18eb82 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1735,6 +1735,11 @@ export class TerminalLink implements vscode.TerminalLink { } } +export enum TerminalLocation { + Panel = 0, + Editor = 1, +} + export class TerminalProfile implements vscode.TerminalProfile { constructor( public options: vscode.TerminalOptions | vscode.ExtensionTerminalOptions diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 652223e1a45ed..dadc49d7e4535 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -23,7 +23,21 @@ 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); - terminal.create(options, internalOptions); + terminal.create(options, this._serializeParentTerminal(options, internalOptions)); return terminal.value; } + + private _serializeParentTerminal(options: vscode.TerminalOptions, internalOptions?: ITerminalInternalOptions): ITerminalInternalOptions { + internalOptions = internalOptions ? internalOptions : {}; + if (options.location && typeof options.location === 'object' && 'parentTerminal' in options.location) { + const parentTerminal = options.location.parentTerminal; + if (parentTerminal) { + const parentExtHostTerminal = this._terminals.find(t => t.value === parentTerminal); + if (parentExtHostTerminal) { + internalOptions.resolvedExtHostIdentifier = parentExtHostTerminal._id; + } + } + } + return internalOptions; + } } diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index af66f24f1deb3..85a3a4d0452a8 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1302,7 +1302,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { if (terminal.group === group) { const originalInstance = terminal.terminal; await originalInstance.waitForTitle(); - result = await this.terminalService.createTerminal({ instanceToSplit: originalInstance, config: launchConfigs }); + result = await this.terminalService.createTerminal({ location: { parentTerminal: originalInstance }, config: launchConfigs }); if (result) { break; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index f51867a263ecb..07436d4c0e829 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource, TerminalShellType, ICreateContributedTerminalProfileOptions, TerminalLocation, IExtensionTerminalProfile, ITerminalProfileType } from 'vs/platform/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource, TerminalShellType, IExtensionTerminalProfile, ITerminalProfileType, TerminalLocation, ICreateContributedTerminalProfileOptions } from 'vs/platform/terminal/common/terminal'; import { ICommandTracker, INavigationMode, IOffProcessTerminalService, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as XTermTerminal } from 'xterm'; import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; @@ -20,6 +20,7 @@ import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { IEditableData } from 'vs/workbench/common/views'; import { DeserializedTerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorSerializer'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; +import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalEditorService = createDecorator('terminalEditorService'); @@ -110,6 +111,7 @@ export interface ITerminalService extends ITerminalInstanceHost { readonly availableProfiles: ITerminalProfile[]; readonly allProfiles: ITerminalProfileType[] | undefined; readonly profilesReady: Promise; + readonly defaultLocation: TerminalLocation; initializeTerminals(): Promise; onDidChangeActiveGroup: Event; @@ -182,7 +184,7 @@ export interface ITerminalService extends ITerminalInstanceHost { safeDisposeTerminal(instance: ITerminalInstance): Promise; getDefaultInstanceHost(): ITerminalInstanceHost; - getInstanceHost(target: TerminalLocation | undefined): ITerminalInstanceHost; + getInstanceHost(target: ITerminalLocationOptions | undefined): ITerminalInstanceHost; getFindHost(instance?: ITerminalInstance): ITerminalFindHost; getDefaultProfileName(): string; @@ -198,7 +200,7 @@ export interface ITerminalEditorService extends ITerminalInstanceHost, ITerminal /** Gets all _terminal editor_ instances. */ readonly instances: readonly ITerminalInstance[]; - openEditor(instance: ITerminalInstance, sideGroup?: boolean): Promise; + openEditor(instance: ITerminalInstance, editorOptions?: TerminalEditorLocation): Promise; detachActiveEditorInstance(): ITerminalInstance; detachInstance(instance: ITerminalInstance): void; splitInstance(instanceToSplit: ITerminalInstance, shellLaunchConfig?: IShellLaunchConfig): ITerminalInstance; @@ -208,6 +210,8 @@ export interface ITerminalEditorService extends ITerminalInstanceHost, ITerminal getInputFromResource(resource: URI): TerminalEditorInput; } +export type ITerminalLocationOptions = TerminalLocation | TerminalEditorLocation | { parentTerminal: ITerminalInstance } | { splitActiveTerminal: boolean }; + export interface ICreateTerminalOptions { /** * The shell launch config or profile to launch with, when not specified the default terminal @@ -219,23 +223,20 @@ export interface ICreateTerminalOptions { * specified. */ cwd?: string | URI; - /** - * Where to create the terminal, when not specified the default target will be used. - */ - target?: TerminalLocation; - /** - * Creates a split terminal without requiring a terminal instance to split, for example when splitting - * a terminal editor - */ - forceSplit?: boolean; /** * The terminal's resource, passed when the terminal has moved windows. */ resource?: URI; + /** - * The terminal instance to split + * The terminal's location (editor or panel), it's terminal parent (split to the right), or editor group */ - instanceToSplit?: ITerminalInstance; + location?: ITerminalLocationOptions; +} + +export interface TerminalEditorLocation { + viewColumn: EditorGroupColumn, + preserveFocus?: boolean } /** diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 1874c449ac34d..0b4372a87650f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -44,6 +44,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; export const switchTerminalActionViewItemSeparator = '─────────'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); @@ -122,7 +123,7 @@ export function registerTerminalActions() { const terminalService = accessor.get(ITerminalService); const terminalGroupService = accessor.get(ITerminalGroupService); if (terminalService.isProcessSupportRegistered) { - const instance = await terminalService.createTerminal({ target: terminalService.configHelper.config.defaultLocation }); + const instance = await terminalService.createTerminal({ location: terminalService.defaultLocation }); if (!instance) { return; } @@ -148,7 +149,7 @@ export function registerTerminalActions() { async run(accessor: ServicesAccessor) { const terminalService = accessor.get(ITerminalService); const instance = await terminalService.createTerminal({ - target: TerminalLocation.Editor + location: TerminalLocation.Editor }); instance.focusWhenReady(); } @@ -167,8 +168,7 @@ export function registerTerminalActions() { async run(accessor: ServicesAccessor) { const terminalService = accessor.get(ITerminalService); const instance = await terminalService.createTerminal({ - target: TerminalLocation.Editor, - forceSplit: true + location: { viewColumn: SIDE_GROUP } }); instance.focusWhenReady(); } @@ -386,7 +386,7 @@ export function registerTerminalActions() { async run(accessor: ServicesAccessor) { const terminalService = accessor.get(ITerminalService); const terminalGroupService = accessor.get(ITerminalGroupService); - const instance = terminalService.activeInstance || await terminalService.createTerminal({ target: TerminalLocation.TerminalView }); + const instance = terminalService.activeInstance || await terminalService.createTerminal({ location: TerminalLocation.Panel }); if (!instance) { return; } @@ -1425,7 +1425,7 @@ export function registerTerminalActions() { const terminalService = accessor.get(ITerminalService); const workspaceContextService = accessor.get(IWorkspaceContextService); const options = convertOptionsOrProfileToOptions(optionsOrProfile); - const activeInstance = terminalService.getInstanceHost(options?.target).activeInstance; + const activeInstance = terminalService.getInstanceHost(options?.location).activeInstance; if (!activeInstance) { return; } @@ -1433,7 +1433,7 @@ export function registerTerminalActions() { if (cwd === undefined) { return undefined; } - const instance = await terminalService.createTerminal({ instanceToSplit: activeInstance, config: options?.config, cwd, target: activeInstance.target }); + const instance = await terminalService.createTerminal({ location: { parentTerminal: activeInstance }, config: options?.config, cwd }); if (instance) { if (instance.target === TerminalLocation.Editor) { instance.focusWhenReady(); @@ -1471,7 +1471,7 @@ export function registerTerminalActions() { terminalService.setActiveInstance(t); terminalService.doWithActiveInstance(async instance => { const cwd = await getCwdForSplit(terminalService.configHelper, instance); - await terminalService.createTerminal({ instanceToSplit: instance, cwd }); + await terminalService.createTerminal({ location: { parentTerminal: instance }, cwd }); await terminalGroupService.showPanel(true); }); } @@ -1546,7 +1546,7 @@ export function registerTerminalActions() { const terminalGroupService = accessor.get(ITerminalGroupService); await terminalService.doWithActiveInstance(async t => { const cwd = await getCwdForSplit(terminalService.configHelper, t); - const instance = await terminalService.createTerminal({ instanceToSplit: t, cwd }); + const instance = await terminalService.createTerminal({ location: { parentTerminal: t }, cwd }); if (instance?.target !== TerminalLocation.Editor) { await terminalGroupService.showPanel(true); } @@ -1613,14 +1613,14 @@ export function registerTerminalActions() { const activeInstance = terminalService.activeInstance; if (activeInstance) { const cwd = await getCwdForSplit(terminalService.configHelper, activeInstance); - await terminalService.createTerminal({ instanceToSplit: activeInstance, cwd }); + await terminalService.createTerminal({ location: { parentTerminal: activeInstance }, cwd }); return; } } if (terminalService.isProcessSupportRegistered) { eventOrOptions = !eventOrOptions || eventOrOptions instanceof MouseEvent ? {} : eventOrOptions; - eventOrOptions.target = eventOrOptions.target || terminalService.configHelper.config.defaultLocation; + eventOrOptions.location = eventOrOptions.location || terminalService.defaultLocation; let instance: ITerminalInstance | undefined; if (folders.length <= 1) { // Allow terminal service to handle the path when there is only a @@ -2026,10 +2026,10 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]) { const folders = workspaceContextService.getWorkspace().folders; if (event && (event.altKey || event.ctrlKey)) { - const activeInstance = terminalService.activeInstance; - if (activeInstance) { - const cwd = await getCwdForSplit(terminalService.configHelper, activeInstance); - await terminalService.createTerminal({ instanceToSplit: activeInstance, config: options?.config, cwd }); + const parentTerminal = terminalService.activeInstance; + if (parentTerminal) { + const cwd = await getCwdForSplit(terminalService.configHelper, parentTerminal); + await terminalService.createTerminal({ location: { parentTerminal }, config: options?.config, cwd }); return; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 3b96d099d469d..5378eddcc561a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -16,7 +16,6 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; @@ -32,6 +31,7 @@ import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { openContextMenu } from 'vs/workbench/contrib/terminal/browser/terminalContextMenu'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; const findWidgetSelector = '.simple-find-part-wrapper'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index 1cd21d81fc364..a13d8ffcb90fa 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -11,7 +11,7 @@ import { EditorActivation } from 'vs/platform/editor/common/editor'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { IShellLaunchConfig, TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IEditorInput } from 'vs/workbench/common/editor'; -import { IRemoteTerminalService, ITerminalEditorService, ITerminalInstance, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IRemoteTerminalService, ITerminalEditorService, ITerminalInstance, ITerminalInstanceService, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalEditor } from 'vs/workbench/contrib/terminal/browser/terminalEditor'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; import { DeserializedTerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorSerializer'; @@ -158,7 +158,7 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor this._onDidChangeActiveInstance.fire(this.activeInstance); } - async openEditor(instance: ITerminalInstance, sideGroup: boolean = false): Promise { + async openEditor(instance: ITerminalInstance, editorOptions?: TerminalEditorLocation): Promise { const resource = this.resolveResource(instance); if (resource) { await this._editorService.openEditor({ @@ -166,10 +166,10 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor options: { pinned: true, - forceReload: true + forceReload: true, + preserveFocus: editorOptions?.preserveFocus } - }, - sideGroup ? SIDE_GROUP : undefined); + }, editorOptions?.viewColumn); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 0468c8df956d3..d80f3b6e1a891 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -17,6 +17,7 @@ import { ICreateTerminalOptions, ITerminalService } from 'vs/workbench/contrib/t import { TerminalCommandId, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys, TerminalContextKeyStrings } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; +import { SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; const enum ContextMenuGroup { Create = '1_create', @@ -571,7 +572,7 @@ export function setupTerminalMenus(): void { }); } -export function getTerminalActionBarArgs(target: TerminalLocation, profiles: ITerminalProfile[], defaultProfileName: string, contributedProfiles: readonly IExtensionTerminalProfile[], instantiationService: IInstantiationService, terminalService: ITerminalService, contextKeyService: IContextKeyService, commandService: ICommandService, dropdownMenu: IMenu): { +export function getTerminalActionBarArgs(location: TerminalLocation, profiles: ITerminalProfile[], defaultProfileName: string, contributedProfiles: readonly IExtensionTerminalProfile[], instantiationService: IInstantiationService, terminalService: ITerminalService, contextKeyService: IContextKeyService, commandService: ICommandService, dropdownMenu: IMenu): { primaryAction: MenuItemAction, dropdownAction: IAction, dropdownMenuActions: IAction[], @@ -586,7 +587,7 @@ export function getTerminalActionBarArgs(target: TerminalLocation, profiles: ITe const options: IMenuActionOptions = { arg: { config: p, - target + location } as ICreateTerminalOptions, shouldForwardArgs: true }; @@ -608,17 +609,16 @@ export function getTerminalActionBarArgs(target: TerminalLocation, profiles: ITe id: contributed.id, title }, - forceSplit: false, - target + location }))); + const splitLocation = location === TerminalLocation.Editor ? { viewColumn: SIDE_GROUP } : location; submenuActions.push(new Action(TerminalCommandId.NewWithProfile, title, undefined, true, () => terminalService.createTerminal({ config: { extensionIdentifier: contributed.extensionIdentifier, id: contributed.id, title }, - forceSplit: true, - target + location: splitLocation }))); } @@ -651,7 +651,7 @@ export function getTerminalActionBarArgs(target: TerminalLocation, profiles: ITe const primaryAction = instantiationService.createInstance( MenuItemAction, { - id: target === TerminalLocation.TerminalView ? TerminalCommandId.New : TerminalCommandId.CreateTerminalEditor, + id: location === TerminalLocation.Panel ? TerminalCommandId.New : TerminalCommandId.CreateTerminalEditor, title: localize('terminal.new', "New Terminal"), icon: Codicon.plus }, @@ -662,7 +662,7 @@ export function getTerminalActionBarArgs(target: TerminalLocation, profiles: ITe }, { shouldForwardArgs: true, - arg: { target } as ICreateTerminalOptions, + arg: { location } as ICreateTerminalOptions, }); const dropdownAction = new Action('refresh profiles', 'Launch Profile...', 'codicon-chevron-down', true); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 09a6d9ddeb12c..a3937089afda1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -23,7 +23,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ICreateContributedTerminalProfileOptions, IExtensionTerminalProfile, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalProfileType, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalSettingId, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal'; +import { ICreateContributedTerminalProfileOptions, IExtensionTerminalProfile, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalProfileType, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString, TerminalSettingId, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal'; import { registerTerminalDefaultProfileConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration'; import { iconForeground } from 'vs/platform/theme/common/colorRegistry'; import { IconDefinition } from 'vs/platform/theme/common/iconRegistry'; @@ -31,7 +31,7 @@ import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { VirtualWorkspaceContext } from 'vs/workbench/browser/contextkeys'; import { IEditableData, IViewsService } from 'vs/workbench/common/views'; -import { ICreateTerminalOptions, IRemoteTerminalService, IRequestAddInstanceToGroupEvent, ITerminalEditorService, ITerminalExternalLinkProvider, ITerminalFindHost, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalProfileProvider, ITerminalService, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ICreateTerminalOptions, IRemoteTerminalService, IRequestAddInstanceToGroupEvent, ITerminalEditorService, ITerminalExternalLinkProvider, ITerminalFindHost, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalProfileProvider, ITerminalService, TerminalConnectionState, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; import { refreshTerminalActions } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalEditor } from 'vs/workbench/contrib/terminal/browser/terminalEditor'; @@ -96,6 +96,8 @@ export class TerminalService implements ITerminalService { return this._terminalGroupService.instances.concat(this._terminalEditorService.instances); } + get defaultLocation(): TerminalLocation { return this.configHelper.config.defaultLocation === TerminalLocationString.Editor ? TerminalLocation.Editor : TerminalLocation.Panel; } + private _activeInstance: ITerminalInstance | undefined; get activeInstance(): ITerminalInstance | undefined { // Check if either an editor or panel terminal has focus and return that, regardless of the @@ -412,7 +414,7 @@ export class TerminalService implements ITerminalService { // create group and terminal terminalInstance = await this.createTerminal({ config: { attachPersistentProcess: terminalLayout.terminal! }, - target: TerminalLocation.TerminalView + location: TerminalLocation.Panel }); group = this._terminalGroupService.getGroupForInstance(terminalInstance); if (groupLayout.isActive) { @@ -420,7 +422,7 @@ export class TerminalService implements ITerminalService { } } else { // add split terminals to this group - await this.createTerminal({ config: { attachPersistentProcess: terminalLayout.terminal! }, instanceToSplit: terminalInstance },); + await this.createTerminal({ config: { attachPersistentProcess: terminalLayout.terminal! }, location: { parentTerminal: terminalInstance } }); } } const activeInstance = this.instances.find(t => { @@ -646,7 +648,7 @@ export class TerminalService implements ITerminalService { await this._localTerminalsInitPromise; } if (this._terminalGroupService.groups.length === 0 && this.isProcessSupportRegistered) { - this.createTerminal({ target: TerminalLocation.TerminalView }); + this.createTerminal({ location: TerminalLocation.Panel }); } } @@ -679,7 +681,7 @@ export class TerminalService implements ITerminalService { if (source.target !== TerminalLocation.Editor) { return; } - source.target = TerminalLocation.TerminalView; + source.target = TerminalLocation.Panel; let group: ITerminalGroup | undefined; if (target) { @@ -937,7 +939,7 @@ export class TerminalService implements ITerminalService { if ('id' in value.profile) { await this._createContributedTerminalProfile(value.profile.extensionIdentifier, value.profile.id, { - isSplitTerminal: !!(keyMods?.alt && activeInstance), + splitActiveTerminal: !!(keyMods?.alt && activeInstance), icon: value.profile.icon, color: value.profile.color }); @@ -945,13 +947,13 @@ export class TerminalService implements ITerminalService { } else { if (keyMods?.alt && activeInstance) { // create split, only valid if there's an active instance - instance = await this.createTerminal({ instanceToSplit: activeInstance, config: value.profile }); + instance = await this.createTerminal({ location: { parentTerminal: activeInstance }, config: value.profile }); } else { - instance = await this.createTerminal({ target: this.configHelper.config.defaultLocation, config: value.profile, cwd }); + instance = await this.createTerminal({ location: this.defaultLocation, config: value.profile, cwd }); } } - if (instance && this.configHelper.config.defaultLocation !== TerminalLocation.Editor) { + if (instance && this.defaultLocation !== TerminalLocation.Editor) { this._terminalGroupService.showPanel(true); this.setActiveInstance(instance); return instance; @@ -992,18 +994,25 @@ export class TerminalService implements ITerminalService { getDefaultInstanceHost(): ITerminalInstanceHost { - if (this.configHelper.config.defaultLocation === TerminalLocation.Editor) { + if (this.defaultLocation === TerminalLocation.Editor) { return this._terminalEditorService; } return this._terminalGroupService; } - getInstanceHost(target: TerminalLocation | undefined): ITerminalInstanceHost { - if (target) { - if (target === TerminalLocation.Editor) { + getInstanceHost(location: ITerminalLocationOptions | undefined): ITerminalInstanceHost { + if (location) { + if (location === TerminalLocation.Editor) { return this._terminalEditorService; + } else if (typeof location === 'object') { + if ('viewColumn' in location) { + return this._terminalEditorService; + } else if ('parentTerminal' in location) { + return location.parentTerminal.target === TerminalLocation.Editor ? this._terminalEditorService : this._terminalGroupService; + } + } else { + return this._terminalGroupService; } - return this._terminalGroupService; } return this; } @@ -1126,12 +1135,12 @@ export class TerminalService implements ITerminalService { // Launch the contributed profile if (contributedProfile) { await this._createContributedTerminalProfile(contributedProfile.extensionIdentifier, contributedProfile.id, { - isSplitTerminal: options?.forceSplit || !!options?.instanceToSplit, icon: contributedProfile.icon, - target: options?.target, - color: contributedProfile.color + color: contributedProfile.color, + splitActiveTerminal: typeof options?.location === 'object' && 'splitActiveTerminal' in options.location ? true : false }); - const instanceHost = options?.target === TerminalLocation.Editor ? this._terminalEditorService : this._terminalGroupService; + // TODO shouldn't the below use defaultLocation? + const instanceHost = options?.location === TerminalLocation.Editor ? this._terminalEditorService : this._terminalGroupService; const instance = instanceHost.instances[instanceHost.instances.length - 1]; await instance.focusWhenReady(); return instance; @@ -1141,15 +1150,6 @@ export class TerminalService implements ITerminalService { shellLaunchConfig.cwd = options.cwd; } - // Use the URI from the base instance if it exists, this will correctly split local terminals - if (options?.instanceToSplit && typeof shellLaunchConfig.cwd !== 'object' && typeof options.instanceToSplit.shellLaunchConfig.cwd === 'object') { - shellLaunchConfig.cwd = URI.from({ - scheme: options.instanceToSplit.shellLaunchConfig.cwd.scheme, - authority: options.instanceToSplit.shellLaunchConfig.cwd.authority, - path: shellLaunchConfig.cwd || options.instanceToSplit.shellLaunchConfig.cwd.path - }); - } - if (!shellLaunchConfig.customPtyImplementation && !this.isProcessSupportRegistered) { throw new Error('Could not create terminal when process support is not registered'); } @@ -1163,34 +1163,85 @@ export class TerminalService implements ITerminalService { } this._evaluateLocalCwd(shellLaunchConfig); + const location = this._resolveLocation(options?.location) || this.defaultLocation; + const parent = this._getSplitParent(options?.location); - let instance: ITerminalInstance; - const target = options?.target || this.configHelper.config.defaultLocation; - if (options?.instanceToSplit) { - if (target === TerminalLocation.Editor || options?.instanceToSplit?.target === TerminalLocation.Editor) { - instance = this._terminalEditorService.splitInstance(options.instanceToSplit, shellLaunchConfig); - } else { - const group = this._terminalGroupService.getGroupForInstance(options.instanceToSplit); - if (!group) { - throw new Error(`Cannot split a terminal without a group ${options.instanceToSplit}`); - } - instance = group.split(shellLaunchConfig); - this._terminalGroupService.groups.forEach((g, i) => g.setVisible(i === this._terminalGroupService.activeGroupIndex)); - } + if (parent) { + return this._splitTerminal(shellLaunchConfig, location, parent); } else { - if (target === TerminalLocation.Editor) { - instance = this._terminalInstanceService.createInstance(shellLaunchConfig, undefined, options?.resource); - instance.target = TerminalLocation.Editor; - this._terminalEditorService.openEditor(instance, options?.forceSplit || !!options?.instanceToSplit); - } else { - // TODO: pass resource? - const group = this._terminalGroupService.createGroup(shellLaunchConfig); - instance = group.terminalInstances[0]; + return this._createTerminal(shellLaunchConfig, location, options); + } + } + + private _splitTerminal(shellLaunchConfig: IShellLaunchConfig, location: TerminalLocation, parent: ITerminalInstance): ITerminalInstance { + let instance; + // Use the URI from the base instance if it exists, this will correctly split local terminals + if (typeof shellLaunchConfig.cwd !== 'object' && typeof parent.shellLaunchConfig.cwd === 'object') { + shellLaunchConfig.cwd = URI.from({ + scheme: parent.shellLaunchConfig.cwd.scheme, + authority: parent.shellLaunchConfig.cwd.authority, + path: shellLaunchConfig.cwd || parent.shellLaunchConfig.cwd.path + }); + } + if (location === TerminalLocation.Editor || parent.target === TerminalLocation.Editor) { + instance = this._terminalEditorService.splitInstance(parent, shellLaunchConfig); + } else { + const group = this._terminalGroupService.getGroupForInstance(parent); + if (!group) { + throw new Error(`Cannot split a terminal without a group ${parent}`); } + instance = group.split(shellLaunchConfig); + this._terminalGroupService.groups.forEach((g, i) => g.setVisible(i === this._terminalGroupService.activeGroupIndex)); + } + return instance; + } + + private _createTerminal(shellLaunchConfig: IShellLaunchConfig, location: TerminalLocation, options?: ICreateTerminalOptions): ITerminalInstance { + let instance; + const editorOptions = this._getEditorOptions(options?.location); + if (location === TerminalLocation.Editor) { + instance = this._terminalInstanceService.createInstance(shellLaunchConfig, undefined, options?.resource); + instance.target = TerminalLocation.Editor; + this._terminalEditorService.openEditor(instance, editorOptions); + } else { + // TODO: pass resource? + const group = this._terminalGroupService.createGroup(shellLaunchConfig); + instance = group.terminalInstances[0]; } return instance; } + private _resolveLocation(location?: ITerminalLocationOptions): TerminalLocation | undefined { + if (!location) { + return location; + } else if (typeof location === 'object') { + if ('parentTerminal' in location) { + return location.parentTerminal.target; + } else if ('viewColumn' in location) { + return TerminalLocation.Editor; + } else if ('splitActiveTerminal' in location) { + return this._activeInstance?.target || this.defaultLocation; + } + } + return location; + } + + private _getSplitParent(location?: ITerminalLocationOptions): ITerminalInstance | undefined { + if (location && typeof location === 'object' && 'parentTerminal' in location) { + return location.parentTerminal; + } else if (location && typeof location === 'object' && 'splitActiveTerminal' in location) { + return this.activeInstance; + } + return undefined; + } + + private _getEditorOptions(location?: ITerminalLocationOptions): TerminalEditorLocation | undefined { + if (location && typeof location === 'object' && 'viewColumn' in location) { + return location; + } + return undefined; + } + private _evaluateLocalCwd(shellLaunchConfig: IShellLaunchConfig) { // Add welcome message and title annotation for local terminals launched within remote or // virtual workspaces diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index ee3fc4f730cb4..7547311d2ee10 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -131,7 +131,7 @@ export class TerminalTabList extends WorkbenchList { this.onMouseDblClick(async e => { const focus = this.getFocus(); if (focus.length === 0) { - const instance = await this._terminalService.createTerminal({ target: TerminalLocation.TerminalView }); + const instance = await this._terminalService.createTerminal({ location: TerminalLocation.Panel }); this._terminalGroupService.setActiveInstance(instance); await instance.focusWhenReady(); } @@ -144,7 +144,7 @@ export class TerminalTabList extends WorkbenchList { // unless multi-selection is in progress this.onMouseClick(async e => { if (e.browserEvent.altKey && e.element) { - await this._terminalService.createTerminal({ instanceToSplit: e.element }); + await this._terminalService.createTerminal({ location: { parentTerminal: e.element } }); } else if (this._getFocusMode() === 'singleClick') { if (this.getSelection().length <= 1) { e.element?.focus(true); @@ -459,7 +459,7 @@ class TerminalTabsRenderer implements IListRenderer { - this._runForSelectionOrInstance(instance, e => this._terminalService.createTerminal({ instanceToSplit: e })); + this._runForSelectionOrInstance(instance, e => this._terminalService.createTerminal({ location: { parentTerminal: e } })); }), new Action(TerminalCommandId.KillInstance, terminalStrings.kill.short, ThemeIcon.asClassName(Codicon.trashcan), true, async () => { this._runForSelectionOrInstance(instance, e => e.dispose()); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index e6d7737c1bce4..6d3d14d466ba0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -136,7 +136,7 @@ export class TerminalViewPane extends ViewPane { if (this._terminalService.isProcessSupportRegistered) { if (this._terminalsInitialized) { if (!hadTerminals) { - this._terminalService.createTerminal({ target: TerminalLocation.TerminalView }); + this._terminalService.createTerminal({ location: TerminalLocation.Panel }); } } else { this._terminalsInitialized = true; @@ -183,7 +183,7 @@ export class TerminalViewPane extends ViewPane { run: async () => { const instance = this._terminalGroupService.activeInstance; if (instance) { - const newInstance = await this._terminalService.createTerminal({ instanceToSplit: instance, forceSplit: true }); + const newInstance = await this._terminalService.createTerminal({ location: { parentTerminal: instance } }); return newInstance?.focusWhenReady(); } return; @@ -204,8 +204,7 @@ export class TerminalViewPane extends ViewPane { this._tabButtons.dispose(); } - const actions = getTerminalActionBarArgs(TerminalLocation.TerminalView, this._terminalService.availableProfiles, this._getDefaultProfileName(), this._terminalContributionService.terminalProfiles, this._instantiationService, this._terminalService, this._contextKeyService, this._commandService, this._dropdownMenu); - + const actions = getTerminalActionBarArgs(TerminalLocation.Panel, this._terminalService.availableProfiles, this._getDefaultProfileName(), this._terminalContributionService.terminalProfiles, this._instantiationService, this._terminalService, this._contextKeyService, this._commandService, this._dropdownMenu); this._tabButtons = new DropdownWithPrimaryActionViewItem(actions.primaryAction, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, {}, this._keybindingService, this._notificationService, this._contextKeyService); this._updateTabActionBar(this._terminalService.availableProfiles); return this._tabButtons; @@ -229,7 +228,7 @@ export class TerminalViewPane extends ViewPane { } private _updateTabActionBar(profiles: ITerminalProfile[]): void { - const actions = getTerminalActionBarArgs(TerminalLocation.TerminalView, profiles, this._getDefaultProfileName(), this._terminalContributionService.terminalProfiles, this._instantiationService, this._terminalService, this._contextKeyService, this._commandService, this._dropdownMenu); + const actions = getTerminalActionBarArgs(TerminalLocation.Panel, profiles, this._getDefaultProfileName(), this._terminalContributionService.terminalProfiles, this._instantiationService, this._terminalService, this._contextKeyService, this._commandService, this._dropdownMenu); this._tabButtons?.update(actions.dropdownAction, actions.dropdownMenuActions); } @@ -388,7 +387,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { override async onClick(event: MouseEvent): Promise { if (event.altKey && this._menuItemAction.alt) { - this._commandService.executeCommand(this._menuItemAction.alt.id, { target: TerminalLocation.TerminalView } as ICreateTerminalOptions); + this._commandService.executeCommand(this._menuItemAction.alt.id, { target: TerminalLocation.Panel } as ICreateTerminalOptions); } else { this._openContextMenu(); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index da6cfced3cf4c..527695acad050 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessReadyEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalIcon, TerminalLocationString, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; @@ -203,7 +203,7 @@ export interface ITerminalConfiguration { focusMode: 'singleClick' | 'doubleClick'; }, bellDuration: number; - defaultLocation: TerminalLocation; + defaultLocation: TerminalLocationString; customGlyphs: boolean; } diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index cc6a33a2bc3d8..2f5a6aea18979 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -6,7 +6,7 @@ import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { localize } from 'vs/nls'; import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -78,7 +78,7 @@ const terminalConfiguration: IConfigurationNode = { }, [TerminalSettingId.DefaultLocation]: { type: 'string', - enum: [TerminalLocation.Editor, TerminalLocation.TerminalView], + enum: [TerminalLocationString.Editor, TerminalLocationString.TerminalView], enumDescriptions: [ localize('terminal.integrated.defaultLocation.editor', "Create terminals in the editor"), localize('terminal.integrated.defaultLocation.view', "Create terminals in the terminal view")