From bec4f6d5c7fa4a824cbc83630b1381494d1ffea5 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Mon, 1 Nov 2021 15:09:25 +0000 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 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 4/6] 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 5/6] 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 6/6] 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;