From 166f41a2c065903a9b50995afd29bb11bfc97348 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 16 Jun 2018 18:12:12 +0200 Subject: [PATCH] Add support for dimensions APIs --- src/vs/vscode.proposed.d.ts | 37 +++++++++++++++-- .../mainThreadTerminalService.ts | 29 ++++++++++--- src/vs/workbench/api/node/extHost.protocol.ts | 3 ++ .../api/node/extHostTerminalService.ts | 41 ++++++++++++++++++- src/vs/workbench/api/shared/terminal.ts | 4 ++ .../parts/terminal/common/terminal.ts | 22 +++++++++- .../parts/terminal/common/terminalService.ts | 3 ++ .../electron-browser/terminalInstance.ts | 36 ++++++++++++++-- 8 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 src/vs/workbench/api/shared/terminal.ts diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ca6ae5b2461..a1aa64a12ee 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -344,6 +344,21 @@ declare module 'vscode' { onData: Event; } + /** + * Represents dimensions of a terminal. + */ + export interface TerminalDimensions { + /** + * The number of columns in the terminal. + */ + cols: number; + + /** + * The number of rows in the terminal. + */ + rows: number; + } + /** * Represents a terminal without a process where all interaction and output in the terminal is * controlled by an extension. This is similar to an output window but has the same VT sequence @@ -355,8 +370,20 @@ declare module 'vscode' { */ name: string; - // Setting to undefined will reset to use the maximum available - // dimensions: TerminalDimensions; + /** + * The dimensions of the terminal, the rows and columns of the terminal can only be set to + * a value smaller than the maximum value, if this is undefined the terminal will auto fit + * to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions). + */ + dimensions: TerminalDimensions; + + /** + * The maximum dimensions of the terminal, this will be undefined immediately after a + * terminal renderer is created and also until the terminal becomes visible in the UI. + * Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions) + * to get notified when this value changes. + */ + readonly maximumDimensions: TerminalDimensions; /** * Write text to the terminal. Unlike [Terminal.sendText](#Terminal.sendText) which sends @@ -385,7 +412,11 @@ declare module 'vscode' { onData: Event; // in // Fires when the panel area is resized, this DOES NOT fire when `dimensions` is set - // onDidChangeDimensions: Event; + /** + * An event which fires when the [maximum dimensions](#TerminalRenderer.maimumDimensions) of + * the terminal renderer change. + */ + onDidChangeMaximumDimensions: Event; } export namespace window { diff --git a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts index e981cde9655..adbbc21d52e 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts @@ -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 } 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'; @@ -24,14 +24,15 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape @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), 100); })); - 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))); // Set initial ext host state @@ -62,7 +63,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $createTerminalRenderer(name: string): TPromise { - return TPromise.as(this.terminalService.createTerminalRenderer(name).id); + const instance = this.terminalService.createTerminalRenderer(name); + return TPromise.as(instance.id); } public $show(terminalId: number, preserveFocus: boolean): void { @@ -100,6 +102,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } + public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void { + const terminalInstance = this.terminalService.getInstanceFromId(terminalId); + if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { + terminalInstance.setDimensions(dimensions); + } + } + public $terminalRendererRegisterOnDataListener(terminalId: number): void { const terminalInstance = this.terminalService.getInstanceFromId(terminalId); if (terminalInstance) { @@ -143,6 +152,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 = { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 80d0da53777..46f3887d424 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -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; @@ -339,6 +340,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable { // Renderer $terminalRendererSetName(terminalId: number, name: string): void; + $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void; $terminalRendererWrite(terminalId: number, text: string): void; $terminalRendererRegisterOnDataListener(terminalId: number): void; } @@ -843,6 +845,7 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalProcessId(id: number, processId: number): void; $acceptTerminalProcessData(id: number, data: string): void; $acceptTerminalRendererData(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; diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 0f5cb1571e3..e5cecc2c239 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -150,12 +150,37 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco return this._onData && this._onData.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]); + // TODO: Send overridden dimensions over to the terminal instance + } + + 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 = new Emitter(); + public get onDidChangeMaximumDimensions(): Event { + return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event; + } + constructor( proxy: MainThreadTerminalServiceShape, private _name: string ) { super(proxy); - this._proxy.$createTerminalRenderer(this._name).then((id) => { + this._proxy.$createTerminalRenderer(this._name).then(id => { this._runQueuedRequests(id); }); } @@ -168,6 +193,11 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco public _fireOnData(data: string): void { this._onData.fire(data); } + + public _setMaximumDimensions(cols: number, rows: number): void { + this._maximumDimensions = { cols, rows }; + this._onDidChangeMaximumDimensions.fire(this.maximumDimensions); + } } export class ExtHostTerminalService implements ExtHostTerminalServiceShape { @@ -221,6 +251,15 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { terminal._fireOnData(data); } + public $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void { + const index = this._getTerminalRendererIndexById(id); + if (index === null) { + return; + } + const renderer = this._terminalRenderers[index]; + renderer._setMaximumDimensions(cols, rows); + } + public $acceptTerminalRendererData(id: number, data: string): void { const index = this._getTerminalRendererIndexById(id); if (index === null) { diff --git a/src/vs/workbench/api/shared/terminal.ts b/src/vs/workbench/api/shared/terminal.ts new file mode 100644 index 00000000000..a4a092d8349 --- /dev/null +++ b/src/vs/workbench/api/shared/terminal.ts @@ -0,0 +1,4 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 3cf571ff3cb..059b62be07a 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -176,6 +176,7 @@ export interface ITerminalService { onInstanceCreated: Event; onInstanceDisposed: Event; onInstanceProcessIdReady: Event; + onInstanceDimensionsChanged: Event; onInstanceRequestExtHostProcess: Event; onInstancesChanged: Event; onInstanceTitleChanged: Event; @@ -250,12 +251,27 @@ export interface ITerminalTab { split(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; } +export interface ITerminalDimensions { + /** + * The columns of the terminal. + */ + readonly cols: number; + + /** + * The rows of the terminal. + */ + readonly rows: number; +} + export interface ITerminalInstance { /** * The ID of the terminal instance, this is an arbitrary number only used to identify the * terminal instance. */ - id: number; + readonly id: number; + + readonly cols: number; + readonly rows: number; /** * The process ID of the shell process, this is undefined when there is no process associated @@ -279,6 +295,8 @@ export interface ITerminalInstance { onRequestExtHostProcess: Event; + onDimensionsChanged: Event; + processReady: TPromise; /** @@ -511,6 +529,8 @@ export interface ITerminalInstance { */ setTitle(title: string, eventFromProcess: boolean): void; + setDimensions(dimensions: ITerminalDimensions): void; + addDisposable(disposable: IDisposable): void; } diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index fc4db6b0820..9bd06bb85e0 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -41,6 +41,8 @@ export abstract class TerminalService implements ITerminalService { public get onInstanceProcessIdReady(): Event { return this._onInstanceProcessIdReady.event; } protected readonly _onInstanceRequestExtHostProcess: Emitter = new Emitter(); public get onInstanceRequestExtHostProcess(): Event { return this._onInstanceRequestExtHostProcess.event; } + protected readonly _onInstanceDimensionsChanged: Emitter = new Emitter(); + public get onInstanceDimensionsChanged(): Event { return this._onInstanceDimensionsChanged.event; } protected readonly _onInstancesChanged: Emitter = new Emitter(); public get onInstancesChanged(): Event { return this._onInstancesChanged.event; } protected readonly _onInstanceTitleChanged: Emitter = new Emitter(); @@ -288,6 +290,7 @@ export abstract class TerminalService implements ITerminalService { instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed)); instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged)); instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady)); + instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance))); } private _getTabForInstance(instance: ITerminalInstance): ITerminalTab { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index d1cde9fa3b1..cad92a84db4 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -14,7 +14,7 @@ import { Terminal as XTermTerminal } from 'vscode-xterm'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalProcessManager, ProcessState, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalProcessManager, ProcessState, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; @@ -63,6 +63,7 @@ export class TerminalInstance implements ITerminalInstance { private _terminalHasTextContextKey: IContextKey; private _cols: number; private _rows: number; + private _dimensionsOverride: ITerminalDimensions; private _windowsShellHelper: WindowsShellHelper; private _onLineDataListeners: ((lineData: string) => void)[]; private _xtermReadyPromise: TPromise; @@ -76,6 +77,8 @@ export class TerminalInstance implements ITerminalInstance { public disableLayout: boolean; public get id(): number { return this._id; } + public get cols(): number { return this._cols; } + public get rows(): number { return this._rows; } // TODO: Ideally processId would be merged into processReady public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; } // TODO: How does this work with detached processes? @@ -97,6 +100,8 @@ export class TerminalInstance implements ITerminalInstance { public get onTitleChanged(): Event { return this._onTitleChanged.event; } private readonly _onRequestExtHostProcess: Emitter = new Emitter(); public get onRequestExtHostProcess(): Event { return this._onRequestExtHostProcess.event; } + private readonly _onDimensionsChanged: Emitter = new Emitter(); + public get onDimensionsChanged(): Event { return this._onDimensionsChanged.event; } public constructor( private readonly _terminalFocusContextKey: IContextKey, @@ -930,6 +935,21 @@ export class TerminalInstance implements ITerminalInstance { return; } + if (this._xterm) { + this._xterm.element.style.width = terminalWidth + 'px'; + } + + this._resize(); + } + + private _resize(): void { + let cols = this._cols; + let rows = this._rows; + if (this._dimensionsOverride && this._dimensionsOverride.cols && this._dimensionsOverride.rows) { + cols = Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols); + rows = Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows); + } + if (this._xterm) { const font = this._configHelper.getFont(this._xterm); @@ -946,8 +966,11 @@ export class TerminalInstance implements ITerminalInstance { this._safeSetOption('drawBoldTextInBrightColors', config.drawBoldTextInBrightColors); } - this._xterm.resize(this._cols, this._rows); - this._xterm.element.style.width = terminalWidth + 'px'; + if (cols !== this._xterm.getOption('cols') || rows !== this._xterm.getOption('rows')) { + this._onDimensionsChanged.fire(); + } + + this._xterm.resize(cols, rows); if (this._isVisible) { // Force the renderer to unpause by simulating an IntersectionObserver event. This // is to fix an issue where dragging the window to the top of the screen to maximize @@ -960,7 +983,7 @@ export class TerminalInstance implements ITerminalInstance { } if (this._processManager) { - this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(this._cols, this._rows)); + this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows)); } } @@ -989,6 +1012,11 @@ export class TerminalInstance implements ITerminalInstance { } } + public setDimensions(dimensions: ITerminalDimensions): void { + this._dimensionsOverride = dimensions; + this._resize(); + } + private _getXtermTheme(theme?: ITheme): any { if (!theme) { theme = this._themeService.getTheme(); -- GitLab