Merge pull request #52143 from Microsoft/46192_terminal_renderer

Terminal renderer and activeTerminal APIs
This commit is contained in:
Daniel Imms
2018-06-19 05:29:00 +08:00
committed by GitHub
22 changed files with 860 additions and 252 deletions

View File

@@ -5,7 +5,7 @@
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
import { TPromise } from 'vs/base/common/winjs.base';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
@@ -16,22 +16,23 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
private _proxy: ExtHostTerminalServiceShape;
private _toDispose: IDisposable[] = [];
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
private _dataListeners: { [id: number]: IDisposable } = {};
constructor(
extHostContext: IExtHostContext,
@ITerminalService private terminalService: ITerminalService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
this._toDispose.push(terminalService.onInstanceCreated((terminalInstance) => {
this._toDispose.push(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(terminalInstance), 100);
setTimeout(() => this._onTerminalOpened(instance), EXT_HOST_CREATION_DELAY);
}));
this._toDispose.push(terminalService.onInstanceDisposed(terminalInstance => this._onTerminalDisposed(terminalInstance)));
this._toDispose.push(terminalService.onInstanceProcessIdReady(terminalInstance => this._onTerminalProcessIdReady(terminalInstance)));
this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request)));
this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : undefined)));
// Set initial ext host state
this.terminalService.terminalInstances.forEach(t => {
@@ -60,8 +61,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
return TPromise.as(this.terminalService.createTerminal(shellLaunchConfig).id);
}
public $createTerminalRenderer(name: string): TPromise<number> {
const instance = this.terminalService.createTerminalRenderer(name);
return TPromise.as(instance.id);
}
public $show(terminalId: number, preserveFocus: boolean): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this.terminalService.setActiveInstance(terminalInstance);
this.terminalService.showPanel(!preserveFocus);
@@ -75,31 +81,68 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public $dispose(terminalId: number): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.dispose();
}
}
public $terminalRendererWrite(terminalId: number, text: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.write(text);
}
}
public $terminalRendererSetName(terminalId: number, name: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setTitle(name, false);
}
}
public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setDimensions(dimensions);
}
}
public $terminalRendererRegisterOnInputListener(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.addDisposable(terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data)));
}
}
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.sendText(text, addNewLine);
}
}
public $registerOnDataListener(terminalId: number): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this._dataListeners[terminalId] = terminalInstance.onData(data => this._onTerminalData(terminalId, data));
terminalInstance.onDisposed(instance => delete this._dataListeners[terminalId]);
terminalInstance.addDisposable(terminalInstance.onData(data => {
this._onTerminalData(terminalId, data);
}));
}
}
private _onActiveTerminalChanged(terminalId: number | undefined): void {
this._proxy.$acceptActiveTerminalChanged(terminalId);
}
private _onTerminalData(terminalId: number, data: string): void {
this._proxy.$acceptTerminalProcessData(terminalId, data);
}
private _onTerminalRendererInput(terminalId: number, data: string): void {
this._proxy.$acceptTerminalRendererInput(terminalId, data);
}
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
this._proxy.$acceptTerminalClosed(terminalInstance.id);
}
@@ -112,6 +155,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId);
}
private _onInstanceDimensionsChanged(instance: ITerminalInstance): void {
// Only send the dimensions if the terminal is a renderer only as there is no API to access
// dimensions on a plain Terminal.
if (instance.shellLaunchConfig.isRendererOnly) {
this._proxy.$acceptTerminalRendererDimensions(instance.id, instance.cols, instance.rows);
}
}
private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void {
this._terminalProcesses[request.proxy.terminalId] = request.proxy;
const shellLaunchConfigDto: ShellLaunchConfigDto = {

View File

@@ -331,6 +331,9 @@ export function createApiFactory(
get visibleTextEditors() {
return extHostEditors.getVisibleTextEditors();
},
get activeTerminal() {
return proposedApiFunction(extension, extHostTerminalService.activeTerminal);
},
get terminals() {
return proposedApiFunction(extension, extHostTerminalService.terminals);
},
@@ -372,6 +375,9 @@ export function createApiFactory(
onDidOpenTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
return extHostTerminalService.onDidOpenTerminal(listener, thisArg, disposables);
}),
onDidChangeActiveTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables);
}),
get state() {
return extHostWindow.state;
},
@@ -427,6 +433,9 @@ export function createApiFactory(
}
return extHostTerminalService.createTerminal(<string>nameOrOptions, shellPath, shellArgs);
},
createTerminalRenderer(name: string): vscode.TerminalRenderer {
return extHostTerminalService.createTerminalRenderer(name);
},
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider);
},

