diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d09cbd0ba1e..db0a30804fc 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -809,4 +809,53 @@ declare module 'vscode' { */ export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg?: any): Disposable; } + + export interface Terminal { + + /** + * The name of the terminal. + */ + readonly name: string; + + /** + * The process ID of the shell process. + */ + readonly processId: Thenable; + + /** + * Send text to the terminal. The text is written to the stdin of the underlying pty process + * (shell) of the terminal. + * + * @param text The text to send. + * @param addNewLine Whether to add a new line to the text being sent, this is normally + * required to run a command in the terminal. The character(s) added are \n or \r\n + * depending on the platform. This defaults to `true`. + */ + sendText(text: string, addNewLine?: boolean): void; + + /** + * Show the terminal panel and reveal this terminal in the UI. + * + * @param preserveFocus When `true` the terminal will not take focus. + */ + show(preserveFocus?: boolean): void; + + /** + * Hide the terminal panel if this terminal is currently showing. + */ + hide(): void; + + /** + * Dispose and free associated resources. + */ + dispose(): void; + + /** + * Experimental API that allows listening to the raw data stream coming from the terminal's + * pty process (including ANSI escape sequences). + * + * @param callback The callback that is triggered when data is sent to the terminal. + */ + onData(callback: (data: string) => any): void; + } } \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 0e44c44a332..c8c0dadd479 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -204,6 +204,7 @@ export abstract class MainThreadTerminalServiceShape { $hide(terminalId: number): void { throw ni(); } $sendText(terminalId: number, text: string, addNewLine: boolean): void { throw ni(); } $show(terminalId: number, preserveFocus: boolean): void { throw ni(); } + $registerOnData(terminalId: number): void { throw ni(); } } export interface MyQuickPickItems extends IPickOpenEntry { @@ -413,6 +414,7 @@ export abstract class ExtHostQuickOpenShape { export abstract class ExtHostTerminalServiceShape { $acceptTerminalClosed(id: number): void { throw ni(); } $acceptTerminalProcessId(id: number, processId: number): void { throw ni(); } + $acceptTerminalData(id: number, data: string): void { throw ni(); } } export abstract class ExtHostSCMShape { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 2729f12071b..d3a5a42dc6f 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -20,6 +20,8 @@ export class ExtHostTerminal implements vscode.Terminal { private _pidPromise: TPromise; private _pidPromiseComplete: TValueCallback; + private _onDataCallback: (data: string) => any; + constructor( proxy: MainThreadTerminalServiceShape, name?: string, @@ -67,6 +69,11 @@ export class ExtHostTerminal implements vscode.Terminal { this._queueApiRequest(this._proxy.$hide, []); } + public onData(callback: (data: string) => any): void { + this._onDataCallback = callback; + this._queueApiRequest(this._proxy.$registerOnData, []); + } + public dispose(): void { if (!this._disposed) { this._disposed = true; @@ -79,6 +86,10 @@ export class ExtHostTerminal implements vscode.Terminal { this._pidPromiseComplete = null; } + public _onData(data: string): void { + this._onDataCallback(data); + } + private _queueApiRequest(callback: (...args: any[]) => void, args: any[]) { let request: ApiRequest = new ApiRequest(callback, args); if (!this._id) { @@ -138,6 +149,11 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { terminal._setProcessId(processId); } + public $acceptTerminalData(id: number, data: string): void { + let terminal = this._getTerminalById(id); + terminal._onData(data); + } + private _getTerminalById(id: number): ExtHostTerminal { let index = this._getTerminalIndexById(id); return index !== null ? this._terminals[index] : null; diff --git a/src/vs/workbench/api/node/mainThreadTerminalService.ts b/src/vs/workbench/api/node/mainThreadTerminalService.ts index 83b1f50c893..7df3be34e0b 100644 --- a/src/vs/workbench/api/node/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/node/mainThreadTerminalService.ts @@ -24,6 +24,7 @@ export class MainThreadTerminalService extends MainThreadTerminalServiceShape { this._toDispose = []; this._toDispose.push(terminalService.onInstanceDisposed((terminalInstance) => this._onTerminalDisposed(terminalInstance))); this._toDispose.push(terminalService.onInstanceProcessIdReady((terminalInstance) => this._onTerminalProcessIdReady(terminalInstance))); + this._toDispose.push(terminalService.onInstanceData(event => this._onTerminalData(event.instance, event.data))); } public dispose(): void { @@ -55,6 +56,13 @@ export class MainThreadTerminalService extends MainThreadTerminalServiceShape { } } + public $registerOnData(terminalId: number): void { + let terminalInstance = this.terminalService.getInstanceFromId(terminalId); + if (terminalInstance) { + terminalInstance.enableApiOnData(); + } + } + public $dispose(terminalId: number): void { let terminalInstance = this.terminalService.getInstanceFromId(terminalId); if (terminalInstance) { @@ -76,4 +84,8 @@ export class MainThreadTerminalService extends MainThreadTerminalServiceShape { private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void { this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId); } + + private _onTerminalData(terminalInstance: ITerminalInstance, data: string): void { + this._proxy.$acceptTerminalData(terminalInstance.id, data); + } } diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 8cfdc0658b9..09851c1e6f6 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -126,6 +126,7 @@ export interface ITerminalService { onActiveInstanceChanged: Event; onInstanceDisposed: Event; onInstanceProcessIdReady: Event; + onInstanceData: Event<{ instance: ITerminalInstance, data: string }>; onInstancesChanged: Event; onInstanceTitleChanged: Event; terminalInstances: ITerminalInstance[]; @@ -314,4 +315,6 @@ export interface ITerminalInstance { * @param shell The new launch configuration. */ reuseTerminal(shell?: IShellLaunchConfig): void; + + enableApiOnData(): void; } diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 187cd227926..7b37c034ec9 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -21,6 +21,7 @@ export abstract class TerminalService implements ITerminalService { protected _onInstancesChanged: Emitter; protected _onInstanceDisposed: Emitter; protected _onInstanceProcessIdReady: Emitter; + protected _onInstanceData: Emitter<{ instance: ITerminalInstance, data: string }>; protected _onInstanceTitleChanged: Emitter; protected _terminalInstances: ITerminalInstance[]; @@ -31,6 +32,7 @@ export abstract class TerminalService implements ITerminalService { public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } public get onInstanceDisposed(): Event { return this._onInstanceDisposed.event; } public get onInstanceProcessIdReady(): Event { return this._onInstanceProcessIdReady.event; } + public get onInstanceData(): Event<{ instance: ITerminalInstance, data: string }> { return this._onInstanceData.event; } public get onInstanceTitleChanged(): Event { return this._onInstanceTitleChanged.event; } public get onInstancesChanged(): Event { return this._onInstancesChanged.event; } public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } @@ -50,6 +52,7 @@ export abstract class TerminalService implements ITerminalService { this._onActiveInstanceChanged = new Emitter(); this._onInstanceDisposed = new Emitter(); this._onInstanceProcessIdReady = new Emitter(); + this._onInstanceData = new Emitter<{ instance: ITerminalInstance, data: string }>(); this._onInstanceTitleChanged = new Emitter(); this._onInstancesChanged = new Emitter(); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index e6a1134d917..5deecbcbba3 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -56,6 +56,7 @@ export class TerminalInstance implements ITerminalInstance { private _isVisible: boolean; private _isDisposed: boolean; private _onDisposed: Emitter; + private _onDataForApi: Emitter<{ instance: ITerminalInstance, data: string }>; private _onProcessIdReady: Emitter; private _onTitleChanged: Emitter; private _process: cp.ChildProcess; @@ -77,6 +78,7 @@ export class TerminalInstance implements ITerminalInstance { public get id(): number { return this._id; } public get processId(): number { return this._processId; } public get onDisposed(): Event { return this._onDisposed.event; } + public get onDataForApi(): Event<{ instance: ITerminalInstance, data: string }> { return this._onDataForApi.event; } public get onProcessIdReady(): Event { return this._onProcessIdReady.event; } public get onTitleChanged(): Event { return this._onTitleChanged.event; } public get title(): string { return this._title; } @@ -107,6 +109,7 @@ export class TerminalInstance implements ITerminalInstance { this._terminalHasTextContextKey = KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED.bindTo(this._contextKeyService); this._onDisposed = new Emitter(); + this._onDataForApi = new Emitter<{ instance: ITerminalInstance, data: string }>(); this._onProcessIdReady = new Emitter(); this._onTitleChanged = new Emitter(); @@ -737,6 +740,11 @@ export class TerminalInstance implements ITerminalInstance { } } + public enableApiOnData(): void { + // Only send data through IPC if the API explicitly requests it. + this.onData(data => this._onDataForApi.fire({ instance: this, data })); + } + public static setTerminalProcessFactory(factory: ITerminalProcessFactory): void { this._terminalProcessFactory = factory; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 7615b57a36e..eed6376060a 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -57,6 +57,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina shell); terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); terminalInstance.addDisposable(terminalInstance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); + terminalInstance.addDisposable(terminalInstance.onDataForApi(this._onInstanceData.fire, this._onInstanceData)); terminalInstance.addDisposable(terminalInstance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); this.terminalInstances.push(terminalInstance); if (this.terminalInstances.length === 1) {