diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 651014c5e64..cf16787d23b 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto, TerminalIdentifier } 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'; @@ -21,6 +21,12 @@ import { ILogService } from 'vs/platform/log/common/log'; export class MainThreadTerminalService implements MainThreadTerminalServiceShape { private _proxy: ExtHostTerminalServiceShape; + /** + * Stores a map from a temporary terminal id (a UUID generated on the extension host side) + * to a numeric terminal id (an id generated on the renderer side) + * This comes in play only when dealing with terminals created on the extension host side + */ + private _extHostTerminalIds = new Map(); private _remoteAuthority: string | null; private readonly _toDispose = new DisposableStore(); private readonly _terminalProcessProxies = new Map(); @@ -47,13 +53,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // ITerminalService listeners this._toDispose.add(_terminalService.onInstanceCreated((instance) => { - // Delay this message so the TerminalInstance constructor has a chance to finish and - // return the ID normally to the extension host. The ID that is passed here will be - // used to register non-extension API terminals in the extension host. - setTimeout(() => { - this._onTerminalOpened(instance); - this._onInstanceDimensionsChanged(instance); - }, EXT_HOST_CREATION_DELAY); + this._onTerminalOpened(instance); + this._onInstanceDimensionsChanged(instance); })); this._toDispose.add(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance))); @@ -100,7 +101,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // when the extension host process goes down ? } - public $createTerminal(launchConfig: TerminalLaunchConfig): Promise<{ id: number, name: string }> { + private _getTerminalId(id: TerminalIdentifier): number | undefined { + if (typeof id === 'number') { + return id; + } + return this._extHostTerminalIds.get(id); + } + + private _getTerminalInstance(id: TerminalIdentifier): ITerminalInstance | undefined { + const rendererId = this._getTerminalId(id); + if (typeof rendererId === 'number') { + return this._terminalService.getInstanceFromId(rendererId); + } + return undefined; + } + + public async $createTerminal(extHostTerminalId: string, launchConfig: TerminalLaunchConfig): Promise { const shellLaunchConfig: IShellLaunchConfig = { name: launchConfig.name, executable: launchConfig.shellPath, @@ -112,39 +128,38 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape strictEnv: launchConfig.strictEnv, hideFromUser: launchConfig.hideFromUser, isExtensionTerminal: launchConfig.isExtensionTerminal, + extHostTerminalId: extHostTerminalId, isFeatureTerminal: launchConfig.isFeatureTerminal }; const terminal = this._terminalService.createTerminal(shellLaunchConfig); - return Promise.resolve({ - id: terminal.id, - name: terminal.title - }); + this._extHostTerminalIds.set(extHostTerminalId, terminal.id); } - public $show(terminalId: number, preserveFocus: boolean): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); + public $show(id: TerminalIdentifier, preserveFocus: boolean): void { + const terminalInstance = this._getTerminalInstance(id); if (terminalInstance) { this._terminalService.setActiveInstance(terminalInstance); this._terminalService.showPanel(!preserveFocus); } } - public $hide(terminalId: number): void { + public $hide(id: TerminalIdentifier): void { + const rendererId = this._getTerminalId(id); const instance = this._terminalService.getActiveInstance(); - if (instance && instance.id === terminalId) { + if (instance && instance.id === rendererId) { this._terminalService.hidePanel(); } } - public $dispose(terminalId: number): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); + public $dispose(id: TerminalIdentifier): void { + const terminalInstance = this._getTerminalInstance(id); if (terminalInstance) { terminalInstance.dispose(); } } - public $sendText(terminalId: number, text: string, addNewLine: boolean): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); + public $sendText(id: TerminalIdentifier, text: string, addNewLine: boolean): void { + const terminalInstance = this._getTerminalInstance(id); if (terminalInstance) { terminalInstance.sendText(text, addNewLine); } @@ -204,6 +219,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } private _onTerminalOpened(terminalInstance: ITerminalInstance): void { + const extHostTerminalId = terminalInstance.shellLaunchConfig.extHostTerminalId; const shellLaunchConfigDto: IShellLaunchConfigDto = { name: terminalInstance.shellLaunchConfig.name, executable: terminalInstance.shellLaunchConfig.executable, @@ -212,13 +228,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape env: terminalInstance.shellLaunchConfig.env, hideFromUser: terminalInstance.shellLaunchConfig.hideFromUser }; - if (terminalInstance.title) { - this._proxy.$acceptTerminalOpened(terminalInstance.id, terminalInstance.title, shellLaunchConfigDto); - } else { - terminalInstance.waitForTitle().then(title => { - this._proxy.$acceptTerminalOpened(terminalInstance.id, title, shellLaunchConfigDto); - }); - } + this._proxy.$acceptTerminalOpened(terminalInstance.id, extHostTerminalId, terminalInstance.title, shellLaunchConfigDto); } private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void { @@ -294,32 +304,53 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $sendProcessTitle(terminalId: number, title: string): void { - this._getTerminalProcess(terminalId).emitTitle(title); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitTitle(title); + } } public $sendProcessData(terminalId: number, data: string): void { - this._getTerminalProcess(terminalId).emitData(data); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitData(data); + } } public $sendProcessReady(terminalId: number, pid: number, cwd: string): void { - this._getTerminalProcess(terminalId).emitReady(pid, cwd); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitReady(pid, cwd); + } } public $sendProcessExit(terminalId: number, exitCode: number | undefined): void { - this._getTerminalProcess(terminalId).emitExit(exitCode); - this._terminalProcessProxies.delete(terminalId); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitExit(exitCode); + this._terminalProcessProxies.delete(terminalId); + } } public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void { - this._getTerminalProcess(terminalId).emitOverrideDimensions(dimensions); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitOverrideDimensions(dimensions); + } } public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void { - this._getTerminalProcess(terminalId).emitInitialCwd(initialCwd); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitInitialCwd(initialCwd); + } } public $sendProcessCwd(terminalId: number, cwd: string): void { - this._getTerminalProcess(terminalId).emitCwd(cwd); + const terminalProcess = this._terminalProcessProxies.get(terminalId); + if (terminalProcess) { + terminalProcess.emitCwd(cwd); + } } public $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ab836952d17..09d50bcc5f8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -442,6 +442,16 @@ export interface MainThreadProgressShape extends IDisposable { $progressEnd(handle: number): void; } +/** + * A terminal that is created on the extension host side is temporarily assigned + * a UUID by the extension host that created it. Once the renderer side has assigned + * a real numeric id, the numeric id will be used. + * + * All other terminals (that are not created on the extension host side) always + * use the numeric id. + */ +export type TerminalIdentifier = number | string; + export interface TerminalLaunchConfig { name?: string; shellPath?: string; @@ -456,11 +466,11 @@ export interface TerminalLaunchConfig { } export interface MainThreadTerminalServiceShape extends IDisposable { - $createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string; }>; - $dispose(terminalId: number): void; - $hide(terminalId: number): void; - $sendText(terminalId: number, text: string, addNewLine: boolean): void; - $show(terminalId: number, preserveFocus: boolean): void; + $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; $startSendingDataEvents(): void; $stopSendingDataEvents(): void; $startLinkProvider(): void; @@ -1524,7 +1534,7 @@ export interface ITerminalDimensionsDto { export interface ExtHostTerminalServiceShape { $acceptTerminalClosed(id: number, exitCode: number | undefined): void; - $acceptTerminalOpened(id: number, name: string, shellLaunchConfig: IShellLaunchConfigDto): void; + $acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfig: IShellLaunchConfigDto): void; $acceptActiveTerminalChanged(id: number | null): void; $acceptTerminalProcessId(id: number, processId: number): void; $acceptTerminalProcessData(id: number, data: string): void; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 1cccd958fb6..00e09861cfb 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -5,12 +5,11 @@ import type * as vscode from 'vscode'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ITerminalChildProcess, EXT_HOST_CREATION_DELAY, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; -import { timeout } from 'vs/base/common/async'; +import { ITerminalChildProcess, ITerminalLaunchError, ITerminalDimensionsOverride } from 'vs/workbench/contrib/terminal/common/terminal'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; @@ -21,6 +20,7 @@ import { localize } from 'vs/nls'; import { NotSupportedError } from 'vs/base/common/errors'; import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { generateUuid } from 'vs/base/common/uuid'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable { @@ -47,63 +47,8 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, ID export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); -export class BaseExtHostTerminal { - public _id: number | undefined; - protected _idPromise: Promise; - private _idPromiseComplete: ((value: number) => any) | undefined; +export class ExtHostTerminal implements vscode.Terminal { private _disposed: boolean = false; - private _queuedRequests: ApiRequest[] = []; - - constructor( - protected _proxy: MainThreadTerminalServiceShape, - id?: number - ) { - this._idPromise = new Promise(c => { - if (id !== undefined) { - this._id = id; - c(id); - } else { - this._idPromiseComplete = c; - } - }); - } - - public dispose(): void { - if (!this._disposed) { - this._disposed = true; - this._queueApiRequest(this._proxy.$dispose, []); - } - } - - protected _checkDisposed() { - if (this._disposed) { - throw new Error('Terminal has already been disposed'); - } - } - - protected _queueApiRequest(callback: (...args: any[]) => void, args: any[]): void { - const request: ApiRequest = new ApiRequest(callback, args); - if (!this._id) { - this._queuedRequests.push(request); - return; - } - request.run(this._proxy, this._id); - } - - public _runQueuedRequests(id: number): void { - this._id = id; - if (this._idPromiseComplete) { - this._idPromiseComplete(id); - this._idPromiseComplete = undefined; - } - this._queuedRequests.forEach((r) => { - r.run(this._proxy, id); - }); - this._queuedRequests.length = 0; - } -} - -export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal { private _pidPromise: Promise; private _cols: number | undefined; private _pidPromiseComplete: ((value: number | undefined) => any) | undefined; @@ -113,12 +58,11 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi public isOpen: boolean = false; constructor( - proxy: MainThreadTerminalServiceShape, + private _proxy: MainThreadTerminalServiceShape, + public _id: TerminalIdentifier, private readonly _creationOptions: vscode.TerminalOptions | vscode.ExtensionTerminalOptions, private _name?: string, - id?: number ) { - super(proxy, id); this._creationOptions = Object.freeze(this._creationOptions); this._pidPromise = new Promise(c => this._pidPromiseComplete = c); } @@ -133,16 +77,35 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi hideFromUser?: boolean, isFeatureTerminal?: boolean ): Promise { - const result = await this._proxy.$createTerminal({ name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal }); - this._name = result.name; - this._runQueuedRequests(result.id); + 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, waitOnExit, strictEnv, hideFromUser, isFeatureTerminal }); } public async createExtensionTerminal(): Promise { - const result = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true }); - this._name = result.name; - this._runQueuedRequests(result.id); - return result.id; + if (typeof this._id !== 'string') { + throw new Error('Terminal has already been created'); + } + await this._proxy.$createTerminal(this._id, { name: this._name, isExtensionTerminal: true }); + // At this point, the id has been set via `$acceptTerminalOpened` + if (typeof this._id === 'string') { + throw new Error('Terminal creation failed'); + } + return this._id; + } + + public dispose(): void { + if (!this._disposed) { + this._disposed = true; + this._proxy.$dispose(this._id); + } + } + + private _checkDisposed() { + if (this._disposed) { + throw new Error('Terminal has already been disposed'); + } } public get name(): string { @@ -194,17 +157,17 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi public sendText(text: string, addNewLine: boolean = true): void { this._checkDisposed(); - this._queueApiRequest(this._proxy.$sendText, [text, addNewLine]); + this._proxy.$sendText(this._id, text, addNewLine); } public show(preserveFocus: boolean): void { this._checkDisposed(); - this._queueApiRequest(this._proxy.$show, [preserveFocus]); + this._proxy.$show(this._id, preserveFocus); } public hide(): void { this._checkDisposed(); - this._queueApiRequest(this._proxy.$hide, []); + this._proxy.$hide(this._id); } public _setProcessId(processId: number | undefined): void { @@ -223,20 +186,6 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi } } -class ApiRequest { - private _callback: (...args: any[]) => void; - private _args: any[]; - - constructor(callback: (...args: any[]) => void, args: any[]) { - this._callback = callback; - this._args = args; - } - - public run(proxy: MainThreadTerminalServiceShape, id: number) { - this._callback.apply(proxy, [id].concat(this._args)); - } -} - export class ExtHostPseudoterminal implements ITerminalChildProcess { private readonly _onProcessData = new Emitter(); public readonly onProcessData: Event = this._onProcessData.event; @@ -370,7 +319,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, options, options.name); + const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name); const p = new ExtHostPseudoterminal(options.pty); terminal.createExtensionTerminal().then(id => { const disposable = this._setupExtHostProcessListeners(id, p); @@ -381,7 +330,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void { - const terminal = this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (!terminal) { throw new Error(`Cannot resolve terminal with id ${id} for virtual process`); } @@ -399,7 +348,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } return; } - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (terminal) { this._activeTerminal = terminal; if (original !== this._activeTerminal) { @@ -409,14 +358,14 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public async $acceptTerminalProcessData(id: number, data: string): Promise { - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (terminal) { this._onDidWriteTerminalData.fire({ terminal, data }); } } public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise { - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (terminal) { if (terminal.setDimensions(cols, rows)) { this._onDidChangeTerminalDimensions.fire({ @@ -428,23 +377,19 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } public async $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): Promise { - await this._getTerminalByIdEventually(id); - // Extension pty terminal only - when virtual process resize fires it means that the // terminal's maximum dimensions changed this._terminalProcesses.get(id)?.resize(cols, rows); } public async $acceptTerminalTitleChange(id: number, name: string): Promise { - await this._getTerminalByIdEventually(id); - const extHostTerminal = this._getTerminalObjectById(this.terminals, id); - if (extHostTerminal) { - extHostTerminal.name = name; + const terminal = this._getTerminalById(id); + if (terminal) { + terminal.name = name; } } public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise { - await this._getTerminalByIdEventually(id); const index = this._getTerminalObjectIndexById(this.terminals, id); if (index !== null) { const terminal = this._terminals.splice(index, 1)[0]; @@ -453,13 +398,17 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } } - public $acceptTerminalOpened(id: number, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void { - const index = this._getTerminalObjectIndexById(this._terminals, id); - if (index !== null) { - // The terminal has already been created (via createTerminal*), only fire the event - this._onDidOpenTerminal.fire(this.terminals[index]); - this.terminals[index].isOpen = true; - return; + public $acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfigDto: IShellLaunchConfigDto): void { + if (extHostTerminalId) { + // Resolve with the renderer generated id + const index = this._getTerminalObjectIndexById(this._terminals, extHostTerminalId); + if (index !== null) { + // The terminal has already been created (via createTerminal*), only fire the event + this.terminals[index]._id = id; + this._onDidOpenTerminal.fire(this.terminals[index]); + this.terminals[index].isOpen = true; + return; + } } const creationOptions: vscode.TerminalOptions = { @@ -470,14 +419,14 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I env: shellLaunchConfigDto.env, hideFromUser: shellLaunchConfigDto.hideFromUser }; - const terminal = new ExtHostTerminal(this._proxy, creationOptions, name, id); + const terminal = new ExtHostTerminal(this._proxy, id, creationOptions, name); this._terminals.push(terminal); this._onDidOpenTerminal.fire(terminal); terminal.isOpen = true; } public async $acceptTerminalProcessId(id: number, processId: number): Promise { - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (terminal) { terminal._setProcessId(processId); } @@ -486,7 +435,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise { // Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call // Pseudoterminal.start - const terminal = await this._getTerminalByIdEventually(id); + const terminal = this._getTerminalById(id); if (!terminal) { return { message: localize('launchFail.idMissingOnExtHost', "Could not find the terminal with id {0} on the extension host", id) }; } @@ -670,32 +619,6 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I this._proxy.$sendProcessExit(id, exitCode); } - // TODO: This could be improved by using a single promise and resolve it when the terminal is ready - private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { - if (!this._getTerminalPromises[id]) { - this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries); - } - return this._getTerminalPromises[id]; - } - - private _createGetTerminalPromise(id: number, retries: number = 5): Promise { - return new Promise(c => { - if (retries === 0) { - c(undefined); - return; - } - - const terminal = this._getTerminalById(id); - if (terminal) { - c(terminal); - } else { - // This should only be needed immediately after createTerminalRenderer is called as - // the ExtHostTerminal has not yet been iniitalized - timeout(EXT_HOST_CREATION_DELAY * 2).then(() => c(this._createGetTerminalPromise(id, retries - 1))); - } - }); - } - private _getTerminalById(id: number): ExtHostTerminal | null { return this._getTerminalObjectById(this._terminals, id); } @@ -705,7 +628,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I return index !== null ? array[index] : null; } - private _getTerminalObjectIndexById(array: T[], id: number): number | null { + private _getTerminalObjectIndexById(array: T[], id: TerminalIdentifier): number | null { let index: number | null = null; array.some((item, i) => { const thisId = item._id; diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 0b4a07af8f5..3f411f061a3 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -25,6 +25,7 @@ import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/termin import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { withNullAsUndefined } from 'vs/base/common/types'; import { getSystemShell } from 'vs/base/node/shell'; +import { generateUuid } from 'vs/base/common/uuid'; export class ExtHostTerminalService extends BaseExtHostTerminalService { @@ -49,14 +50,14 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { } public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, { name, shellPath, shellArgs }, name); + const terminal = new ExtHostTerminal(this._proxy, generateUuid(), { name, shellPath, shellArgs }, name); this._terminals.push(terminal); terminal.create(shellPath, shellArgs); return terminal; } public createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal { - const terminal = new ExtHostTerminal(this._proxy, options, options.name); + const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name); this._terminals.push(terminal); terminal.create( withNullAsUndefined(options.shellPath), diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index f7768934872..a3b3f9eff25 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -53,11 +53,6 @@ export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverM export const TERMINAL_CREATION_COMMANDS = ['workbench.action.terminal.toggleTerminal', 'workbench.action.terminal.new', 'workbench.action.togglePanel', 'workbench.action.terminal.focus']; -// The creation of extension host terminals is delayed by this value (milliseconds). The purpose of -// this delay is to allow the terminal instance to initialize correctly and have its ID set before -// trying to create the corressponding object on the ext host. -export const EXT_HOST_CREATION_DELAY = 100; - export const TerminalCursorStyle = { BLOCK: 'block', LINE: 'line', @@ -259,6 +254,11 @@ export interface IShellLaunchConfig { */ isExtensionTerminal?: boolean; + /** + * A UUID generated by the extension host process for terminals created on the extension host process. + */ + extHostTerminalId?: string; + /** * This is a terminal that attaches to an already running remote terminal. */