From 5a4e88dc51dc92c5a1dddb6c9ecde41c696a58d3 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Sun, 31 Oct 2021 12:27:41 +0100 Subject: [PATCH 01/27] input: prevent duplicate IME input on Linux - fixes #3533 --- src/browser/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index d28be5bf..0312ca4b 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1182,7 +1182,7 @@ export class Terminal extends CoreTerminal implements ITerminal { protected _inputEvent(ev: InputEvent): boolean { // Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to // support reading out character input which can doubling up input characters - if (ev.data && ev.inputType === 'insertText' && !this.optionsService.options.screenReaderMode) { + if (ev.data && ev.inputType === 'insertText' && !ev.composed && !this.optionsService.options.screenReaderMode) { if (this._keyPressHandled) { return false; } From bec4f6d5c7fa4a824cbc83630b1381494d1ffea5 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Mon, 1 Nov 2021 15:09:25 +0000 Subject: [PATCH 02/27] clear isWrapped in eraseBufferLine --- src/common/InputHandler.test.ts | 50 +++++++++++++++++++++++++++++++++ src/common/InputHandler.ts | 28 +++++++++--------- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index e25c3df1..a0930c51 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -386,6 +386,56 @@ describe('InputHandler', () => { assert.equal(bufferService.buffer.lines.get(2)!.translateToString(false), Array(bufferService.cols + 1).join(' ')); }); + it('eraseInLine reflow', async () => { + const bufferService = new MockBufferService(80, 30); + const inputHandler = new TestInputHandler( + bufferService, + new MockCharsetService(), + new MockCoreService(), + new MockDirtyRowService(), + new MockLogService(), + new MockOptionsService(), + new MockCoreMouseService(), + new MockUnicodeService() + ); + + const resetToBaseState = async (): Promise => { + // reset and add a wrapped line + bufferService.buffer.y = 0; + bufferService.buffer.x = 0; + await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0 + await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); + + // confirm precondition that line 2 is wrapped + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true); + }; + + // params[0] - erase from the cursor through the end of the row. + await resetToBaseState(); + bufferService.buffer.y = 2; + bufferService.buffer.x = 40; + inputHandler.eraseInLine(Params.fromArray([0])); + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true); + bufferService.buffer.y = 2; + bufferService.buffer.x = 0; + inputHandler.eraseInLine(Params.fromArray([0])); + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false); + + // params[1] - erase from the beginning of the line through the cursor + await resetToBaseState(); + bufferService.buffer.y = 2; + bufferService.buffer.x = 40; + inputHandler.eraseInLine(Params.fromArray([1])); + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true); + + // params[2] - erase complete line + await resetToBaseState(); + bufferService.buffer.y = 2; + bufferService.buffer.x = 40; + inputHandler.eraseInDisplay(Params.fromArray([2])); + assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false); + }); it('eraseInDisplay', async () => { const bufferService = new MockBufferService(80, 7); const inputHandler = new TestInputHandler( diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 9371e5f5..89089947 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -25,7 +25,7 @@ import { IBuffer } from 'common/buffer/Types'; /** * Map collect to glevel. Used in `selectCharset`. */ -const GLEVEL: {[key: string]: number} = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 }; +const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 }; /** * VT commands done by the parser - FIXME: move this to the parser? @@ -167,7 +167,7 @@ class DECRQSS implements IDcsHandler { break; case 'r': // DECSTBM const pt = '' + (this._bufferService.buffer.scrollTop + 1) + - ';' + (this._bufferService.buffer.scrollBottom + 1) + 'r'; + ';' + (this._bufferService.buffer.scrollBottom + 1) + 'r'; this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`); break; case 'm': // SGR @@ -175,7 +175,7 @@ class DECRQSS implements IDcsHandler { this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`); break; case ' q': // DECSCUSR - const STYLES: {[key: string]: number} = { 'block': 2, 'underline': 4, 'bar': 6 }; + const STYLES: { [key: string]: number } = { 'block': 2, 'underline': 4, 'bar': 6 }; let style = STYLES[this._optionsService.options.cursorStyle]; style -= this._optionsService.options.cursorBlink ? 1 : 0; this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); @@ -855,10 +855,9 @@ export class InputHandler extends Disposable implements IInputHandler { * - any cursor movement sequence keeps working as expected */ if (this._activeBuffer.x === 0 - && this._activeBuffer.y > this._activeBuffer.scrollTop - && this._activeBuffer.y <= this._activeBuffer.scrollBottom - && this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) - { + && this._activeBuffer.y > this._activeBuffer.scrollTop + && this._activeBuffer.y <= this._activeBuffer.scrollBottom + && this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) { this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false; this._activeBuffer.y--; this._activeBuffer.x = this._bufferService.cols - 1; @@ -1195,6 +1194,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @param y row index * @param start first cell index to be erased * @param end end - 1 is last erased cell + * @param cleanWrap clear the isWrapped flag */ private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void { const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; @@ -1320,13 +1320,13 @@ export class InputHandler extends Disposable implements IInputHandler { this._restrictCursor(this._bufferService.cols); switch (params.params[0]) { case 0: - this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols); + this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0); break; case 1: - this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1); + this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false); break; case 2: - this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols); + this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true); break; } this._dirtyRowService.markDirty(this._activeBuffer.y); @@ -1977,7 +1977,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 1049: // alt screen buffer cursor this.saveCursor(); - // FALL-THROUGH + // FALL-THROUGH case 47: // alt screen buffer case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); @@ -2197,7 +2197,7 @@ export class InputHandler extends Disposable implements IInputHandler { this.restoreCursor(); break; case 1049: // alt screen buffer cursor - // FALL-THROUGH + // FALL-THROUGH case 47: // normal screen buffer case 1047: // normal screen buffer - clearing it first // Ensure the selection manager has the correct buffer @@ -2264,7 +2264,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // exit early if can decide color mode with semicolons if ((accu[1] === 5 && advance + cSpace >= 2) - || (accu[1] === 2 && advance + cSpace >= 5)) { + || (accu[1] === 2 && advance + cSpace >= 5)) { break; } // offset colorSpace slot for semicolon mode @@ -2683,7 +2683,7 @@ export class InputHandler extends Disposable implements IInputHandler { const top = params.params[0] || 1; let bottom: number; - if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) { + if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) { bottom = this._bufferService.rows; } From 086ac39095aa1dc21f24fde627e7194ebd737d26 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Mon, 1 Nov 2021 15:23:10 +0000 Subject: [PATCH 03/27] Add whitespaces --- src/common/InputHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 89089947..870ecf01 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -1977,7 +1977,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 1049: // alt screen buffer cursor this.saveCursor(); - // FALL-THROUGH + // FALL-THROUGH case 47: // alt screen buffer case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); @@ -2197,7 +2197,7 @@ export class InputHandler extends Disposable implements IInputHandler { this.restoreCursor(); break; case 1049: // alt screen buffer cursor - // FALL-THROUGH + // FALL-THROUGH case 47: // normal screen buffer case 1047: // normal screen buffer - clearing it first // Ensure the selection manager has the correct buffer From 47e53cbdaa8da47ddd66e920c322820af52730b8 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Mon, 1 Nov 2021 15:42:01 +0000 Subject: [PATCH 04/27] Fix unit test calling eraseInDisplay instead of eraseInLine --- src/common/InputHandler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index a0930c51..bff7cbe9 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -433,7 +433,7 @@ describe('InputHandler', () => { await resetToBaseState(); bufferService.buffer.y = 2; bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([2])); + inputHandler.eraseInLine(Params.fromArray([2])); assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false); }); it('eraseInDisplay', async () => { From 3582c00a6176fe705d68265bc7ea4bc6cd6c5f31 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sat, 6 Nov 2021 14:36:40 +0000 Subject: [PATCH 05/27] Allow setting multiple options through term.options --- src/browser/Terminal.ts | 4 ---- src/browser/Types.d.ts | 1 - src/browser/public/Terminal.ts | 3 +++ src/common/CoreTerminal.ts | 9 +++++++-- src/common/TestUtils.test.ts | 6 ++++++ src/common/services/Services.ts | 18 ++++++++++-------- typings/xterm.d.ts | 2 +- 7 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 24dc7af9..b0506850 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -52,7 +52,6 @@ import { MouseService } from 'browser/services/MouseService'; import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; import { CoreTerminal } from 'common/CoreTerminal'; -import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; import { rgba } from 'browser/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; @@ -74,9 +73,6 @@ export class Terminal extends CoreTerminal implements ITerminal { public browser: IBrowser = Browser as any; - // TODO: We should remove options once components adopt optionsService - public get options(): IInitializedTerminalOptions { return this.optionsService.options; } - private _customKeyEventHandler: CustomKeyEventHandler | undefined; // browser services diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index bafeff77..a6165840 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -16,7 +16,6 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal { browser: IBrowser; buffer: IBuffer; viewport: IViewport | undefined; - // TODO: We should remove options once components adopt optionsService options: ITerminalOptions; linkifier: ILinkifier; linkifier2: ILinkifier2; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 087fde94..a5536855 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -92,6 +92,9 @@ export class Terminal implements ITerminalApi { public get options(): ITerminalOptions { return this._core.options; } + public set options(options: ITerminalOptions) { + this._core.options = options; + } public blur(): void { this._core.blur(); } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index a5f78f66..6ef9011a 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -22,12 +22,12 @@ */ import { Disposable } from 'common/Lifecycle'; -import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum } from 'common/services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; -import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource } from 'common/Types'; +import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; @@ -87,6 +87,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { public get rows(): number { return this._bufferService.rows; } public get buffers(): IBufferSet { return this._bufferService.buffers; } public get options(): ITerminalOptions { return this.optionsService.publicOptions; } + public set options(options: ITerminalOptions) { + for (const key in options) { + this.optionsService.publicOptions[key] = options[key]; + } + } constructor( options: Partial diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 014142ac..00aedff6 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -131,6 +131,12 @@ export class MockOptionsService implements IOptionsService { } } } + public setOptions(options: ITerminalOptions): void { + for (const key of Object.keys(options)) { + this.options[key] = options[key]; + this.publicOptions[key] = options[key]; + } + } public setOption(key: string, value: T): void { throw new Error('Method not implemented.'); } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 56b10f73..ed909723 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -164,6 +164,14 @@ export interface IInstantiationService { createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; } +export enum LogLevelEnum { + DEBUG = 0, + INFO = 1, + WARN = 2, + ERROR = 3, + OFF = 4 +} + export const ILogService = createDecorator('LogService'); export interface ILogService { serviceBrand: undefined; @@ -191,13 +199,7 @@ export interface IOptionsService { export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number; export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off'; -export enum LogLevelEnum { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3, - OFF = 4 -} + export type RendererType = 'dom' | 'canvas'; export interface ITerminalOptions { @@ -207,6 +209,7 @@ export interface ITerminalOptions { bellSound: string; bellStyle: 'none' | 'sound' /* | 'visual' | 'both' */; cols: number; + convertEol: boolean; cursorBlink: boolean; cursorStyle: 'block' | 'underline' | 'bar'; cursorWidth: number; @@ -240,7 +243,6 @@ export interface ITerminalOptions { [key: string]: any; cancelEvents: boolean; - convertEol: boolean; termName: string; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f67e3a16..39f2bfb0 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -638,7 +638,7 @@ declare module 'xterm' { /** * Get the terminal options */ - readonly options: ITerminalOptions; + options: ITerminalOptions; /** * Natural language strings that can be localized. From b1c179461d612e0ef5e2c35395218ad8d28231cc Mon Sep 17 00:00:00 2001 From: silamon <32477463+silamon@users.noreply.github.com> Date: Wed, 10 Nov 2021 09:15:43 +0100 Subject: [PATCH 06/27] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 39f2bfb0..234b7ea0 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -636,7 +636,26 @@ declare module 'xterm' { readonly modes: IModes; /** - * Get the terminal options + * Gets or sets the terminal options. This supports setting multiple options. + * + * @example Get a single option + * ```typescript + * console.log(terminal.options.fontSize); + * ``` + * + * @example Set a single option + * ```typescript + * terminal.options.fontSize = 12; + * ``` + * + * @example Set multiple options + * ```typescript + * terminal.options = { + * fontSize: 12, + * fontFamily: 'Arial', + * ; + * ``` + */ */ options: ITerminalOptions; From 2c65d9686376fa9cdc901d045c3ca9daa0c6ada3 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 10 Nov 2021 08:39:28 +0000 Subject: [PATCH 07/27] Fix tests --- src/browser/Terminal.ts | 10 +++++++++- src/common/CoreTerminal.ts | 4 ++-- typings/xterm.d.ts | 3 +-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index b0506850..5336382d 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IAnsiColorChangeEvent } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ScrollSource, IAnsiColorChangeEvent } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -54,6 +54,7 @@ import { CoreBrowserService } from 'browser/services/CoreBrowserService'; import { CoreTerminal } from 'common/CoreTerminal'; import { rgba } from 'browser/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; +import { ITerminalOptions } from 'common/services/Services'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -73,6 +74,13 @@ export class Terminal extends CoreTerminal implements ITerminal { public browser: IBrowser = Browser as any; + public get options(): ITerminalOptions { return this.optionsService.options; } + public set options(options: ITerminalOptions) { + for (const key in options) { + this.optionsService.options[key] = options[key]; + } + } + private _customKeyEventHandler: CustomKeyEventHandler | undefined; // browser services diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 6ef9011a..ab1d6c24 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -22,12 +22,12 @@ */ import { Disposable } from 'common/Lifecycle'; -import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions } from 'common/services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; -import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource } from 'common/Types'; +import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource, ITerminalOptions } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 234b7ea0..2a274a24 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -653,10 +653,9 @@ declare module 'xterm' { * terminal.options = { * fontSize: 12, * fontFamily: 'Arial', - * ; + * }; * ``` */ - */ options: ITerminalOptions; /** From 317641c3bb4bd6ebe2ff2a345920be1bdda983cc Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 10 Nov 2021 09:42:45 +0000 Subject: [PATCH 08/27] Add some tests --- src/browser/Terminal.test.ts | 18 +++++++++++- src/browser/Terminal.ts | 7 ----- src/browser/TestUtils.test.ts | 1 + src/browser/Types.d.ts | 1 + src/browser/public/Terminal.test.ts | 43 +++++++++++++++++++++++++++++ src/browser/public/Terminal.ts | 4 +-- src/common/CoreTerminal.ts | 14 +++++++--- 7 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 src/browser/public/Terminal.test.ts diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index a102d93f..872bfdc7 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -11,7 +11,7 @@ import { IBufferService, IUnicodeService } from 'common/services/Services'; import { Linkifier } from 'browser/Linkifier'; import { MockLogService, MockUnicodeService } from 'common/TestUtils.test'; import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; -import { IMarker } from 'common/Types'; +import { IMarker, ITerminalOptions } from 'common/Types'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -1501,6 +1501,22 @@ describe('Terminal', () => { assert.deepEqual(markers.map(el => el.line), [-1, -1, 0, 1, 2]); }); }); + + describe('options', () => { + beforeEach(async () => { + term = new TestTerminal({}); + }); + it('get options', () => { + assert.equal(term.options.cols, 80); + assert.equal(term.options.rows, 24); + }); + it('set options', async () => { + term.options.cols = 40; + assert.equal(term.options.cols, 40); + term.options.rows = 20; + assert.equal(term.options.rows, 20); + }); + }); }); class TestLinkifier extends Linkifier { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 5336382d..523b50d2 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -74,13 +74,6 @@ export class Terminal extends CoreTerminal implements ITerminal { public browser: IBrowser = Browser as any; - public get options(): ITerminalOptions { return this.optionsService.options; } - public set options(options: ITerminalOptions) { - for (const key in options) { - this.optionsService.options[key] = options[key]; - } - } - private _customKeyEventHandler: CustomKeyEventHandler | undefined; // browser services diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 8fdf458e..268630c7 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -147,6 +147,7 @@ export class MockTerminal implements ITerminal { public linkifier2!: ILinkifier2; public isFocused!: boolean; public options: ITerminalOptions = {}; + public publicOptions: ITerminalOptions = {}; public element!: HTMLElement; public screenElement!: HTMLElement; public rowContainer!: HTMLElement; diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index a6165840..5540690d 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -16,6 +16,7 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal { browser: IBrowser; buffer: IBuffer; viewport: IViewport | undefined; + publicOptions: ITerminalOptions; options: ITerminalOptions; linkifier: ILinkifier; linkifier2: ILinkifier2; diff --git a/src/browser/public/Terminal.test.ts b/src/browser/public/Terminal.test.ts new file mode 100644 index 00000000..f03945a0 --- /dev/null +++ b/src/browser/public/Terminal.test.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal } from 'browser/public/Terminal'; +import { assert } from 'chai'; +import { ITerminalOptions } from 'common/Types'; + +const INIT_COLS = 80; +const INIT_ROWS = 24; + +describe('Public Terminal', () => { + let term: Terminal; + const termOptions = { + cols: INIT_COLS, + rows: INIT_ROWS + }; + + describe('options', () => { + beforeEach(async () => { + term = new Terminal(termOptions); + }); + it('get options', () => { + const options: ITerminalOptions = term.options; + assert.equal(options.cols, 80); + assert.equal(options.rows, 24); + }); + it('set options', async () => { + const options: ITerminalOptions = term.options; + assert.throws(() => options.cols = 40); + assert.throws(() => options.rows = 20); + term.options.scrollback = 1; + assert.equal(term.options.scrollback, 1); + term.options= { + fontSize: 12, + fontFamily: 'Arial' + }; + assert.equal(term.options.fontSize, 12); + assert.equal(term.options.fontFamily, 'Arial'); + }); + }); +}); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index a5536855..2138f116 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -90,10 +90,10 @@ export class Terminal implements ITerminalApi { }; } public get options(): ITerminalOptions { - return this._core.options; + return this._core.publicOptions; } public set options(options: ITerminalOptions) { - this._core.options = options; + this._core.publicOptions = options; } public blur(): void { this._core.blur(); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index ab1d6c24..4223e780 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -22,12 +22,12 @@ */ import { Disposable } from 'common/Lifecycle'; -import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum } from 'common/services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; -import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource, ITerminalOptions } from 'common/Types'; +import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource, ITerminalOptions as IPublicTerminalOptions } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; @@ -86,12 +86,18 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } public get buffers(): IBufferSet { return this._bufferService.buffers; } - public get options(): ITerminalOptions { return this.optionsService.publicOptions; } - public set options(options: ITerminalOptions) { + public get publicOptions(): IPublicTerminalOptions { return this.optionsService.publicOptions; } + public set publicOptions(options: IPublicTerminalOptions) { for (const key in options) { this.optionsService.publicOptions[key] = options[key]; } } + public get options(): ITerminalOptions { return this.optionsService.options; } + public set options(options: ITerminalOptions) { + for (const key in options) { + this.optionsService.options[key] = options[key]; + } + } constructor( options: Partial From 4245ce33b59f876a6c7403d0ab84a76c10bc2449 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 10 Nov 2021 10:17:14 +0000 Subject: [PATCH 09/27] Remove publicOptions completely --- src/browser/TestUtils.test.ts | 1 - src/browser/Types.d.ts | 1 - src/browser/public/Terminal.ts | 22 ++++++++++++++++++++-- src/common/CoreTerminal.ts | 6 ------ src/common/TestUtils.test.ts | 3 --- src/common/services/OptionsService.ts | 22 ++++------------------ src/common/services/Services.ts | 1 - 7 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 268630c7..8fdf458e 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -147,7 +147,6 @@ export class MockTerminal implements ITerminal { public linkifier2!: ILinkifier2; public isFocused!: boolean; public options: ITerminalOptions = {}; - public publicOptions: ITerminalOptions = {}; public element!: HTMLElement; public screenElement!: HTMLElement; public rowContainer!: HTMLElement; diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 5540690d..a6165840 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -16,7 +16,6 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal { browser: IBrowser; buffer: IBuffer; viewport: IViewport | undefined; - publicOptions: ITerminalOptions; options: ITerminalOptions; linkifier: ILinkifier; linkifier2: ILinkifier2; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 2138f116..059b6402 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -13,6 +13,11 @@ import { UnicodeApi } from 'common/public/UnicodeApi'; import { AddonManager } from 'common/public/AddonManager'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; +/** + * The set of options that only have an effect when set in the Terminal constructor. + */ +const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; + export class Terminal implements ITerminalApi { private _core: ITerminal; private _addonManager: AddonManager; @@ -90,10 +95,11 @@ export class Terminal implements ITerminalApi { }; } public get options(): ITerminalOptions { - return this._core.publicOptions; + return this._core.options; } public set options(options: ITerminalOptions) { - this._core.publicOptions = options; + this._checkReadonlyOptions(options); + this._core.options = options; } public blur(): void { this._core.blur(); @@ -219,6 +225,7 @@ export class Terminal implements ITerminalApi { public setOption(key: 'cols' | 'rows', value: number): void; public setOption(key: string, value: any): void; public setOption(key: any, value: any): void { + this._checkReadonlyOptions(); this._core.optionsService.setOption(key, value); } public refresh(start: number, end: number): void { @@ -245,4 +252,15 @@ export class Terminal implements ITerminalApi { } } } + + private _checkReadonlyOptions(options?: ITerminalOptions): void { + // Throw an error if any constructor only option is modified + // from terminal.options + // Modifications from anywhere else are allowed + for (const propName in options) { + if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) { + throw new Error(`Option "${propName}" can only be set in the constructor`); + } + } + } } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 4223e780..d5378247 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -86,12 +86,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } public get buffers(): IBufferSet { return this._bufferService.buffers; } - public get publicOptions(): IPublicTerminalOptions { return this.optionsService.publicOptions; } - public set publicOptions(options: IPublicTerminalOptions) { - for (const key in options) { - this.optionsService.publicOptions[key] = options[key]; - } - } public get options(): ITerminalOptions { return this.optionsService.options; } public set options(options: ITerminalOptions) { for (const key in options) { diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 00aedff6..67488be0 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -121,20 +121,17 @@ export class MockLogService implements ILogService { export class MockOptionsService implements IOptionsService { public serviceBrand: any; public options: ITerminalOptions = clone(DEFAULT_OPTIONS); - public publicOptions: ITerminalOptions = clone(DEFAULT_OPTIONS); public onOptionChange: IEvent = new EventEmitter().event; constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { this.options[key] = testOptions[key]; - this.publicOptions[key] = testOptions[key]; } } } public setOptions(options: ITerminalOptions): void { for (const key of Object.keys(options)) { this.options[key] = options[key]; - this.publicOptions[key] = options[key]; } } public setOption(key: string, value: T): void { diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index a245c153..65b0703c 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -57,17 +57,11 @@ export const DEFAULT_OPTIONS: Readonly = { const FONT_WEIGHT_OPTIONS: Extract[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']; -/** - * The set of options that only have an effect when set in the Terminal constructor. - */ -const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; - export class OptionsService implements IOptionsService { public serviceBrand: any; private _options: ITerminalOptions; public options: ITerminalOptions; - public publicOptions: ITerminalOptions; private _onOptionChange = new EventEmitter(); public get onOptionChange(): IEvent { return this._onOptionChange.event; } @@ -87,11 +81,10 @@ export class OptionsService implements IOptionsService { } // set up getters and setters for each option - this.options = this._setupOptions(this._options, false); - this.publicOptions = this._setupOptions(this._options, true); + this.options = this._setupOptions(this._options); } - private _setupOptions(options: ITerminalOptions, isPublic: boolean): ITerminalOptions { + private _setupOptions(options: ITerminalOptions): ITerminalOptions { const copiedOptions = { ... options }; for (const propName in copiedOptions) { Object.defineProperty(copiedOptions, propName, { @@ -106,13 +99,6 @@ export class OptionsService implements IOptionsService { throw new Error(`No option with key "${propName}"`); } - // Throw an error if any constructor only option is modified - // from terminal.options - // Modifications from anywhere else are allowed - if (isPublic && CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) { - throw new Error(`Option "${propName}" can only be set in the constructor`); - } - value = this._sanitizeAndValidateOption(propName, value); // Don't fire an option change event if they didn't change if (this._options[propName] !== value) { @@ -126,7 +112,7 @@ export class OptionsService implements IOptionsService { } public setOption(key: string, value: any): void { - this.publicOptions[key] = value; + this.options[key] = value; } private _sanitizeAndValidateOption(key: string, value: any): any { @@ -181,6 +167,6 @@ export class OptionsService implements IOptionsService { } public getOption(key: string): any { - return this.publicOptions[key]; + return this.options[key]; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index ed909723..537ac6db 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -189,7 +189,6 @@ export interface IOptionsService { serviceBrand: undefined; readonly options: ITerminalOptions; - readonly publicOptions: ITerminalOptions; readonly onOptionChange: IEvent; From 71257ec00e00a57ee557eb2087ce788f11915b31 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 10 Nov 2021 10:45:42 +0000 Subject: [PATCH 10/27] Fix tests --- src/browser/public/Terminal.ts | 46 ++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 059b6402..f65d48c1 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm'; +import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm'; import { ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; @@ -12,6 +12,7 @@ import { ParserApi } from 'common/public/ParserApi'; import { UnicodeApi } from 'common/public/UnicodeApi'; import { AddonManager } from 'common/public/AddonManager'; import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi'; +import { ITerminalOptions } from 'common/Types'; /** * The set of options that only have an effect when set in the Terminal constructor. @@ -23,10 +24,33 @@ export class Terminal implements ITerminalApi { private _addonManager: AddonManager; private _parser: IParser | undefined; private _buffer: BufferNamespaceApi | undefined; + private _publicOptions: ITerminalOptions; constructor(options?: ITerminalOptions) { this._core = new TerminalCore(options); this._addonManager = new AddonManager(); + + this._publicOptions = {}; + for (const propName in this._core.options) { + Object.defineProperty(this._publicOptions, propName, { + get: () => { + return this._core.options[propName]; + }, + set: (value: any) => { + this._checkReadonlyOptions(propName); + this._core.options[propName] = value; + } + }); + } + } + + private _checkReadonlyOptions(propName: string): void { + // Throw an error if any constructor only option is modified + // from terminal.options + // Modifications from anywhere else are allowed + if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) { + throw new Error(`Option "${propName}" can only be set in the constructor`); + } } private _checkProposedApi(): void { @@ -95,11 +119,12 @@ export class Terminal implements ITerminalApi { }; } public get options(): ITerminalOptions { - return this._core.options; + return this._publicOptions; } public set options(options: ITerminalOptions) { - this._checkReadonlyOptions(options); - this._core.options = options; + for (const propName in options) { + this._publicOptions[propName] = options[propName]; + } } public blur(): void { this._core.blur(); @@ -225,7 +250,7 @@ export class Terminal implements ITerminalApi { public setOption(key: 'cols' | 'rows', value: number): void; public setOption(key: string, value: any): void; public setOption(key: any, value: any): void { - this._checkReadonlyOptions(); + this._checkReadonlyOptions(key); this._core.optionsService.setOption(key, value); } public refresh(start: number, end: number): void { @@ -252,15 +277,4 @@ export class Terminal implements ITerminalApi { } } } - - private _checkReadonlyOptions(options?: ITerminalOptions): void { - // Throw an error if any constructor only option is modified - // from terminal.options - // Modifications from anywhere else are allowed - for (const propName in options) { - if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) { - throw new Error(`Option "${propName}" can only be set in the constructor`); - } - } - } } From 5a468c08770aec164597d9a7bb250cc22558590f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 10 Nov 2021 18:02:00 +0100 Subject: [PATCH 11/27] fix #3548 --- src/browser/renderer/atlas/CharAtlasUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts index b196b373..be92727a 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -16,7 +16,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number cursor: undefined, cursorAccent: undefined, selection: undefined, - ansi: colors.ansi + ansi: [...colors.ansi] }; return { devicePixelRatio: window.devicePixelRatio, From 935a1ba903cdc3a56c953f517e289a070ec985d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Fri, 12 Nov 2021 15:14:13 +0000 Subject: [PATCH 12/27] fix(xterm-addon-webgl): wide characters overflow the cache canvas --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 609df6eb..3627e1f5 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -14,8 +14,9 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { channels, rgba } from 'browser/Color'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; -// In practice we're probably never going to exhaust a texture this large. For debugging purposes, -// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. +// FIXME: rendering many characters can overflow the texture rarely +// For debugging purposes, it can be useful to set this to a really tiny value, +// to verify that LRU eviction works. const TEXTURE_WIDTH = 1024; const TEXTURE_HEIGHT = 1024; @@ -463,7 +464,7 @@ export class WebglCharAtlas implements IDisposable { const clippedImageData = this._clipImageData(imageData, this._workBoundingBox); // Check if there is enough room in the current row and go to next if needed - if (this._currentRowX + this._config.scaledCharWidth > TEXTURE_WIDTH) { + if (this._currentRowX + rasterizedGlyph.size.x > TEXTURE_WIDTH) { this._currentRowX = 0; this._currentRowY += this._currentRowHeight; this._currentRowHeight = 0; From dbd3766a9dcb667fe73735e6938f199bac2d1dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Sat, 13 Nov 2021 01:36:46 +0900 Subject: [PATCH 13/27] chore: reflect reviews --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 3627e1f5..dd95f177 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -14,7 +14,6 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { channels, rgba } from 'browser/Color'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; -// FIXME: rendering many characters can overflow the texture rarely // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. const TEXTURE_WIDTH = 1024; From d7d85870d2efef1a18b270fb0f4a8206c890b99a Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sat, 20 Nov 2021 17:33:38 +0000 Subject: [PATCH 14/27] remove headless include in benchmark projects --- test/benchmark/tsconfig.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/benchmark/tsconfig.json b/test/benchmark/tsconfig.json index cac99d90..2ebe2ebe 100644 --- a/test/benchmark/tsconfig.json +++ b/test/benchmark/tsconfig.json @@ -21,8 +21,7 @@ }, "include": [ "./**/*", - "../../typings/xterm.d.ts", - "../../out/**/*" + "../../typings/xterm.d.ts" ], "exclude": [ "../../**/*test.ts" From d85a57118ebe3c21e21a139e584d3db278be0512 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sat, 20 Nov 2021 17:48:45 +0000 Subject: [PATCH 15/27] remove headless include in benchmark projects --- addons/xterm-addon-serialize/benchmark/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-serialize/benchmark/tsconfig.json b/addons/xterm-addon-serialize/benchmark/tsconfig.json index 42aaaa1b..bf5e335c 100644 --- a/addons/xterm-addon-serialize/benchmark/tsconfig.json +++ b/addons/xterm-addon-serialize/benchmark/tsconfig.json @@ -14,7 +14,7 @@ "SerializeAddon": ["../src/SerializeAddon"] } }, - "include": ["../**/*", "../../../typings/xterm.d.ts", "../../../out/**/*"], + "include": ["../**/*", "../../../typings/xterm.d.ts"], "exclude": ["../../../**/*test.ts", "../../**/*api.ts"], "references": [ { "path": "../../../src/common" }, From 070afc64c817e728603b8088d63f84c502970850 Mon Sep 17 00:00:00 2001 From: Labhansh Agrawal Date: Wed, 24 Nov 2021 12:53:10 +0000 Subject: [PATCH 16/27] trycatch characterJoiner handlers --- src/browser/services/CharacterJoinerService.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/browser/services/CharacterJoinerService.ts b/src/browser/services/CharacterJoinerService.ts index ea65c29b..9cef9fea 100644 --- a/src/browser/services/CharacterJoinerService.ts +++ b/src/browser/services/CharacterJoinerService.ts @@ -176,10 +176,20 @@ export class CharacterJoinerService implements ICharacterJoinerService { // At this point we already know that there is at least one joiner so // we can just pull its value and assign it directly rather than // merging it into an empty array, which incurs unnecessary writes. - const joinedRanges: [number, number][] = this._characterJoiners[0].handler(text); + let joinedRanges: [number, number][] = []; + try { + joinedRanges = this._characterJoiners[0].handler(text); + } catch (error) { + console.error(error); + } for (let i = 1; i < this._characterJoiners.length; i++) { // We merge any overlapping ranges across the different joiners - const joinerRanges = this._characterJoiners[i].handler(text); + let joinerRanges: [number, number][] = []; + try { + joinerRanges = this._characterJoiners[i].handler(text); + } catch (error) { + console.error(error); + } for (let j = 0; j < joinerRanges.length; j++) { CharacterJoinerService._mergeRanges(joinedRanges, joinerRanges[j]); } From d10d5be222173c1d21409959bc92b97d8506fcd2 Mon Sep 17 00:00:00 2001 From: Labhansh Agrawal Date: Thu, 25 Nov 2021 00:11:44 +0530 Subject: [PATCH 17/27] make requested changes --- src/browser/services/CharacterJoinerService.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/browser/services/CharacterJoinerService.ts b/src/browser/services/CharacterJoinerService.ts index 9cef9fea..ca4f1984 100644 --- a/src/browser/services/CharacterJoinerService.ts +++ b/src/browser/services/CharacterJoinerService.ts @@ -176,26 +176,25 @@ export class CharacterJoinerService implements ICharacterJoinerService { // At this point we already know that there is at least one joiner so // we can just pull its value and assign it directly rather than // merging it into an empty array, which incurs unnecessary writes. - let joinedRanges: [number, number][] = []; + let allJoinedRanges: [number, number][] = []; try { - joinedRanges = this._characterJoiners[0].handler(text); + allJoinedRanges = this._characterJoiners[0].handler(text); } catch (error) { console.error(error); } for (let i = 1; i < this._characterJoiners.length; i++) { // We merge any overlapping ranges across the different joiners - let joinerRanges: [number, number][] = []; try { - joinerRanges = this._characterJoiners[i].handler(text); + const joinerRanges = this._characterJoiners[i].handler(text); + for (let j = 0; j < joinerRanges.length; j++) { + CharacterJoinerService._mergeRanges(allJoinedRanges, joinerRanges[j]); + } } catch (error) { console.error(error); } - for (let j = 0; j < joinerRanges.length; j++) { - CharacterJoinerService._mergeRanges(joinedRanges, joinerRanges[j]); - } } - this._stringRangesToCellRanges(joinedRanges, lineData, startCol); - return joinedRanges; + this._stringRangesToCellRanges(allJoinedRanges, lineData, startCol); + return allJoinedRanges; } /** From 1304fd8a72e7a37ab1db28a245f55c4fe11fb823 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 9 Dec 2021 20:30:38 +0000 Subject: [PATCH 18/27] Add tabIndex to xterm-accessibility See microsoft/vscode#135920 --- src/browser/AccessibilityManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index 80092202..eda29c05 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -55,6 +55,7 @@ export class AccessibilityManager extends Disposable { this._accessibilityTreeRoot = document.createElement('div'); this._accessibilityTreeRoot.setAttribute('role', 'document'); this._accessibilityTreeRoot.classList.add('xterm-accessibility'); + this._accessibilityTreeRoot.tabIndex = 0; this._rowContainer = document.createElement('div'); this._rowContainer.setAttribute('role', 'list'); From b7c33974713263af7caaa686730817d8268c48b4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 9 Dec 2021 13:14:43 -0800 Subject: [PATCH 19/27] Clear unprocessed dead key state on input or press Fixes #3573 --- src/browser/Terminal.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 23122f3f..130cb3db 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1169,6 +1169,10 @@ export class Terminal extends CoreTerminal implements ITerminal { this._keyPressHandled = true; + // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow + // keys could be ignored + this._unprocessedDeadKey = false; + return true; } @@ -1186,6 +1190,10 @@ export class Terminal extends CoreTerminal implements ITerminal { return false; } + // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow + // keys could be ignored + this._unprocessedDeadKey = false; + const text = ev.data; this.coreService.triggerDataEvent(text, true); From ddc3d07bd49c1b6b4e7e89779f00cf9efd00996d Mon Sep 17 00:00:00 2001 From: Michael Chlebek Date: Wed, 15 Dec 2021 18:17:28 +0100 Subject: [PATCH 20/27] Added support for custom regex to WebLinkProvider --- addons/xterm-addon-web-links/src/WebLinksAddon.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.ts index dd1c1f17..d1f9d00b 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.ts @@ -43,6 +43,7 @@ function handleLink(event: MouseEvent, uri: string): void { interface ILinkProviderOptions { hover?(event: MouseEvent, text: string, location: IViewportRange): void; leave?(event: MouseEvent, text: string): void; + urlRegex: RegExp | undefined; } export class WebLinksAddon implements ITerminalAddon { @@ -62,7 +63,11 @@ export class WebLinksAddon implements ITerminalAddon { if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) { const options = this._options as ILinkProviderOptions; - this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler, options)); + let regex = strictUrlRegex; + if (options.urlRegex) { + regex = options.urlRegex; + } + this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options)); } else { // TODO: This should be removed eventually const options = this._options as ILinkMatcherOptions; From 7663e4a4e3345fd42768f9a37f9631879c950fa3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 15 Dec 2021 12:03:29 -0800 Subject: [PATCH 21/27] Simplify with || --- addons/xterm-addon-web-links/src/WebLinksAddon.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.ts index d1f9d00b..a9ce3cdc 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.ts @@ -63,10 +63,7 @@ export class WebLinksAddon implements ITerminalAddon { if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) { const options = this._options as ILinkProviderOptions; - let regex = strictUrlRegex; - if (options.urlRegex) { - regex = options.urlRegex; - } + const regex = options.urlRegex || strictUrlRegex; this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options)); } else { // TODO: This should be removed eventually From 7157b03a7e208a082a38d14145751ae426db73eb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 15 Dec 2021 12:04:07 -0800 Subject: [PATCH 22/27] Change interface to use ? So it's not a breaking change --- addons/xterm-addon-web-links/src/WebLinksAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.ts index a9ce3cdc..6ba211fe 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.ts @@ -43,7 +43,7 @@ function handleLink(event: MouseEvent, uri: string): void { interface ILinkProviderOptions { hover?(event: MouseEvent, text: string, location: IViewportRange): void; leave?(event: MouseEvent, text: string): void; - urlRegex: RegExp | undefined; + urlRegex?: RegExp; } export class WebLinksAddon implements ITerminalAddon { From 0ddb43ac8af23516920a4c012343d345716776f6 Mon Sep 17 00:00:00 2001 From: Squitch <63391793+SquitchYT@users.noreply.github.com> Date: Sat, 18 Dec 2021 16:40:58 +0100 Subject: [PATCH 23/27] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93675f32..23d53802 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**WizardWebssh**](https://gitlab.com/mikeramsey/wizardwebssh): A terminal with Pyqt5 Widget for embedding, which can be used as an ssh client to connect to your ssh servers. It is written in Python, based on tornado, paramiko, and xterm.js. - [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built-in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease. - [**ucli**](https://github.com/tsadarsh/ucli): Command Line for everyone :family_man_woman_girl_boy: at [www.ucli.tech](https://www.ucli.tech). -- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone. +- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone. Discover more at [tessapp.dev](https://tessapp.dev) - [**HashiCorp Nomad**](https://www.nomadproject.io/): A container orchestrator with the ability to connect to remote tasks via a web interface using websockets and xterm.js. - [**TermPair**](https://github.com/cs01/termpair): View and control terminals from your browser with end-to-end encryption - [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger) From 9a920a10f38b2102d48d7f45fde01e31a0c42837 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 20 Dec 2021 08:46:13 -0800 Subject: [PATCH 24/27] Have linkifier2 use screen element for link detection Fixes #3579 --- css/xterm.css | 3 ++- src/browser/Terminal.ts | 2 +- typings/xterm.d.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 3fab18bd..38e27a00 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -133,7 +133,8 @@ cursor: default; } -.xterm.xterm-cursor-pointer { +.xterm.xterm-cursor-pointer, +.xterm .xterm-cursor-pointer { cursor: pointer; } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 23122f3f..6ab17060 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -537,7 +537,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._mouseZoneManager); this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier.attachToDom(this.element, this._mouseZoneManager); - this.linkifier2.attachToDom(this.element, this._mouseService, this._renderService); + this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 18bc0b95..40583628 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1265,12 +1265,12 @@ declare module 'xterm' { */ interface IBufferCellPosition { /** - * The x position within the buffer. + * The x position within the buffer (1-based). */ x: number; /** - * The y position within the buffer. + * The y position within the buffer (1-based). */ y: number; } From 65712980614eac68bed4b03040e3a13bf86ff673 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 20 Dec 2021 10:02:49 -0800 Subject: [PATCH 25/27] Mark IKeyboardEvent.keyCode as deprecated --- src/common/Types.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 88497e4a..3af7e2dc 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -46,6 +46,7 @@ export interface IKeyboardEvent { ctrlKey: boolean; shiftKey: boolean; metaKey: boolean; + /** @deprecated See KeyboardEvent.keyCode */ keyCode: number; key: string; type: string; From 7535d2e527697b4c223280ed6e3a2256400961d3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Dec 2021 09:13:53 -0800 Subject: [PATCH 26/27] Move options test to Terminal.api.ts --- src/browser/public/Terminal.test.ts | 43 ----------------------------- test/api/Terminal.api.ts | 31 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 43 deletions(-) delete mode 100644 src/browser/public/Terminal.test.ts diff --git a/src/browser/public/Terminal.test.ts b/src/browser/public/Terminal.test.ts deleted file mode 100644 index f03945a0..00000000 --- a/src/browser/public/Terminal.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Terminal } from 'browser/public/Terminal'; -import { assert } from 'chai'; -import { ITerminalOptions } from 'common/Types'; - -const INIT_COLS = 80; -const INIT_ROWS = 24; - -describe('Public Terminal', () => { - let term: Terminal; - const termOptions = { - cols: INIT_COLS, - rows: INIT_ROWS - }; - - describe('options', () => { - beforeEach(async () => { - term = new Terminal(termOptions); - }); - it('get options', () => { - const options: ITerminalOptions = term.options; - assert.equal(options.cols, 80); - assert.equal(options.rows, 24); - }); - it('set options', async () => { - const options: ITerminalOptions = term.options; - assert.throws(() => options.cols = 40); - assert.throws(() => options.rows = 20); - term.options.scrollback = 1; - assert.equal(term.options.scrollback, 1); - term.options= { - fontSize: 12, - fontFamily: 'Arial' - }; - assert.equal(term.options.fontSize, 12); - assert.equal(term.options.fontFamily, 'Arial'); - }); - }); -}); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 00598e47..7243e617 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -6,6 +6,7 @@ import { assert } from 'chai'; import { pollFor, timeout, writeSync, openTerminal, launchBrowser } from './TestUtils'; import { Browser, Page } from 'playwright'; +import { fail } from 'assert'; const APP = 'http://127.0.0.1:3001/test'; @@ -160,6 +161,36 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); }); + describe('options', () => { + it('getter', async () => { + await openTerminal(page); + assert.equal(await page.evaluate(`window.term.options.rendererType`), 'canvas'); + assert.equal(await page.evaluate(`window.term.options.cols`), 80); + assert.equal(await page.evaluate(`window.term.options.rows`), 24); + }); + it('setter', async () => { + await openTerminal(page); + try { + await page.evaluate('window.term.options.cols = 40'); + fail(); + } catch {} + try { + await page.evaluate('window.term.options.rows = 20'); + fail(); + } catch {} + await page.evaluate('window.term.options.scrollback = 1'); + assert.equal(await page.evaluate(`window.term.options.scrollback`), 1); + await page.evaluate(` + window.term.options = { + fontSize: 30, + fontFamily: 'Arial' + }; + `); + assert.equal(await page.evaluate(`window.term.options.fontSize`), 30); + assert.equal(await page.evaluate(`window.term.options.fontFamily`), 'Arial'); + }); + }); + describe('renderer', () => { it('foreground', async () => { await openTerminal(page, { rendererType: 'dom' }); From 8c94c97a283e816242523f5d723a1c567aa7f3fe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 21 Dec 2021 10:49:58 -0800 Subject: [PATCH 27/27] Don't include trailing EOL when selecting multiple lines Fixes #3552 --- src/browser/selection/SelectionModel.test.ts | 8 ++++++++ src/browser/selection/SelectionModel.ts | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/browser/selection/SelectionModel.test.ts b/src/browser/selection/SelectionModel.test.ts index c15cdd29..410902d7 100644 --- a/src/browser/selection/SelectionModel.test.ts +++ b/src/browser/selection/SelectionModel.test.ts @@ -122,5 +122,13 @@ describe('SelectionModel', () => { model.selectionEnd = [5, 2]; assert.deepEqual(model.finalSelectionEnd, [5, 2]); }); + it('should not include a trailing EOL when the selection ends at the end of a line', () => { + model.selectionStart = [0, 0]; + model.selectionStartLength = 80; + assert.deepEqual(model.finalSelectionEnd, [80, 0]); + model.selectionStart = [0, 0]; + model.selectionStartLength = 160; + assert.deepEqual(model.finalSelectionEnd, [80, 1]); + }); }); }); diff --git a/src/browser/selection/SelectionModel.ts b/src/browser/selection/SelectionModel.ts index 1420444e..1d84446a 100644 --- a/src/browser/selection/SelectionModel.ts +++ b/src/browser/selection/SelectionModel.ts @@ -79,6 +79,10 @@ export class SelectionModel { if (!this.selectionEnd || this.areSelectionValuesReversed()) { const startPlusLength = this.selectionStart[0] + this.selectionStartLength; if (startPlusLength > this._bufferService.cols) { + // Ensure the trailing EOL isn't included when the selection ends on the right edge + if (startPlusLength % this._bufferService.cols === 0) { + return [this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols) - 1]; + } return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)]; } return [startPlusLength, this.selectionStart[1]];