View File

@@ -49,6 +49,7 @@ import { ISingleEditOperation } from 'vs/editor/common/model';
import { IPatternInfo, IRawSearchQuery, IRawFileMatch2, ISearchCompleteStats } from 'vs/platform/search/common/search';
import { LogLevel } from 'vs/platform/log/common/log';
import { TaskExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@@ -324,16 +325,24 @@ export interface MainThreadProgressShape extends IDisposable {
export interface MainThreadTerminalServiceShape extends IDisposable {
$createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string, env?: { [key: string]: string }, waitOnExit?: boolean): TPromise<number>;
$createTerminalRenderer(name: string): TPromise<number>;
$dispose(terminalId: number): void;
$hide(terminalId: number): void;
$sendText(terminalId: number, text: string, addNewLine: boolean): void;
$show(terminalId: number, preserveFocus: boolean): void;
$registerOnDataListener(terminalId: number): void;
// Process
$sendProcessTitle(terminalId: number, title: string): void;
$sendProcessData(terminalId: number, data: string): void;
$sendProcessPid(terminalId: number, pid: number): void;
$sendProcessExit(terminalId: number, exitCode: number): void;
// Renderer
$terminalRendererSetName(terminalId: number, name: string): void;
$terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void;
$terminalRendererWrite(terminalId: number, text: string): void;
$terminalRendererRegisterOnInputListener(terminalId: number): void;
}
export interface TransferQuickPickItems extends IQuickPickItem {
@@ -841,8 +850,11 @@ export interface ShellLaunchConfigDto {
export interface ExtHostTerminalServiceShape {
$acceptTerminalClosed(id: number): void;
$acceptTerminalOpened(id: number, name: string): void;
$acceptActiveTerminalChanged(id: number | null): void;
$acceptTerminalProcessId(id: number, processId: number): void;
$acceptTerminalProcessData(id: number, data: string): void;
$acceptTerminalRendererInput(id: number, data: string): void;
$acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void;
$createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, cols: number, rows: number): void;
$acceptProcessInput(id: number, data: string): void;
$acceptProcessResize(id: number, cols: number, rows: number): void;

View File

@@ -15,13 +15,64 @@ import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShap
import { IMessageFromTerminalProcess } from 'vs/workbench/parts/terminal/node/terminal';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ILogService } from 'vs/platform/log/common/log';
import { EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
export class ExtHostTerminal implements vscode.Terminal {
private _name: string;
private _id: number;
private _proxy: MainThreadTerminalServiceShape;
private _disposed: boolean;
private _queuedRequests: ApiRequest[];
const RENDERER_NO_PROCESS_ID = -1;
export class BaseExtHostTerminal {
public _id: number;
protected _idPromise: Promise<number>;
private _idPromiseComplete: (value: number) => any;
private _disposed: boolean = false;
private _queuedRequests: ApiRequest[] = [];
constructor(
protected _proxy: MainThreadTerminalServiceShape,
id?: number
) {
this._idPromise = new Promise<number>(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);
}
protected _runQueuedRequests(id: number): void {
this._id = id;
this._idPromiseComplete(id);
this._queuedRequests.forEach((r) => {
r.run(this._proxy, this._id);
});
this._queuedRequests.length = 0;
}
}
export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal {
private _pidPromise: Promise<number>;
private _pidPromiseComplete: (value: number) => any;
@@ -34,17 +85,17 @@ export class ExtHostTerminal implements vscode.Terminal {
constructor(
proxy: MainThreadTerminalServiceShape,
name: string = '',
id?: number
private _name: string,
id?: number,
pid?: number
) {
this._proxy = proxy;
this._name = name;
if (id) {
this._id = id;
}
this._queuedRequests = [];
super(proxy, id);
this._pidPromise = new Promise<number>(c => {
this._pidPromiseComplete = c;
if (pid === RENDERER_NO_PROCESS_ID) {
c(undefined);
} else {
this._pidPromiseComplete = c;
}
});
}
@@ -56,11 +107,7 @@ export class ExtHostTerminal implements vscode.Terminal {
waitOnExit?: boolean
): void {
this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit).then((id) => {
this._id = id;
this._queuedRequests.forEach((r) => {
r.run(this._proxy, this._id);
});
this._queuedRequests = [];
this._runQueuedRequests(id);
});
}
@@ -87,13 +134,6 @@ export class ExtHostTerminal implements vscode.Terminal {
this._queueApiRequest(this._proxy.$hide, []);
}
public dispose(): void {
if (!this._disposed) {
this._disposed = true;
this._queueApiRequest(this._proxy.$dispose, []);
}
}
public _setProcessId(processId: number): void {
// The event may fire 2 times when the panel is restored
if (this._pidPromiseComplete) {
@@ -105,34 +145,95 @@ export class ExtHostTerminal implements vscode.Terminal {
public _fireOnData(data: string): void {
this._onData.fire(data);
}
}
private _queueApiRequest(callback: (...args: any[]) => void, args: any[]) {
let request: ApiRequest = new ApiRequest(callback, args);
if (!this._id) {
this._queuedRequests.push(request);
return;
}
request.run(this._proxy, this._id);
export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vscode.TerminalRenderer {
public get name(): string { return this._name; }
public set name(newName: string) {
this._name = newName;
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererSetName, [this._name]);
}
private _checkDisposed() {
if (this._disposed) {
throw new Error('Terminal has already been disposed');
private readonly _onInput: Emitter<string> = new Emitter<string>();
public get onInput(): Event<string> {
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]);
// Tell the main side to start sending data if it's not already
// this._proxy.$terminalRendererRegisterOnDataListener(this._id);
return this._onInput && this._onInput.event;
}
private _dimensions: vscode.TerminalDimensions | undefined;
public get dimensions(): vscode.TerminalDimensions { return this._dimensions; }
public set dimensions(dimensions: vscode.TerminalDimensions) {
this._checkDisposed();
this._dimensions = dimensions;
this._queueApiRequest(this._proxy.$terminalRendererSetDimensions, [dimensions]);
}
private _maximumDimensions: vscode.TerminalDimensions;
public get maximumDimensions(): vscode.TerminalDimensions {
if (!this._maximumDimensions) {
return undefined;
}
return {
rows: this._maximumDimensions.rows,
cols: this._maximumDimensions.cols
};
}
private readonly _onDidChangeMaximumDimensions: Emitter<vscode.TerminalDimensions> = new Emitter<vscode.TerminalDimensions>();
public get onDidChangeMaximumDimensions(): Event<vscode.TerminalDimensions> {
return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event;
}
public get terminal(): Promise<ExtHostTerminal> {
return this._idPromise.then(id => this._fetchTerminal(id));
}
constructor(
proxy: MainThreadTerminalServiceShape,
private _name: string,
private _fetchTerminal: (id: number) => Promise<ExtHostTerminal>
) {
super(proxy);
this._proxy.$createTerminalRenderer(this._name).then(id => {
this._runQueuedRequests(id);
});
}
public write(data: string): void {
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererWrite, [data]);
}
public _fireOnInput(data: string): void {
this._onInput.fire(data);
}
public _setMaximumDimensions(cols: number, rows: number): void {
this._maximumDimensions = { cols, rows };
this._onDidChangeMaximumDimensions.fire(this.maximumDimensions);
}
}
export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
private _proxy: MainThreadTerminalServiceShape;
private _activeTerminal: ExtHostTerminal;
private _terminals: ExtHostTerminal[] = [];
private _terminalProcesses: { [id: number]: cp.ChildProcess } = {};
private _terminalRenderers: ExtHostTerminalRenderer[] = [];
public get activeTerminal(): ExtHostTerminal { return this._activeTerminal; }
public get terminals(): ExtHostTerminal[] { return this._terminals; }
private readonly _onDidCloseTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidCloseTerminal(): Event<vscode.Terminal> { return this._onDidCloseTerminal && this._onDidCloseTerminal.event; }
private readonly _onDidOpenTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidOpenTerminal(): Event<vscode.Terminal> { return this._onDidOpenTerminal && this._onDidOpenTerminal.event; }
private readonly _onDidChangeActiveTerminal: Emitter<vscode.Terminal | undefined> = new Emitter<vscode.Terminal | undefined>();
public get onDidChangeActiveTerminal(): Event<vscode.Terminal | undefined> { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; }
constructor(
mainContext: IMainContext,
@@ -143,53 +244,94 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
let terminal = new ExtHostTerminal(this._proxy, name);
const terminal = new ExtHostTerminal(this._proxy, name);
terminal.create(shellPath, shellArgs);
this._terminals.push(terminal);
return terminal;
}
public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal {
let terminal = new ExtHostTerminal(this._proxy, options.name);
const terminal = new ExtHostTerminal(this._proxy, options.name);
terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env /*, options.waitOnExit*/);
this._terminals.push(terminal);
return terminal;
}
public $acceptTerminalProcessData(id: number, data: string): void {
let index = this._getTerminalIndexById(id);
if (index === null) {
return;
public createTerminalRenderer(name: string): vscode.TerminalRenderer {
const renderer = new ExtHostTerminalRenderer(this._proxy, name, (id) => this._getTerminalByIdEventually(id));
this._terminalRenderers.push(renderer);
return renderer;
}
public $acceptActiveTerminalChanged(id: number | null): void {
const original = this._activeTerminal;
if (id === null) {
this._activeTerminal = undefined;
} else {
const terminal = this._getTerminalById(id);
if (terminal) {
this._activeTerminal = terminal;
}
}
if (original !== this._activeTerminal) {
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
}
}
public $acceptTerminalProcessData(id: number, data: string): void {
const terminal = this._getTerminalById(id);
if (terminal) {
terminal._fireOnData(data);
}
}
public $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void {
const renderer = this._getTerminalRendererById(id);
if (renderer) {
renderer._setMaximumDimensions(cols, rows);
}
}
public $acceptTerminalRendererInput(id: number, data: string): void {
const renderer = this._getTerminalRendererById(id);
if (renderer) {
renderer._fireOnInput(data);
}
const terminal = this._terminals[index];
terminal._fireOnData(data);
}
public $acceptTerminalClosed(id: number): void {
let index = this._getTerminalIndexById(id);
const index = this._getTerminalObjectIndexById(this.terminals, id);
if (index === null) {
return;
}
let terminal = this._terminals.splice(index, 1)[0];
const terminal = this._terminals.splice(index, 1)[0];
this._onDidCloseTerminal.fire(terminal);
}
public $acceptTerminalOpened(id: number, name: string): void {
let index = this._getTerminalIndexById(id);
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]);
return;
}
let terminal = new ExtHostTerminal(this._proxy, name, id);
const renderer = this._getTerminalRendererById(id);
const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined);
this._terminals.push(terminal);
this._onDidOpenTerminal.fire(terminal);
}
public $acceptTerminalProcessId(id: number, processId: number): void {
let terminal = this._getTerminalById(id);
if (terminal) {
terminal._setProcessId(processId);
} else {
// Retry one more time in case the terminal has not yet been initialized.
setTimeout(() => {
terminal = this._getTerminalById(id);
terminal._setProcessId(processId);
}, EXT_HOST_CREATION_DELAY);
}
}
@@ -232,7 +374,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// Continue env initialization, merging in the env from the launch
// config and adding keys that are needed to create the process
const env = terminalEnvironment.createTerminalEnv(parentEnv, shellLaunchConfig, initialCwd, locale, cols, rows);
let cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
const cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
const options = { env, cwd, execArgv: [] };
// Fork the process and listen for messages
@@ -286,16 +428,43 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
this._proxy.$sendProcessExit(id, exitCode);
}
private _getTerminalById(id: number): ExtHostTerminal {
let index = this._getTerminalIndexById(id);
return index !== null ? this._terminals[index] : null;
private _getTerminalByIdEventually(id: number, retries: number = 5): Promise<ExtHostTerminal> {
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
setTimeout(() => {
c(this._getTerminalByIdEventually(id, retries - 1));
}, 200);
}
});
}
private _getTerminalIndexById(id: number): number {
private _getTerminalById(id: number): ExtHostTerminal {
return this._getTerminalObjectById(this._terminals, id);
}
private _getTerminalRendererById(id: number): ExtHostTerminalRenderer {
return this._getTerminalObjectById(this._terminalRenderers, id);
}
private _getTerminalObjectById<T extends ExtHostTerminal | ExtHostTerminalRenderer>(array: T[], id: number): T {
const index = this._getTerminalObjectIndexById(array, id);
return index !== null ? array[index] : null;
}
private _getTerminalObjectIndexById<T extends ExtHostTerminal | ExtHostTerminalRenderer>(array: T[], id: number): number {
let index: number = null;
this._terminals.some((terminal, i) => {
// TODO: This shouldn't be cas
let thisId = (<any>terminal)._id;
array.some((item, i) => {
const thisId = item._id;
if (thisId === id) {
index = i;
return true;