diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 6ac61419bc9..347a59d1f50 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3328,6 +3328,11 @@ declare namespace vscode { */ export const onDidChangeTextEditorViewColumn: Event; + /** + * An [event](#Event) which fires when a terminal closes. + */ + export const onDidCloseTerminal: Event; + /** * Show the given document in a text editor. A [column](#ViewColumn) can be provided * to control where the editor is being shown. Might change the [active editor](#window.activeTextEditor). diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 8190d313b37..347ae230bbe 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -109,6 +109,7 @@ export class ExtHostAPIImplementation { const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapMonitor, extHostDiagnostics)); const extHostFileSystemEvent = col.define(ExtHostContext.ExtHostFileSystemEventService).set(new ExtHostFileSystemEventService()); const extHostQuickOpen = col.define(ExtHostContext.ExtHostQuickOpen).set(new ExtHostQuickOpen(threadService)); + const extHostTerminalService = col.define(ExtHostContext.ExtHostTerminalService).set(new ExtHostTerminalService(threadService)); col.define(ExtHostContext.ExtHostExtensionService).set(extensionService); col.finish(false, threadService); @@ -122,7 +123,6 @@ export class ExtHostAPIImplementation { const extHostMessageService = new ExtHostMessageService(threadService); const extHostStatusBar = new ExtHostStatusBar(threadService); const extHostOutputService = new ExtHostOutputService(threadService); - const extHostTerminalService = new ExtHostTerminalService(threadService); const workspacePath = contextService.getWorkspace() ? contextService.getWorkspace().resource.fsPath : undefined; const extHostWorkspace = new ExtHostWorkspace(threadService, workspacePath); const languages = new ExtHostLanguages(threadService); @@ -235,6 +235,7 @@ export class ExtHostAPIImplementation { onDidChangeTextEditorViewColumn(listener, thisArg?, disposables?) { return extHostEditors.onDidChangeTextEditorViewColumn(listener, thisArg, disposables); }, + onDidCloseTerminal: extHostTerminalService.onDidCloseTerminal.bind(extHostTerminalService), showInformationMessage: (message, ...items) => { return extHostMessageService.showMessage(Severity.Info, message, items); }, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 1e10df02db2..be5f7ff7d67 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -310,6 +310,10 @@ export abstract class ExtHostQuickOpenShape { $validateInput(input: string): TPromise { throw ni(); } } +export abstract class ExtHostTerminalServiceShape { + $acceptTerminalClosed(id: number): void { throw ni(); } +} + // --- proxy identifiers export const MainContext = { @@ -343,4 +347,5 @@ export const ExtHostContext = { ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures', ExtHostLanguageFeaturesShape), ExtHostQuickOpen: createExtId('ExtHostQuickOpen', ExtHostQuickOpenShape), ExtHostExtensionService: createExtId('ExtHostExtensionService', ExtHostExtensionServiceShape), + ExtHostTerminalService: createExtId('ExtHostTerminalService', ExtHostTerminalServiceShape) }; diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 2253748554b..dbc17f3a3df 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import Event, {Emitter} from 'vs/base/common/event'; import vscode = require('vscode'); -import {MainContext, MainThreadTerminalServiceShape} from './extHost.protocol'; +import {ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape} from './extHost.protocol'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; export class ExtHostTerminal implements vscode.Terminal { @@ -14,10 +15,11 @@ export class ExtHostTerminal implements vscode.Terminal { private _id: number; private _proxy: MainThreadTerminalServiceShape; private _disposed: boolean; - private _queuedRequests: ApiRequest[] = []; + private _queuedRequests: ApiRequest[]; constructor(proxy: MainThreadTerminalServiceShape, name?: string, shellPath?: string, shellArgs?: string[]) { this._name = name; + this._queuedRequests = []; this._proxy = proxy; this._proxy.$createTerminal(name, shellPath, shellArgs).then((id) => { this._id = id; @@ -70,16 +72,48 @@ export class ExtHostTerminal implements vscode.Terminal { } } -export class ExtHostTerminalService { +export class ExtHostTerminalService implements ExtHostTerminalServiceShape { + private _onDidCloseTerminal: Emitter; private _proxy: MainThreadTerminalServiceShape; + private _terminals: ExtHostTerminal[]; constructor(threadService: IThreadService) { + this._onDidCloseTerminal = new Emitter(); this._proxy = threadService.get(MainContext.MainThreadTerminalService); + this._terminals = []; } public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal { - return new ExtHostTerminal(this._proxy, name, shellPath, shellArgs); + let terminal = new ExtHostTerminal(this._proxy, name, shellPath, shellArgs); + this._terminals.push(terminal); + return terminal; + } + + public get onDidCloseTerminal(): Event { + return this._onDidCloseTerminal && this._onDidCloseTerminal.event; + } + + public $acceptTerminalClosed(id: number): void { + let index = this._getTerminalIndexById(id); + if (index === null) { + // The terminal was not created by the terminal API, ignore it + return; + } + let terminal = this._terminals.splice(index, 1)[0]; + this._onDidCloseTerminal.fire(terminal); + } + + private _getTerminalIndexById(id: number): number { + let index: number = null; + this._terminals.some((terminal, i) => { + let thisId = (terminal)._id; + if (thisId === id) { + index = i; + return true; + } + }); + return index; } } diff --git a/src/vs/workbench/api/node/mainThreadTerminalService.ts b/src/vs/workbench/api/node/mainThreadTerminalService.ts index 6c9a57262ab..6e4edf49b25 100644 --- a/src/vs/workbench/api/node/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/node/mainThreadTerminalService.ts @@ -4,20 +4,33 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminal'; +import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; import {IPartService} from 'vs/workbench/services/part/common/partService'; -import {MainThreadTerminalServiceShape} from './extHost.protocol'; +import {ITerminalService, ITerminalInstance} from 'vs/workbench/parts/terminal/electron-browser/terminal'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import {TPromise} from 'vs/base/common/winjs.base'; +import {ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape} from './extHost.protocol'; export class MainThreadTerminalService extends MainThreadTerminalServiceShape { + private _proxy: ExtHostTerminalServiceShape; + private _toDispose: IDisposable[]; + constructor( @IPanelService private panelService: IPanelService, @IPartService private partService: IPartService, + @IThreadService private threadService: IThreadService, @ITerminalService private terminalService: ITerminalService ) { super(); + this._proxy = threadService.get(ExtHostContext.ExtHostTerminalService); + this._toDispose = []; + this._toDispose.push(terminalService.onInstanceClosed((terminalInstance) => this._onTerminalClosed(terminalInstance))); + } + + public dispose(): void { + this._toDispose = dispose(this._toDispose); } public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): TPromise { @@ -51,4 +64,8 @@ export class MainThreadTerminalService extends MainThreadTerminalServiceShape { terminalInstance.sendText(text, addNewLine); } } + + private _onTerminalClosed(terminalInstance: ITerminalInstance): void { + this._proxy.$acceptTerminalClosed(terminalInstance.id); + } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.ts index 1c558a60f49..bfe51b61531 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.ts @@ -58,6 +58,7 @@ export interface ITerminalService { activeTerminalInstanceIndex: number; configHelper: TerminalConfigHelper; onActiveInstanceChanged: Event; + onInstanceClosed: Event; onInstancesChanged: Event; onInstanceTitleChanged: Event; terminalInstances: ITerminalInstance[]; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 551727181bf..c9e96aa6f9f 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -29,12 +29,13 @@ export class TerminalInstance implements ITerminalInstance { private static ID_COUNTER = 1; private static EOL_REGEX = /\r?\n/g; - private _id: number; private _title: string; + private _onClosed: Emitter; private _onTitleChanged: Emitter; public get id(): number { return this._id; } public get title(): string { return this._title; } + public get onClosed(): Event { return this._onClosed.event; } public get onTitleChanged(): Event { return this._onTitleChanged.event; } private isExiting: boolean = false; @@ -59,6 +60,7 @@ export class TerminalInstance implements ITerminalInstance { ) { this._id = TerminalInstance.ID_COUNTER++; this._onTitleChanged = new Emitter(); + this._onClosed = new Emitter(); this.createProcess(workspace, name, shell); if (container) { @@ -168,7 +170,9 @@ export class TerminalInstance implements ITerminalInstance { } this.process = null; } + this._onClosed.fire(this); this.toDispose = lifecycle.dispose(this.toDispose); + // TODO: Move exit callback to listen onClose event this.onExitCallback(this); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 5c91bb1ff06..2e5968b6cdb 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -23,14 +23,16 @@ export class TerminalService implements ITerminalService { private _activeTerminalInstanceIndex: number = 0; private _configHelper: TerminalConfigHelper; private _onActiveInstanceChanged: Emitter; - private _onInstancesChanged: Emitter; + private _onInstanceClosed: Emitter; private _onInstanceTitleChanged: Emitter; + private _onInstancesChanged: Emitter; private _terminalInstances: ITerminalInstance[] = []; public get activeTerminalInstanceIndex(): number { return this._activeTerminalInstanceIndex; } public get configHelper(): TerminalConfigHelper { return this._configHelper; } public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } - public get onInstancesChanged(): Event { return this._onInstancesChanged.event; } + public get onInstanceClosed(): Event { return this._onInstanceClosed.event; } public get onInstanceTitleChanged(): Event { return this._onInstanceTitleChanged.event; } + public get onInstancesChanged(): Event { return this._onInstancesChanged.event; } public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } private terminalContainer: HTMLElement; @@ -45,6 +47,7 @@ export class TerminalService implements ITerminalService { @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService ) { this._onActiveInstanceChanged = new Emitter(); + this._onInstanceClosed = new Emitter(); this._onInstancesChanged = new Emitter(); this._onInstanceTitleChanged = new Emitter(); this.terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this.contextKeyService); @@ -65,6 +68,7 @@ export class TerminalService implements ITerminalService { name, shell); terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); + terminalInstance.addDisposable(terminalInstance.onClosed(this._onInstanceClosed.fire, this._onInstanceClosed)); this.terminalInstances.push(terminalInstance); if (this.terminalInstances.length === 1) { // It's the first instance so it should be made active automatically