diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 707508b723c1203112c75440e5abc5ca689725d8..a08d5d049d53eb54834abd5384666f541cbd39af 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, Terminal } from 'vscode'; +import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, Terminal, TerminalDimensionsChangeEvent } from 'vscode'; import { join } from 'path'; import { closeAllEditors, pathEquals, createRandomFile } from '../utils'; @@ -695,6 +695,45 @@ suite('window namespace tests', () => { const terminal = window.createTerminal(); terminal.show(); }); + + test('onDidChangeTerminalDimensions should fire when new terminals are created', (done) => { + const reg1 = window.onDidChangeTerminalDimensions((event: TerminalDimensionsChangeEvent) => { + assert.equal(event.terminal, terminal1); + assert.equal(typeof event.dimensions.columns, 'number'); + assert.equal(typeof event.dimensions.rows, 'number'); + assert.ok(event.dimensions.columns > 0); + assert.ok(event.dimensions.rows > 0); + reg1.dispose(); + let terminal2: Terminal; + const reg2 = window.onDidOpenTerminal((newTerminal) => { + // THis is guarentees to fire before dimensions change event + if (newTerminal !== terminal1) { + terminal2 = newTerminal; + reg2.dispose(); + } + }); + let firstCalled = false; + let secondCalled = false; + const reg3 = window.onDidChangeTerminalDimensions((event: TerminalDimensionsChangeEvent) => { + if (event.terminal === terminal1) { + // The original terminal should fire dimension change after a split + firstCalled = true; + } else if (event.terminal !== terminal1) { + // The new split terminal should fire dimension change + secondCalled = true; + } + if (firstCalled && secondCalled) { + terminal1.dispose(); + terminal2.dispose(); + reg3.dispose(); + done(); + } + }); + commands.executeCommand('workbench.action.terminal.split'); + }); + const terminal1 = window.createTerminal({ name: 'test' }); + terminal1.show(); + }); }); }); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e8d0f99c5630a51384271d9bee507f133d2c377e..6192f6b1bbc4153058b91af7c7ee309173dec99b 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -888,13 +888,41 @@ declare module 'vscode' { //#region Terminal + /** + * An [event](#Event) which fires when a [Terminal](#Terminal)'s dimensions change. + */ + export interface TerminalDimensionsChangeEvent { + /** + * The [terminal](#Terminal) for which the dimensions have changed. + */ + readonly terminal: Terminal; + /** + * The new value for the [terminal's dimensions](#Terminal.dimensions). + */ + readonly dimensions: TerminalDimensions; + } + + namespace window { + /** + * An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change. + */ + export const onDidChangeTerminalDimensions: Event; + } + export interface Terminal { + /** + * The current dimensions of the terminal. This will be `undefined` immediately after the + * terminal is created as the dimensions are not known until shortly after the terminal is + * created. + */ + readonly dimensions: TerminalDimensions | undefined; + /** * Fires when the terminal's pty slave pseudo-device is written to. In other words, this * provides access to the raw data stream from the process running within the terminal, * including VT sequences. */ - onDidWriteData: Event; + readonly onDidWriteData: Event; } /** diff --git a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts index 4a846cf295290d04057201d200cbf6346265f974..bdc0b2125632d3977b82169a472a168fc2c3fbe8 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts @@ -28,7 +28,10 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // 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(instance), EXT_HOST_CREATION_DELAY); + setTimeout(() => { + this._onTerminalOpened(instance); + this._onInstanceDimensionsChanged(instance); + }, EXT_HOST_CREATION_DELAY); })); this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance))); this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance))); @@ -196,11 +199,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } 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); - } + this._proxy.$acceptTerminalDimensions(instance.id, instance.cols, instance.rows); } private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 59ecf348f916485745a5db513e492bc79134416c..7373f1f92d4231924cb7876ddae8ed9bd7b52010 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -424,6 +424,9 @@ export function createApiFactory( onDidChangeActiveTerminal(listener, thisArg?, disposables?) { return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables); }, + onDidChangeTerminalDimensions(listener, thisArg?, disposables?) { + return extHostTerminalService.onDidChangeTerminalDimensions(listener, thisArg, disposables); + }, get state() { return extHostWindow.state; }, @@ -479,9 +482,9 @@ export function createApiFactory( } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, - createTerminalRenderer: proposedApiFunction(extension, (name: string) => { + createTerminalRenderer(name: string): vscode.TerminalRenderer { return extHostTerminalService.createTerminalRenderer(name); - }), + }, registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 85e68f5080d6960b2976f379bc65e31520351fc4..93bf62e21a9327cc00d9f9c455022c0e70fb5bb5 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -935,7 +935,7 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalProcessData(id: number, data: string): void; $acceptTerminalRendererInput(id: number, data: string): void; $acceptTerminalTitleChange(id: number, name: string): void; - $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void; + $acceptTerminalDimensions(id: number, cols: number, rows: number): void; $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: URI, 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 1136debe9a1dac7a3e7a42c16df4eb20a5145c3d..005f6b766a82a088f1318b9116c23ddff7e17875 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -78,6 +78,8 @@ export class BaseExtHostTerminal { export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal { private _pidPromise: Promise; private _pidPromiseComplete: (value: number) => any; + private _cols: number | undefined; + private _rows: number | undefined; private readonly _onData = new Emitter(); public get onDidWriteData(): Event { @@ -126,6 +128,26 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi this._name = name; } + public get dimensions(): vscode.TerminalDimensions | undefined { + if (this._cols === undefined && this._rows === undefined) { + return undefined; + } + return { + columns: this._cols, + rows: this._rows + }; + } + + public setDimensions(cols: number, rows: number): boolean { + if (cols === this._cols && rows === this._rows) { + // Nothing changed + return false; + } + this._cols = cols; + this._rows = rows; + return true; + } + public get processId(): Promise { return this._pidPromise; } @@ -258,6 +280,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public get onDidOpenTerminal(): Event { return this._onDidOpenTerminal && this._onDidOpenTerminal.event; } private readonly _onDidChangeActiveTerminal: Emitter = new Emitter(); public get onDidChangeActiveTerminal(): Event { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; } + private readonly _onDidChangeTerminalDimensions: Emitter = new Emitter(); + public get onDidChangeTerminalDimensions(): Event { return this._onDidChangeTerminalDimensions && this._onDidChangeTerminalDimensions.event; } constructor( mainContext: IMainContext, @@ -319,7 +343,17 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { }); } - public $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void { + public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise { + const terminal = this._getTerminalById(id); + if (terminal) { + if (terminal.setDimensions(cols, rows)) { + this._onDidChangeTerminalDimensions.fire({ + terminal: terminal, + dimensions: terminal.dimensions + }); + } + } + // When a terminal's dimensions change, a renderer's _maximum_ dimensions change const renderer = this._getTerminalRendererById(id); if (renderer) { renderer._setMaximumDimensions(cols, rows); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index b19e38fcae58ac7c725f937e86d56a8b7ab06ef2..15cc76a4fc3fd8d32d417dffbb516823fcbc9fb9 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -190,8 +190,18 @@ 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; } + public get cols(): number { + if (this._dimensionsOverride && this._dimensionsOverride.cols) { + return Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols); + } + return this._cols; + } + public get rows(): number { + if (this._dimensionsOverride && this._dimensionsOverride.rows) { + return Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows); + } + 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? @@ -1173,6 +1183,7 @@ export class TerminalInstance implements ITerminalInstance { return; } + const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height); if (!terminalWidth) { return; @@ -1187,12 +1198,8 @@ export class TerminalInstance implements ITerminalInstance { @debounce(50) 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), cols); - rows = Math.min(Math.max(this._dimensionsOverride.rows, 2), rows); - } + let cols = this.cols; + let rows = this.rows; if (this._xterm) { // Only apply these settings when the terminal is visible so that