mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge pull request #4539 from Tyriar/tyriar/windowsPty
Add more granular windowsPty option
This commit is contained in:
+5
-1
@@ -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);
|
||||
|
||||
@@ -1041,6 +1041,47 @@ describe('Terminal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Windows Pty', () => {
|
||||
it('should mark lines as wrapped when the line ends in a non-null character after a LF', async () => {
|
||||
const data = [
|
||||
'aaaaaaaaaa\n\r', // cannot wrap as it's the first
|
||||
'aaaaaaaaa\n\r', // wrapped (windows mode only)
|
||||
'aaaaaaaaa' // not wrapped
|
||||
];
|
||||
|
||||
const normalTerminal = new TestTerminal({ rows: 5, cols: 10, windowsPty: {} });
|
||||
await normalTerminal.writeP(data.join(''));
|
||||
assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false);
|
||||
assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false);
|
||||
assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false);
|
||||
|
||||
const windowsModeTerminal = new TestTerminal({ rows: 5, cols: 10, windowsPty: { backend: 'conpty', buildNumber: 19000 } });
|
||||
await windowsModeTerminal.writeP(data.join(''));
|
||||
assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false);
|
||||
assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');
|
||||
assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false);
|
||||
});
|
||||
|
||||
it('should mark lines as wrapped when the line ends in a non-null character after a CUP', async () => {
|
||||
const data = [
|
||||
'aaaaaaaaaa\x1b[2;1H', // cannot wrap as it's the first
|
||||
'aaaaaaaaa\x1b[3;1H', // wrapped (windows mode only)
|
||||
'aaaaaaaaa' // not wrapped
|
||||
];
|
||||
|
||||
const normalTerminal = new TestTerminal({ rows: 5, cols: 10, windowsPty: {} });
|
||||
await normalTerminal.writeP(data.join(''));
|
||||
assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false);
|
||||
assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false);
|
||||
assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false);
|
||||
|
||||
const windowsModeTerminal = new TestTerminal({ rows: 5, cols: 10, windowsPty: { backend: 'conpty', buildNumber: 19000 } });
|
||||
await windowsModeTerminal.writeP(data.join(''));
|
||||
assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false);
|
||||
assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');
|
||||
assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false);
|
||||
});
|
||||
});
|
||||
describe('Windows Mode', () => {
|
||||
it('should mark lines as wrapped when the line ends in a non-null character after a LF', async () => {
|
||||
const data = [
|
||||
|
||||
+23
-19
@@ -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<string>());
|
||||
public readonly onBinary = this._onBinary.event;
|
||||
@@ -131,7 +131,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
this.register(forwardEvent(this.coreService.onBinary, this._onBinary));
|
||||
this.register(this.coreService.onRequestScrollToBottom(() => this.scrollToBottom()));
|
||||
this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput()));
|
||||
this.register(this.optionsService.onSpecificOptionChange('windowsMode', e => this._handleWindowsModeOptionChange(e)));
|
||||
this.register(this.optionsService.onMultipleOptionChange(['windowsMode', 'windowsPty'], () => this._handleWindowsPtyOptionChange()));
|
||||
this.register(this._bufferService.onScroll(event => {
|
||||
this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL });
|
||||
this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
|
||||
@@ -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,9 +250,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
}
|
||||
|
||||
protected _setup(): void {
|
||||
if (this.optionsService.rawOptions.windowsMode) {
|
||||
this._enableWindowsMode();
|
||||
}
|
||||
this._handleWindowsPtyOptionChange();
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
@@ -263,30 +261,36 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
this.coreMouseService.reset();
|
||||
}
|
||||
|
||||
private _handleWindowsModeOptionChange(value: boolean): void {
|
||||
|
||||
private _handleWindowsPtyOptionChange(): void {
|
||||
let value = false;
|
||||
const windowsPty = this.optionsService.rawOptions.windowsPty;
|
||||
if (windowsPty && windowsPty.buildNumber !== undefined && windowsPty.buildNumber !== undefined) {
|
||||
value = !!(windowsPty.backend === 'conpty' && windowsPty.buildNumber < 21376);
|
||||
} else if (this.optionsService.rawOptions.windowsMode) {
|
||||
value = true;
|
||||
}
|
||||
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();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
|
||||
rightClickSelectsWord: isMac,
|
||||
windowOptions: {},
|
||||
windowsMode: false,
|
||||
windowsPty: {},
|
||||
wordSeparator: ' ()[]{}\',"`',
|
||||
altClickMovesCursor: true,
|
||||
convertEol: false,
|
||||
|
||||
@@ -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<IBufferService>('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;
|
||||
|
||||
Vendored
+39
@@ -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.
|
||||
*/
|
||||
|
||||
Vendored
+35
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user