diff --git a/package.json b/package.json index ac2965c6998cef4d5e5394a356af4a8db77af387..a4ee523d485a0752dcd31d3b4e51a9d825f7f147 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "vscode-ripgrep": "^1.2.5", "vscode-sqlite3": "4.0.7", "vscode-textmate": "^4.0.1", - "vscode-xterm": "3.13.0-beta3", + "vscode-xterm": "3.14.0-beta1", "yauzl": "^2.9.1", "yazl": "^2.4.3" }, @@ -152,4 +152,4 @@ "windows-mutex": "0.2.1", "windows-process-tree": "0.2.3" } -} \ No newline at end of file +} diff --git a/src/typings/vscode-xterm.d.ts b/src/typings/vscode-xterm.d.ts index 569af6027a64c1581e6f40bdfbce0bb659628700..9ca7342a34025f055e2ad775116eb7113f8b5f9b 100644 --- a/src/typings/vscode-xterm.d.ts +++ b/src/typings/vscode-xterm.d.ts @@ -101,17 +101,6 @@ declare module 'vscode-xterm' { */ experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; - /** - * (EXPERIMENTAL) Defines which implementation to use for buffer lines. - * - * - 'JsArray': The default/stable implementation. - * - 'TypedArray': The new experimental implementation based on TypedArrays that is expected to - * significantly boost performance and memory consumption. Use at your own risk. - * - * @deprecated This option will be removed in the future. - */ - experimentalBufferLineImpl?: 'JsArray' | 'TypedArray'; - /** * The font size used to render text. */ @@ -199,6 +188,18 @@ declare module 'vscode-xterm' { * The color theme of the terminal. */ theme?: ITheme; + + /** + * Whether "Windows mode" is enabled. Because Windows backends winpty and + * conpty operate by doing line wrapping on their side, xterm.js does not + * have access to wrapped lines. When Windows mode is enabled the following + * changes will be in effect: + * + * - Reflow is disabled. + * - Lines are assumed to be wrapped if the last character of the line is + * not whitespace. + */ + windowsMode?: boolean; } /** @@ -306,6 +307,14 @@ declare module 'vscode-xterm' { dispose(): void; } + /** + * An event that can be listened to. + * @returns an `IDisposable` to stop listening. + */ + export interface IEvent { + (listener: (e: T) => any): IDisposable; + } + export interface IMarker extends IDisposable { readonly id: number; readonly isDisposed: boolean; @@ -325,28 +334,32 @@ declare module 'vscode-xterm' { /** * The element containing the terminal. */ - element: HTMLElement; + readonly element: HTMLElement; /** * The textarea that accepts input for the terminal. */ - textarea: HTMLTextAreaElement; + readonly textarea: HTMLTextAreaElement; /** - * The number of rows in the terminal's viewport. + * The number of rows in the terminal's viewport. Use + * `ITerminalOptions.rows` to set this in the constructor and + * `Terminal.resize` for when the terminal exists. */ - rows: number; + readonly rows: number; /** - * The number of columns in the terminal's viewport. + * The number of columns in the terminal's viewport. Use + * `ITerminalOptions.cols` to set this in the constructor and + * `Terminal.resize` for when the terminal exists. */ - cols: number; + readonly cols: number; /** * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt * buffer is active this will always return []. */ - markers: IMarker[]; + readonly markers: ReadonlyArray; /** * Natural language strings that can be localized. @@ -360,6 +373,70 @@ declare module 'vscode-xterm' { */ constructor(options?: ITerminalOptions); + /** + * Adds an event listener for the cursor moves. + * @returns an `IDisposable` to stop listening. + */ + onCursorMove: IEvent; + + /** + * Adds an event listener for when a data event fires. This happens for + * example when the user types or pastes into the terminal. The event value + * is whatever `string` results, in a typical setup, this should be passed + * on to the backing pty. + * @returns an `IDisposable` to stop listening. + */ + onData: IEvent; + + /** + * Adds an event listener for a key is pressed. The event value contains the + * string that will be sent in the data event as well as the DOM event that + * triggered it. + * @returns an `IDisposable` to stop listening. + */ + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + + /** + * Adds an event listener for when a line feed is added. + * @returns an `IDisposable` to stop listening. + */ + onLineFeed: IEvent; + + /** + * Adds an event listener for when a scroll occurs. The event value is the + * new position of the viewport. + * @returns an `IDisposable` to stop listening. + */ + onScroll: IEvent; + + /** + * Adds an event listener for when a selection change occurs. + * @returns an `IDisposable` to stop listening. + */ + onSelectionChange: IEvent; + + /** + * Adds an event listener for when rows are rendered. The event value + * contains the start row and end rows of the rendered area (ranges from `0` + * to `Terminal.rows - 1`). + * @returns an `IDisposable` to stop listening. + */ + onRender: IEvent<{ start: number, end: number }>; + + /** + * Adds an event listener for when the terminal is resized. The event value + * contains the new size. + * @returns an `IDisposable` to stop listening. + */ + onResize: IEvent<{ cols: number, rows: number }>; + + /** + * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. + * The event value is the new title. + * @returns an `IDisposable` to stop listening. + */ + onTitleChange: IEvent; + /** * Unfocus the terminal. */ @@ -374,54 +451,63 @@ declare module 'vscode-xterm' { * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'data', listener: (...args: any[]) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'key', listener: (key: string, event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'keypress' | 'keydown', listener: (event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'refresh', listener: (data: { start: number, end: number }) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'resize', listener: (data: { cols: number, rows: number }) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'scroll', listener: (ydisp: number) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'title', listener: (title: string) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: string, listener: (...args: any[]) => void): void; @@ -429,6 +515,7 @@ declare module 'vscode-xterm' { * Deregisters an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener).dispose()` instead. */ off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; @@ -446,11 +533,14 @@ declare module 'vscode-xterm' { * be used to conveniently remove the event listener. * @param type The type of event. * @param handler The event handler. + * @deprecated use `Terminal.onEvent(listener)` instead. */ addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable; /** - * Resizes the terminal. + * Resizes the terminal. It's best practice to debounce calls to resize, + * this will help ensure that the pty can respond to the resize event + * before another one occurs. * @param x The number of columns to resize to. * @param y The number of rows to resize to. */ @@ -476,11 +566,35 @@ declare module 'vscode-xterm' { * should be processed by the terminal and what keys should not. * @param customKeyEventHandler The custom KeyboardEvent handler to attach. * This is a function that takes a KeyboardEvent, allowing consumers to stop - * propogation and/or prevent the default action. The function returns + * propagation and/or prevent the default action. The function returns * whether the event should be processed by xterm.js. */ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; + /** + * (EXPERIMENTAL) Adds a handler for CSI escape sequences. + * @param flag The flag should be one-character string, which specifies the + * final character (e.g "m" for SGR) of the CSI sequence. + * @param callback The function to handle the escape sequence. The callback + * is called with the numerical params, as well as the special characters + * (e.g. "$" for DECSCPP). Return true if the sequence was handled; false if + * we should try a previous handler (set by addCsiHandler or setCsiHandler). + * The most recently-added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + + /** + * (EXPERIMENTAL) Adds a handler for OSC escape sequences. + * @param ident The number (first parameter) of the sequence. + * @param callback The function to handle the escape sequence. The callback + * is called with OSC data string. Return true if the sequence was handled; + * false if we should try a previous handler (set by addOscHandler or + * setOscHandler). The most recently-added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + /** * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to * be matched and handled. @@ -762,10 +876,8 @@ declare module 'vscode-xterm' { handler(text: string): void; - /** - * Emit an event on the terminal. - */ - emit(type: string, data: any): void; + _onScroll: IEventEmitter2; + _onKey: IEventEmitter2<{ key: string }>; charMeasure?: { height: number, width: number }; @@ -775,6 +887,10 @@ declare module 'vscode-xterm' { }; } + interface IEventEmitter2 { + fire(e: T): void; + } + interface ISearchOptions { /** * Whether the find should be done as a regex. @@ -794,7 +910,6 @@ declare module 'vscode-xterm' { _core: TerminalCore; webLinksInit(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void; - winptyCompatInit(): void; /** * Find the next instance of the term, then scroll to and select it. If it @@ -813,7 +928,5 @@ declare module 'vscode-xterm' { * @return Whether a result was found. */ findPrevious(term: string, findOptions: ISearchOptions): boolean; - - addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts index c7857624a7d1a9caff0298aec628b6ace672ce7c..2ff3af60b2e2a3822820dc78c2700cf9cb9029d2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts @@ -30,7 +30,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa constructor( private _xterm: Terminal ) { - this._xterm.on('key', key => this._onKey(key)); + this._xterm.onKey(e => this._onKey(e.key)); } public dispose(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 94b419556afcc0a3f4fe91f2c0e1090d79ca09b7..122250c8ea2cde1d7f50931136eb9e748e6d071c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -371,7 +371,7 @@ export class TerminalInstance implements ITerminalInstance { // it gets removed and then added back to the DOM (resetting scrollTop to 0). // Upstream issue: https://github.com/sourcelair/xterm.js/issues/291 if (this._xterm) { - this._xterm.emit('scroll', this._xterm._core.buffer.ydisp); + this._xterm._core._onScroll.fire(this._xterm._core.buffer.ydisp); } } @@ -416,18 +416,17 @@ export class TerminalInstance implements ITerminalInstance { // TODO: Guess whether to use canvas or dom better rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType, // TODO: Remove this once the setting is removed upstream - experimentalCharAtlas: 'dynamic', - experimentalBufferLineImpl: 'TypedArray' + experimentalCharAtlas: 'dynamic' }); if (this._shellLaunchConfig.initialText) { this._xterm.writeln(this._shellLaunchConfig.initialText); } - this._xterm.on('linefeed', () => this._onLineFeed()); - this._xterm.on('key', (key, ev) => this._onKey(key, ev)); + this._xterm.onLineFeed(() => this._onLineFeed()); + this._xterm.onKey(e => this._onKey(e.key, e.domEvent)); if (this._processManager) { this._processManager.onProcessData(data => this._onProcessData(data)); - this._xterm.on('data', data => this._processManager!.write(data)); + this._xterm.onData(data => this._processManager!.write(data)); // TODO: How does the cwd work on detached processes? this.processReady.then(async () => { this._linkHandler.processCwd = await this._processManager!.getInitialCwd(); @@ -439,7 +438,7 @@ export class TerminalInstance implements ITerminalInstance { return; } if (this._processManager.os === platform.OperatingSystem.Windows) { - this._xterm.winptyCompatInit(); + this._xterm.setOption('windowsMode', true); // Force line data to be sent when the cursor is moved, the main purpose for // this is because ConPTY will often not do a line feed but instead move the // cursor, in which case we still want to send the current line's data to tasks. @@ -453,11 +452,10 @@ export class TerminalInstance implements ITerminalInstance { } else if (this.shellLaunchConfig.isRendererOnly) { this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, undefined, undefined); } - this._xterm.on('focus', () => this._onFocus.fire(this)); // Register listener to trigger the onInput ext API if the terminal is a renderer only if (this._shellLaunchConfig.isRendererOnly) { - this._xterm.on('data', (data) => this._sendRendererInput(data)); + this._xterm.onData(data => this._sendRendererInput(data)); } this._commandTracker = new TerminalCommandTracker(this._xterm); @@ -515,6 +513,7 @@ export class TerminalInstance implements ITerminalInstance { (this._wrapperElement).xterm = this._xterm; this._xterm.open(this._xtermElement); + this._xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this)); this._xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => { // Disable all input if the terminal is exiting if (this._isExiting) { @@ -866,7 +865,7 @@ export class TerminalInstance implements ITerminalInstance { // necessary if the number of rows in the terminal has decreased while it was in the // background since scrollTop changes take no effect but the terminal's position does // change since the number of visible rows decreases. - this._xterm.emit('scroll', this._xterm._core.buffer.ydisp); + this._xterm._core._onScroll.fire(this._xterm._core.buffer.ydisp); if (this._container && this._container.parentElement) { // Force a layout when the instance becomes invisible. This is particularly important // for ensuring that terminals that are created in the background by an extension will diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index b3c85613d87183eff8dcd9ba17dcc28d2e60380f..812db3a707361ad7867cefb222675410e359f9f0 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -36,7 +36,6 @@ export class TerminalInstanceService implements ITerminalInstanceService { // Enable xterm.js addons Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/search/search')); Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/webLinks/webLinks')); - Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/winptyCompat/winptyCompat')); Terminal.applyAddon(typeAheadAddon); // Localize strings Terminal.strings.blankLine = nls.localize('terminal.integrated.a11yBlankLine', 'Blank line'); diff --git a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts index d841d02369590415101785aeddaf8e846d84301f..1bc875456ddb9bf2424e796ba8f92b8a8ed6a542 100644 --- a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts @@ -61,15 +61,15 @@ export class WindowsShellHelper implements IWindowsShellHelper { // If this is done on every linefeed, parsing ends up taking // significantly longer due to resetting timers. Note that this is // private API. - this._xterm.on('linefeed', () => this._newLineFeed = true); - this._xterm.on('cursormove', () => { + this._xterm.onLineFeed(() => this._newLineFeed = true); + this._xterm.onCursorMove(() => { if (this._newLineFeed) { this._onCheckShell.fire(undefined); } }); // Fire a new check for the shell when any key is pressed. - this._xterm.on('keypress', () => this._onCheckShell.fire(undefined)); + this._xterm.onKey(() => this._onCheckShell.fire(undefined)); }); } diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts index ec94ff8a518bcec580a84731953eada0d2c0d2e8..cd80a60ee1ace15429977bb6c71908593cf2df0f 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts @@ -46,13 +46,13 @@ suite('Workbench - TerminalCommandTracker', () => { test('should track commands when the prompt is of sufficient size', () => { assert.equal(xterm.markers.length, 0); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); + xterm._core._onKey.fire({ key: '\x0d' }); assert.equal(xterm.markers.length, 1); }); test('should not track commands when the prompt is too small', () => { assert.equal(xterm.markers.length, 0); syncWrite(xterm, '\x1b[2G'); // Move cursor to column 2 - xterm.emit('key', '\x0d'); + xterm._core._onKey.fire({ key: '\x0d' }); assert.equal(xterm.markers.length, 0); }); }); @@ -60,7 +60,7 @@ suite('Workbench - TerminalCommandTracker', () => { suite('Commands', () => { test('should scroll to the next and previous commands', () => { syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line #10 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line #10 assert.equal(xterm.markers[0].line, 9); for (let i = 0; i < 20; i++) { @@ -94,11 +94,11 @@ suite('Workbench - TerminalCommandTracker', () => { syncWrite(xterm, '\r0'); syncWrite(xterm, '\n\r1'); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line assert.equal(xterm.markers[0].line, 10); syncWrite(xterm, '\n\r2'); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line assert.equal(xterm.markers[1].line, 11); syncWrite(xterm, '\n\r3'); @@ -124,11 +124,11 @@ suite('Workbench - TerminalCommandTracker', () => { syncWrite(xterm, '\r0'); syncWrite(xterm, '\n\r1'); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line assert.equal(xterm.markers[0].line, 10); syncWrite(xterm, '\n\r2'); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line assert.equal(xterm.markers[1].line, 11); syncWrite(xterm, '\n\r3'); diff --git a/yarn.lock b/yarn.lock index 1e8975e4d53cd181a0474257ca7ddeb57314f1e4..3cc66ed63a5a1696f7467e1bd040165772f706c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9541,10 +9541,10 @@ vscode-windows-registry@1.0.1: dependencies: nan "^2.12.1" -vscode-xterm@3.13.0-beta3: - version "3.13.0-beta3" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.13.0-beta3.tgz#ab642ed77df07c2adfca7b15ae39c18328db3fc6" - integrity sha512-XvgD/P6CCV0+79UYM7CEL6Ziv2RiDopI3Wa1hIm3Dm8InWxkl3QwykINlempMNub+r0gwVwLKSQYr+Q/jg1IAw== +vscode-xterm@3.14.0-beta1: + version "3.14.0-beta1" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.14.0-beta1.tgz#3cf7ecd0b8fe995675cb8fd30239c481c420aba7" + integrity sha512-CFA3foOLrY7pUw2E8xzpPwP40hDbFubPNO4D8PTh2u2UDhJ9PYfQaJxXujCoic8qg9uEVVnLUbqqNlK2qGWFnA== vso-node-api@6.1.2-preview: version "6.1.2-preview"