Clean this up a bit and create extension host renderes on demand

This commit is contained in:
Gabriel DeBacker
2019-02-04 17:52:04 -08:00
parent 24b0814326
commit 69db919cf5
5 changed files with 85 additions and 74 deletions

View File

@@ -369,6 +369,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
// Renderer
$terminalRendererSetName(terminalId: number, name: string): void;
$terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void;
$terminalGetDimensions(terminalId: number): Promise<ITerminalDimensions>;
$terminalRendererWrite(terminalId: number, text: string): void;
$terminalRendererRegisterOnInputListener(terminalId: number): void;
}

View File

@@ -28,7 +28,7 @@ import {
import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostTerminalService, ExtHostTerminalRenderer } from 'vs/workbench/api/node/extHostTerminalService';
import { ExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/node/extHostTerminalService';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -335,10 +335,10 @@ interface HandlerData {
class ExtensionCallbackExecutionData implements IDisposable {
private _cancellationSource?: CancellationTokenSource;
private _onDidOpenRendererTerminal?: IDisposable;
private _terminalId?: number;
private readonly _onTaskExecutionComplete: Emitter<ExtensionCallbackExecutionData> = new Emitter<ExtensionCallbackExecutionData>();
private readonly _disposables: IDisposable[] = [];
private terminal?: vscode.Terminal;
private terminalId?: number;
constructor(
private readonly callbackData: vscode.ExtensionCallbackExecution,
@@ -353,13 +353,25 @@ class ExtensionCallbackExecutionData implements IDisposable {
return this._onTaskExecutionComplete.event;
}
private onDidCloseTerminalRenderer(terminalRenderer: vscode.TerminalRenderer): void {
if (terminalRenderer instanceof ExtHostTerminalRenderer && terminalRenderer._id === this._terminalId) {
private onDidCloseTerminal(terminal: vscode.Terminal): void {
if (this.terminal === terminal) {
this._cancellationSource.cancel();
}
}
private onDidOpenRenderTerminal(terminalRenderer: vscode.TerminalRenderer): Thenable<void> {
private onDidOpenTerminal(terminal: vscode.Terminal): void {
if (!(terminal instanceof ExtHostTerminal)) {
throw new Error('How could this not be a extension host terminal?');
}
if (this.terminalId && terminal._id === this.terminalId) {
this.startCallback(this.terminalId);
}
}
public async startCallback(terminalId: number): Promise<void> {
this.terminalId = terminalId;
// If we have already started the extension task callback, then
// do not start it again.
// It is completely valid for multiple terminals to be opened
@@ -368,57 +380,32 @@ class ExtensionCallbackExecutionData implements IDisposable {
return undefined;
}
if (terminalRenderer instanceof ExtHostTerminalRenderer && terminalRenderer._id === this._terminalId) {
// Stop listening (if we are) for more terminals
// to be created.
if (this._onDidOpenRendererTerminal) {
this._onDidOpenRendererTerminal.dispose();
this._onDidOpenRendererTerminal = undefined;
}
const callbackTerminals: vscode.Terminal[] = this.terminalService.terminals.filter((terminal) => terminal._id === terminalId);
if (!(terminalRenderer instanceof ExtHostTerminalRenderer)) {
throw new Error('Expected a terminal renderer');
}
this._cancellationSource = new CancellationTokenSource();
this._disposables.push(this._cancellationSource);
this._disposables.push(this.terminalService.onDidCloseTerminalRenderer(this.onDidCloseTerminalRenderer.bind(this)));
// Regardless of how the task completes, we are done with this extension callback task execution.
return this.callbackData.callback(terminalRenderer, this._cancellationSource.token).then(
(success) => {
this._onTaskExecutionComplete.fire(this);
}, (rejected) => {
this._onTaskExecutionComplete.fire(this);
});
if (!callbackTerminals || callbackTerminals.length === 0) {
this._disposables.push(this.terminalService.onDidOpenTerminal(this.onDidOpenTerminal.bind(this)));
return;
}
return undefined;
}
public startCallback(terminalId: number): void {
this._terminalId = terminalId;
// In order to start the task, we need to wait for the extension host
// to know about the new terminal.
// The order in which the events make it to the extension host (currently)
// is "task created" followed by "terminal opened".
// So, we need to wait for that event.
// However, this loop below ensures that if the order of those events
// ever changes, this code continues to function.
// Check to see if the extension host already knows about this terminal.
for (let terminal of this.terminalService.terminalRenderers) {
if (terminal._id === terminalId) {
this.onDidOpenRenderTerminal(terminal);
return;
}
if (callbackTerminals.length !== 1) {
throw new Error(`Expected to only have one terminal at this point`);
}
// If we get here, then the terminal is unknown to the extension host. Let's wait
// for it to be created and the start our extension callback.
this._disposables.push(this.terminalService.onDidOpenTerminalRenderer(this.onDidOpenRenderTerminal.bind(this)));
this.terminal = callbackTerminals[0];
const terminalRenderer: vscode.TerminalRenderer = await this.terminalService.createTerminalRendererForTerminal(this.terminal);
this._cancellationSource = new CancellationTokenSource();
this._disposables.push(this._cancellationSource);
this._disposables.push(this.terminalService.onDidCloseTerminal(this.onDidCloseTerminal.bind(this)));
// Regardless of how the task completes, we are done with this extension callback task execution.
this.callbackData.callback(terminalRenderer, this._cancellationSource.token).then(
(success) => {
this._onTaskExecutionComplete.fire(this);
}, (rejected) => {
this._onTaskExecutionComplete.fire(this);
});
}
}

View File

@@ -269,12 +269,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
private readonly _onDidCloseTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidCloseTerminal(): Event<vscode.Terminal> { return this._onDidCloseTerminal && this._onDidCloseTerminal.event; }
private readonly _onDidCloseTerminalRenderer: Emitter<vscode.TerminalRenderer> = new Emitter<vscode.TerminalRenderer>();
public get onDidCloseTerminalRenderer(): Event<vscode.TerminalRenderer> { return this._onDidCloseTerminalRenderer && this._onDidCloseTerminalRenderer.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 _onDidOpenTerminalRenderer: Emitter<vscode.TerminalRenderer> = new Emitter<vscode.TerminalRenderer>();
public get onDidOpenTerminalRenderer(): Event<vscode.TerminalRenderer> { return this._onDidOpenTerminalRenderer && this._onDidOpenTerminalRenderer.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; }
@@ -312,6 +308,24 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
return renderer;
}
public async createTerminalRendererForTerminal(terminal: vscode.Terminal): Promise<vscode.TerminalRenderer> {
if (!(terminal instanceof ExtHostTerminal)) {
throw new Error('Only expected instance extension host terminal type');
}
// Check to see if the extension host already knows about this terminal.
for (const terminalRenderer of this.terminalRenderers) {
if (terminalRenderer._id === terminal._id) {
return terminalRenderer;
}
}
const dimensions = await this._proxy.$terminalGetDimensions(terminal._id);
const renderer = new ExtHostTerminalRenderer(this._proxy, terminal.name, terminal, terminal._id, dimensions.cols, dimensions.rows);
this._terminalRenderers.push(renderer);
return renderer;
}
public $acceptActiveTerminalChanged(id: number | null): void {
const original = this._activeTerminal;
if (id === null) {
@@ -365,24 +379,15 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
const terminal = this._terminals.splice(index, 1)[0];
this._onDidCloseTerminal.fire(terminal);
}
const renderer = this._getTerminalRendererById(id);
if (renderer) {
this._onDidCloseTerminalRenderer.fire(renderer);
}
}
public $acceptTerminalOpened(id: number, name: string, isRendererOnly: boolean, cols: number, rows: number): void {
const index = this._getTerminalObjectIndexById(this._terminals, id);
let index = this._getTerminalObjectIndexById(this._terminals, id);
if (index !== null) {
this._onDidOpenTerminal.fire(this.terminals[index]);
}
let renderer = this._getTerminalRendererById(id);
if (renderer) {
// The terminal has already been created (via createTerminal*), only fire the event
this._onDidOpenTerminalRenderer.fire(renderer);
}
// If this is a terminal created by one of the public createTerminal* APIs
// then @acceptTerminalOpened was called from the main thread task
@@ -398,14 +403,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// objects to represent them.
if (!index) {
const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined);
this._terminals.push(terminal);
index = this._terminals.push(terminal);
this._onDidOpenTerminal.fire(terminal);
if (!renderer && isRendererOnly) {
renderer = new ExtHostTerminalRenderer(this._proxy, name, terminal, id, cols, rows);
this.terminalRenderers.push(renderer);
this._onDidOpenTerminalRenderer.fire(renderer);
}
}
}