From 22140b5e941770fc6e8d00c5bd572fe09100575f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 29 May 2023 09:39:55 -0700 Subject: [PATCH] Add more granular windowsPty option See microsoft/vscode#134448 --- demo/client.ts | 6 ++++- src/common/CoreTerminal.ts | 35 +++++++++++++----------- src/common/buffer/Buffer.ts | 6 ++++- src/common/services/OptionsService.ts | 1 + src/common/services/Services.ts | 3 ++- typings/xterm-headless.d.ts | 39 +++++++++++++++++++++++++++ typings/xterm.d.ts | 35 ++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 19 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index d72ddf65..11837c5c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -240,7 +240,11 @@ function createTerminal(): void { const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; term = new Terminal({ allowProposedApi: true, - windowsMode: isWindows, + windowsPty: isWindows ? { + // In a real scenario, these values should be verified on the backend + backend: 'conpty', + buildNumber: 22621 + } : undefined, fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"', theme: xtermjsTheme } as ITerminalOptions); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 33637421..649cd091 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -57,7 +57,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected _inputHandler: InputHandler; private _writeBuffer: WriteBuffer; - private _windowsMode: IDisposable | undefined; + private _windowsWrappingHeuristics: IDisposable | undefined; private readonly _onBinary = this.register(new EventEmitter()); public readonly onBinary = this._onBinary.event; @@ -146,8 +146,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); this.register(toDisposable(() => { - this._windowsMode?.dispose(); - this._windowsMode = undefined; + this._windowsWrappingHeuristics?.dispose(); + this._windowsWrappingHeuristics = undefined; })); } @@ -250,8 +250,13 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { } protected _setup(): void { - if (this.optionsService.rawOptions.windowsMode) { - this._enableWindowsMode(); + const windowsPty = this.optionsService.rawOptions.windowsPty; + if (windowsPty) { + if (windowsPty.buildNumber && windowsPty.backend === 'conpty' && windowsPty.buildNumber < 21376) { + this._enableWindowsWrappingHeuristics(); + } + } else if (this.optionsService.rawOptions.windowsMode) { + this._enableWindowsWrappingHeuristics(); } } @@ -265,28 +270,26 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _handleWindowsModeOptionChange(value: boolean): void { if (value) { - this._enableWindowsMode(); + this._enableWindowsWrappingHeuristics(); } else { - this._windowsMode?.dispose(); - this._windowsMode = undefined; + this._windowsWrappingHeuristics?.dispose(); + this._windowsWrappingHeuristics = undefined; } } - protected _enableWindowsMode(): void { - if (!this._windowsMode) { + protected _enableWindowsWrappingHeuristics(): void { + if (!this._windowsWrappingHeuristics) { const disposables: IDisposable[] = []; disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); disposables.push(this.registerCsiHandler({ final: 'H' }, () => { updateWindowsModeWrappedState(this._bufferService); return false; })); - this._windowsMode = { - dispose: () => { - for (const d of disposables) { - d.dispose(); - } + this._windowsWrappingHeuristics = toDisposable(() => { + for (const d of disposables) { + d.dispose(); } - }; + }); } } } diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index cabc88fa..f32ce385 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -177,7 +177,7 @@ export class Buffer implements IBuffer { if (this._rows < newRows) { for (let y = this._rows; y < newRows; y++) { if (this.lines.length < newRows + this.ybase) { - if (this._optionsService.rawOptions.windowsMode) { + if (this._optionsService.rawOptions.windowsMode || this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined) { // Just add the new missing rows on Windows as conpty reprints the screen with it's // view of the world. Once a line enters scrollback for conpty it remains there this.lines.push(new BufferLine(newCols, nullCell)); @@ -290,6 +290,10 @@ export class Buffer implements IBuffer { } private get _isReflowEnabled(): boolean { + const windowsPty = this._optionsService.rawOptions.windowsPty; + if (windowsPty && windowsPty.buildNumber) { + return this._hasScrollback && windowsPty.backend === 'conpty' && windowsPty.buildNumber >= 21376; + } return this._hasScrollback && !this._optionsService.rawOptions.windowsMode; } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 9709f2a7..a8650e76 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -43,6 +43,7 @@ export const DEFAULT_OPTIONS: Readonly> = { rightClickSelectsWord: isMac, windowOptions: {}, windowsMode: false, + windowsPty: {}, wordSeparator: ' ()[]{}\',"`', altClickMovesCursor: true, convertEol: false, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index b2009690..679b246e 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -7,7 +7,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, IOscLinkData } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; -import { IDecorationOptions, IDecoration, ILinkHandler } from 'xterm'; +import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty } from 'xterm'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { @@ -242,6 +242,7 @@ export interface ITerminalOptions { tabStopWidth?: number; theme?: ITheme; windowsMode?: boolean; + windowsPty?: IWindowsPty; windowOptions?: IWindowOptions; wordSeparator?: string; overviewRulerWidth?: number; diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 741bce85..45a746e3 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -183,9 +183,34 @@ declare module 'xterm-headless' { * - Reflow is disabled. * - Lines are assumed to be wrapped if the last character of the line is * not whitespace. + * + * When using conpty on Windows 11 version >= 21376, it is recommended to + * disable this because native text wrapping sequences are output correctly + * thanks to https://github.com/microsoft/terminal/issues/405 + * + * @deprecated Use {@link windowsPty}. This value will be ignored if + * windowsPty is set. */ windowsMode?: boolean; + /** + * Compatibility information when the pty is known to be hosted on Windows. + * Setting this will turn on certain heuristics/workarounds depending on the + * values: + * + * - `if (!!windowsCompat)` + * - When increasing the rows in the terminal, the amount increased into + * the scrollback. This is done because ConPTY does not behave like + * expect scrollback to come back into the viewport, instead it makes + * empty rows at of the viewport. Not having this behavior can result in + * missing data as the rows get replaced. + * - `if !(backend === 'conpty' && buildNumber >= 21376)` + * - Reflow is disabled + * - Lines are assumed to be wrapped if the last character of the line is + * not whitespace. + */ + windowsPty?: IWindowsPty; + /** * A string containing all characters that are considered word separated by the * double click to select work logic. @@ -265,6 +290,20 @@ declare module 'xterm-headless' { extendedAnsi?: string[]; } + /** + * Pty information for Windows. + */ + export interface IWindowsPty { + /** + * What pty emulation backend is being used. + */ + backend?: 'conpty' | 'winpty'; + /** + * The Windows build version (eg. 19045) + */ + buildNumber?: number; + } + /** * An object that can be disposed via a dispose function. */ diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 6a9be2f1..173af461 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -238,9 +238,30 @@ declare module 'xterm' { * When using conpty on Windows 11 version >= 21376, it is recommended to * disable this because native text wrapping sequences are output correctly * thanks to https://github.com/microsoft/terminal/issues/405 + * + * @deprecated Use {@link windowsPty}. This value will be ignored if + * windowsPty is set. */ windowsMode?: boolean; + /** + * Compatibility information when the pty is known to be hosted on Windows. + * Setting this will turn on certain heuristics/workarounds depending on the + * values: + * + * - `if (backend !== undefined || buildNumber !== undefined)` + * - When increasing the rows in the terminal, the amount increased into + * the scrollback. This is done because ConPTY does not behave like + * expect scrollback to come back into the viewport, instead it makes + * empty rows at of the viewport. Not having this behavior can result in + * missing data as the rows get replaced. + * - `if !(backend === 'conpty' && buildNumber >= 21376)` + * - Reflow is disabled + * - Lines are assumed to be wrapped if the last character of the line is + * not whitespace. + */ + windowsPty?: IWindowsPty; + /** * A string containing all characters that are considered word separated by the * double click to select work logic. @@ -330,6 +351,20 @@ declare module 'xterm' { extendedAnsi?: string[]; } + /** + * Pty information for Windows. + */ + export interface IWindowsPty { + /** + * What pty emulation backend is being used. + */ + backend?: 'conpty' | 'winpty'; + /** + * The Windows build version (eg. 19045) + */ + buildNumber?: number; + } + /** * An object that can be disposed via a dispose function. */