From 6f886fe1672cfca93f76516de76fb9cf54db8c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 16 Feb 2020 20:41:55 +0100 Subject: [PATCH 1/9] working draft --- src/InputHandler.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index fb37f4ab1f..863fea1bab 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -717,9 +717,19 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left." */ public backspace(): void { - this._restrictCursor(); - if (this._bufferService.buffer.x > 0) { - this._bufferService.buffer.x--; + //this._restrictCursor(); + const buffer = this._bufferService.buffer; + if (buffer.x > 0) { + buffer.x--; + } else { + // reverse wraparound + if (buffer.x === 0 && buffer.y && buffer.lines.get(buffer.y + buffer.ybase).isWrapped) { + buffer.y--; + buffer.x = this._bufferService.cols - 1; + } + } + if (buffer.x >= this._bufferService.cols) { + buffer.x = this._bufferService.cols - 1; } } From f736b6c29980dcd64667b2add037292bd4b77c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 16 Feb 2020 20:53:53 +0100 Subject: [PATCH 2/9] comment missing empty cell handling --- src/InputHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 863fea1bab..605e97f120 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -726,6 +726,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (buffer.x === 0 && buffer.y && buffer.lines.get(buffer.y + buffer.ybase).isWrapped) { buffer.y--; buffer.x = this._bufferService.cols - 1; + // TODO: skip last col if it is empty after wide char } } if (buffer.x >= this._bufferService.cols) { From 0c703276b18ef3e812cc9a3f7d74d4eabf8c6fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Feb 2020 12:22:53 +0100 Subject: [PATCH 3/9] fix behavior, first BS tests --- src/InputHandler.test.ts | 57 ++++++++++++++++++++++++++++++ src/InputHandler.ts | 55 +++++++++++++++++++++++----- src/common/TestUtils.test.ts | 1 + src/common/Types.d.ts | 1 + src/common/services/CoreService.ts | 1 + 5 files changed, 107 insertions(+), 8 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index de43022b15..7bac8d0b0d 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1445,4 +1445,61 @@ describe('InputHandler', () => { assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); }); }); + describe('BS with reverseWraparound set/unset', () => { + const ttyBS = '\x08 \x08'; // tty ICANON sends on pressing BS + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); + }); + describe('reverseWraparound unset (default)', () => { + it('cannot delete last cell', () => { + term.writeSync('12345'); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 1), ['123 5']); + term.writeSync(ttyBS.repeat(10)); + assert.deepEqual(getLines(term, 1), [' 5']); + }); + it('cannot access prev line', () => { + term.writeSync('12345'.repeat(2)); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 2), ['12345', '123 5']); + term.writeSync(ttyBS.repeat(10)); + assert.deepEqual(getLines(term, 2), ['12345', ' 5']); + }); + }); + describe('reverseWraparound set', () => { + it('can delete last cell', () => { + term.writeSync('\x1b[?45h'); + term.writeSync('12345'); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 1), ['1234 ']); + term.writeSync(ttyBS.repeat(7)); + assert.deepEqual(getLines(term, 1), [' ']); + }); + it('can access prev line if wrapped', () => { + term.writeSync('\x1b[?45h'); + term.writeSync('12345'.repeat(2)); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 2), ['12345', '1234 ']); + term.writeSync(ttyBS.repeat(7)); + assert.deepEqual(getLines(term, 2), ['12 ', ' ']); + }); + it('should lift isWrapped', () => { + term.writeSync('\x1b[?45h'); + term.writeSync('12345'.repeat(2)); + assert.equal(term.buffer.lines.get(1).isWrapped, true); + term.writeSync(ttyBS.repeat(7)); + assert.equal(term.buffer.lines.get(1).isWrapped, false); + }); + it('stops at hard NLs', () => { + term.writeSync('\x1b[?45h'); + term.writeSync('12345\r\n'); + term.writeSync('12345'.repeat(2)); + term.writeSync(ttyBS.repeat(50)); + assert.deepEqual(getLines(term, 3), ['12345', ' ', ' ']); + assert.equal(term.buffer.x, 0); + assert.equal(term.buffer.y, 1); + }); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 605e97f120..9963b96e15 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -544,6 +544,10 @@ export class InputHandler extends Disposable implements IInputHandler { // autowrap - DECAWM // automatically wraps to the beginning of the next line if (wraparoundMode) { + // clear left over cells to the right + while (buffer.x < cols) { + bufferRow.setCellFromCodePoint(buffer.x++, 0, 1, curAttr.fg, curAttr.bg); + } buffer.x = 0; buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { @@ -717,21 +721,48 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left." */ public backspace(): void { - //this._restrictCursor(); + // xterm allows deleting of the last col if reverseWraparound is set + // otherwise the last cell cannot be deleted by BS + if (!this._coreService.decPrivateModes.reverseWraparound) { + this._restrictCursor(); + } else { + // we still clamp the cursor to sane values here, but have to allow x=cols as well + this._bufferService.buffer.x = Math.min(this._bufferService.cols, Math.max(0, this._bufferService.buffer.x)); + this._bufferService.buffer.y = this._coreService.decPrivateModes.origin + ? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y)) + : Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y)); + this._dirtyRowService.markDirty(this._bufferService.buffer.y); + } const buffer = this._bufferService.buffer; if (buffer.x > 0) { buffer.x--; } else { // reverse wraparound - if (buffer.x === 0 && buffer.y && buffer.lines.get(buffer.y + buffer.ybase).isWrapped) { - buffer.y--; - buffer.x = this._bufferService.cols - 1; - // TODO: skip last col if it is empty after wide char + // TODO: test in xterm - should we peek/unshift from scrollback if y == 0? + if (this._coreService.decPrivateModes.reverseWraparound + && buffer.x === 0 + && buffer.y > 0 + && buffer.lines.get(buffer.y + buffer.ybase).isWrapped) + { + // unset isWrapped + buffer.lines.get(buffer.y + buffer.ybase).isWrapped = false; + buffer.y--; + buffer.x = this._bufferService.cols - 1; + // find last taken cell - last cell can have 3 different states: + // - hasContent(true) + hasWidth(1): narrow char - we are done + // - hasWidth(0): second part of wide char - we are done + // - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one cell further back + const line = buffer.lines.get(buffer.y + buffer.ybase); + if (line.hasWidth(buffer.x) && !line.hasContent(buffer.x)) { + buffer.x--; + // We do this only once, since width=1 + hasContent=false currently happens only once before + // early wrapping of a wide char. + // This needs to be fixed once we support graphemes taking more than 2 cells. } + } } - if (buffer.x >= this._bufferService.cols) { - buffer.x = this._bufferService.cols - 1; - } + // should we use _restrictCursor() here? FIXME: how about origin mode / scroll margins? + buffer.x = Math.max(Math.min(buffer.x, this._bufferService.cols - 1), 0); } /** @@ -1710,6 +1741,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 9 | X10 xterm mouse protocol. | #Y | * | 12 | Start Blinking Cursor. | #Y | * | 25 | Show Cursor (DECTCEM). | #Y | + * | 45 | Reverse wrap-around. | #Y | * | 47 | Use Alternate Screen Buffer. | #Y | * | 66 | Application keypad (DECNKM). | #Y | * | 1000 | X11 xterm mouse protocol. | #Y | @@ -1761,6 +1793,9 @@ export class InputHandler extends Disposable implements IInputHandler { case 12: // this.cursorBlink = true; break; + case 45: + this._coreService.decPrivateModes.reverseWraparound = true; + break; case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; @@ -1944,6 +1979,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 9 | Don't send Mouse X & Y on button press. | #Y | * | 12 | Stop Blinking Cursor. | #Y | * | 25 | Hide Cursor (DECTCEM). | #Y | + * | 45 | No reverse wrap-around. | #Y | * | 47 | Use Normal Screen Buffer. | #Y | * | 66 | Numeric keypad (DECNKM). | #Y | * | 1000 | Don't send Mouse reports. | #Y | @@ -1988,6 +2024,9 @@ export class InputHandler extends Disposable implements IInputHandler { case 12: // this.cursorBlink = false; break; + case 45: + this._coreService.decPrivateModes.reverseWraparound = false; + break; case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 4d9070cd8e..fce5ee6e89 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -62,6 +62,7 @@ export class MockCoreService implements ICoreService { applicationCursorKeys: false, applicationKeypad: false, origin: false, + reverseWraparound: false, wraparound: true }; onData: IEvent = new EventEmitter().event; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index b250b45eac..d8a6b4d5e0 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -155,6 +155,7 @@ export interface IDecPrivateModes { applicationCursorKeys: boolean; applicationKeypad: boolean; origin: boolean; + reverseWraparound: boolean; wraparound: boolean; // defaults: xterm - true, vt100 - false } diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index df9161e9c7..bc58a688dc 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -12,6 +12,7 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ applicationCursorKeys: false, applicationKeypad: false, origin: false, + reverseWraparound: false, wraparound: true // defaults: xterm - true, vt100 - false }); From 7de600cc64d75cf24afa5d8b6815e5e2f775214b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Feb 2020 12:30:35 +0100 Subject: [PATCH 4/9] test case for clearing cells to the right on early wrap-around --- src/InputHandler.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 7bac8d0b0d..645a905db2 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -372,6 +372,13 @@ describe('InputHandler', () => { container[0] = 0x200B; inputHandler.print(container, 0, 1); }); + it('should clear cells to the right on early wrap-around', () => { + const term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); + term.writeSync('12345'); + term.buffer.x = 0; + term.writeSync('¥¥¥'); + assert.deepEqual(getLines(term, 2), ['¥¥', '¥']); + }); }); describe('alt screen', () => { From 62ea2c691e9a0e288bd3733f9e3c7f2b97dbf48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 17 Feb 2020 12:39:36 +0100 Subject: [PATCH 5/9] wide char tests --- src/InputHandler.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 645a905db2..6b98d61e83 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1507,6 +1507,29 @@ describe('InputHandler', () => { assert.equal(term.buffer.x, 0); assert.equal(term.buffer.y, 1); }); + it('handles wide chars correctly', () => { + term.writeSync('\x1b[?45h'); + term.writeSync('¥¥¥'); + assert.deepEqual(getLines(term, 2), ['¥¥', '¥']); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 2), ['¥¥', ' ']); + assert.equal(term.buffer.x, 1); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 2), ['¥¥', ' ']); + assert.equal(term.buffer.x, 0); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 2), ['¥ ', ' ']); + assert.equal(term.buffer.x, 3); // x=4 skipped due to early wrap-around + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 2), ['¥ ', ' ']); + assert.equal(term.buffer.x, 2); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 2), [' ', ' ']); + assert.equal(term.buffer.x, 1); + term.writeSync(ttyBS); + assert.deepEqual(getLines(term, 2), [' ', ' ']); + assert.equal(term.buffer.x, 0); + }); }); }); }); From cb31179274cfa891ebf7c4e1a2c57ca839dc8506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 21 Feb 2020 18:23:00 +0100 Subject: [PATCH 6/9] respect scroll margins, tests --- src/InputHandler.test.ts | 37 ++++++++++++++++++++++++++++++++++ src/InputHandler.ts | 43 ++++++++++++++++++++++++++-------------- src/Terminal2.test.ts | 2 +- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 6b98d61e83..aae042f6dc 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1530,6 +1530,43 @@ describe('InputHandler', () => { assert.deepEqual(getLines(term, 2), [' ', ' ']); assert.equal(term.buffer.x, 0); }); + it('should not reverse outside of scroll margins', () => { + // prepare buffer content + term.writeSync('#####abcdefghijklmnopqrstuvwxy'); + assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); + assert.equal(term.buffer.ydisp, 1); + assert.equal(term.buffer.x, 5); + assert.equal(term.buffer.y, 4); + term.writeSync(ttyBS.repeat(100)); + assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']); + + term.writeSync('\x1b[?45h'); + term.writeSync('uvwxy'); + + // set top/bottom to 1/3 (0-based) + term.writeSync('\x1b[2;4r'); + // place cursor below scroll bottom + term.buffer.x = 5; + term.buffer.y = 4; + term.writeSync(ttyBS.repeat(100)); + assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']); + + term.writeSync('uvwxy'); + // place cursor within scroll margins + term.buffer.x = 5; + term.buffer.y = 3; + term.writeSync(ttyBS.repeat(100)); + assert.deepEqual(getLines(term, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']); + assert.equal(term.buffer.x, 0); + assert.equal(term.buffer.y, term.buffer.scrollTop); // stops at 0, scrollTop + + term.writeSync('fghijklmnopqrst'); + // place cursor above scroll top + term.buffer.x = 5; + term.buffer.y = 0; + term.writeSync(ttyBS.repeat(100)); + assert.deepEqual(getLines(term, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']); + }); }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9963b96e15..d6e33c2ca0 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -719,32 +719,46 @@ export class InputHandler extends Disposable implements IInputHandler { * Backspace (Ctrl-H). * * @vt: #Y C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left." + * By default it is not possible to move the cursor past the leftmost position. + * If `reverse wrap-around` (`CSI ? 45 h`) is set, a previous soft line wrap (DECAWM) + * can be undone with BS within the scroll margins. In that case the cursor will wrap back + * to the end of the previous row. Note that it is not possible to peek back into the scrollbuffer + * with the cursor, thus at the home position (top-leftmost cell) this has no effect. */ public backspace(): void { - // xterm allows deleting of the last col if reverseWraparound is set - // otherwise the last cell cannot be deleted by BS + const buffer = this._bufferService.buffer; + + // by default (resverse wrap-around not set) we restrict the cursor to 0 .. rows/cols - 1 + // thus the last cell of a row cannot be accessed by BS if (!this._coreService.decPrivateModes.reverseWraparound) { this._restrictCursor(); } else { - // we still clamp the cursor to sane values here, but have to allow x=cols as well - this._bufferService.buffer.x = Math.min(this._bufferService.cols, Math.max(0, this._bufferService.buffer.x)); - this._bufferService.buffer.y = this._coreService.decPrivateModes.origin - ? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y)) - : Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y)); - this._dirtyRowService.markDirty(this._bufferService.buffer.y); + // copy of _restrictCursor(), but with x=cols as allowed max cursor position + // to make last cell accessible by BS + buffer.x = Math.min(this._bufferService.cols, Math.max(0, this._bufferService.buffer.x)); + buffer.y = this._coreService.decPrivateModes.origin + ? Math.min(buffer.scrollBottom, Math.max(buffer.scrollTop, buffer.y)) + : Math.min(this._bufferService.rows - 1, Math.max(0, buffer.y)); + this._dirtyRowService.markDirty(buffer.y); } - const buffer = this._bufferService.buffer; + if (buffer.x > 0) { buffer.x--; } else { - // reverse wraparound - // TODO: test in xterm - should we peek/unshift from scrollback if y == 0? + /** + * reverse wrap-around: + * Our implementation deviates from xterm on purpose. Details: + * - only previous soft NLs can be reversed (isWrapped=true) + * - only works within scrollborders (top/bottom, left/right not yet supported) + * - cannot peek into scrollbuffer + * - any cursor movement sequence keeps working as expected + */ if (this._coreService.decPrivateModes.reverseWraparound && buffer.x === 0 - && buffer.y > 0 + && buffer.y > buffer.scrollTop + && buffer.y <= buffer.scrollBottom && buffer.lines.get(buffer.y + buffer.ybase).isWrapped) { - // unset isWrapped buffer.lines.get(buffer.y + buffer.ybase).isWrapped = false; buffer.y--; buffer.x = this._bufferService.cols - 1; @@ -761,8 +775,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } } - // should we use _restrictCursor() here? FIXME: how about origin mode / scroll margins? - buffer.x = Math.max(Math.min(buffer.x, this._bufferService.cols - 1), 0); + this._restrictCursor(); } /** diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index f7046240bd..ff7ed88085 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -19,7 +19,7 @@ const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__ const SKIP_FILES = [ 't0084-CBT.in', 't0101-NLM.in', - 't0103-reverse_wrap.in', + 't0103-reverse_wrap.in', // not comparable, we deviate from xterm reverse wrap on purpose 't0504-vim.in' ]; if (os.platform() === 'darwin') { From 51a808c094934601042da758626e99dca9395b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 26 Apr 2020 16:34:15 +0200 Subject: [PATCH 7/9] some refactoring --- src/InputHandler.ts | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 1274540515..f0962ecdc9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -728,45 +728,44 @@ export class InputHandler extends Disposable implements IInputHandler { public backspace(): void { const buffer = this._bufferService.buffer; - // by default (resverse wrap-around not set) we restrict the cursor to 0 .. rows/cols - 1 - // thus the last cell of a row cannot be accessed by BS + // reverse wrap-around is disabled if (!this._coreService.decPrivateModes.reverseWraparound) { this._restrictCursor(); - } else { - // copy of _restrictCursor(), but with x=cols as allowed max cursor position - // to make last cell accessible by BS - buffer.x = Math.min(this._bufferService.cols, Math.max(0, this._bufferService.buffer.x)); - buffer.y = this._coreService.decPrivateModes.origin - ? Math.min(buffer.scrollBottom, Math.max(buffer.scrollTop, buffer.y)) - : Math.min(this._bufferService.rows - 1, Math.max(0, buffer.y)); - this._dirtyRowService.markDirty(buffer.y); + if (buffer.x > 0) { + buffer.x--; + } + return; } + // reverse wrap-around is enabled + // other than for normal operation mode, reverse wrap-around allows the cursor + // to be at x=cols to be able to address the last cell of a row by BS + this._restrictCursor(this._bufferService.cols); + if (buffer.x > 0) { buffer.x--; } else { /** - * reverse wrap-around: + * reverse wrap-around handling: * Our implementation deviates from xterm on purpose. Details: * - only previous soft NLs can be reversed (isWrapped=true) * - only works within scrollborders (top/bottom, left/right not yet supported) * - cannot peek into scrollbuffer * - any cursor movement sequence keeps working as expected */ - if (this._coreService.decPrivateModes.reverseWraparound - && buffer.x === 0 + if (buffer.x === 0 && buffer.y > buffer.scrollTop && buffer.y <= buffer.scrollBottom - && buffer.lines.get(buffer.y + buffer.ybase).isWrapped) + && buffer.lines.get(buffer.ybase + buffer.y).isWrapped) { - buffer.lines.get(buffer.y + buffer.ybase).isWrapped = false; + buffer.lines.get(buffer.ybase + buffer.y).isWrapped = false; buffer.y--; buffer.x = this._bufferService.cols - 1; // find last taken cell - last cell can have 3 different states: // - hasContent(true) + hasWidth(1): narrow char - we are done // - hasWidth(0): second part of wide char - we are done // - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one cell further back - const line = buffer.lines.get(buffer.y + buffer.ybase); + const line = buffer.lines.get(buffer.ybase + buffer.y); if (line.hasWidth(buffer.x) && !line.hasContent(buffer.x)) { buffer.x--; // We do this only once, since width=1 + hasContent=false currently happens only once before @@ -820,8 +819,8 @@ export class InputHandler extends Disposable implements IInputHandler { /** * Restrict cursor to viewport size / scroll margin (origin mode). */ - private _restrictCursor(): void { - this._bufferService.buffer.x = Math.min(this._bufferService.cols - 1, Math.max(0, this._bufferService.buffer.x)); + private _restrictCursor(maxCol: number = this._bufferService.cols - 1): void { + this._bufferService.buffer.x = Math.min(maxCol, Math.max(0, this._bufferService.buffer.x)); this._bufferService.buffer.y = this._coreService.decPrivateModes.origin ? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y)) : Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y)); From 0d0f04de5cab57e8ac460352df71171d0eff4fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 27 Apr 2020 16:16:31 +0200 Subject: [PATCH 8/9] resolve master conflicts --- src/InputHandler.test.ts | 1621 -------------------------------------- 1 file changed, 1621 deletions(-) delete mode 100644 src/InputHandler.test.ts diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts deleted file mode 100644 index 53ce80aa64..0000000000 --- a/src/InputHandler.test.ts +++ /dev/null @@ -1,1621 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert, expect } from 'chai'; -import { InputHandler } from './InputHandler'; -import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; -import { Terminal } from './Terminal'; -import { IBufferLine, IAttributeData } from 'common/Types'; -import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { CellData } from 'common/buffer/CellData'; -import { Attributes } from 'common/buffer/Constants'; -import { AttributeData } from 'common/buffer/AttributeData'; -import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService } from 'common/TestUtils.test'; -import { IBufferService } from 'common/services/Services'; -import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; -import { clone } from 'common/Clone'; - -function getCursor(term: TestTerminal): number[] { - return [ - term.buffer.x, - term.buffer.y - ]; -} - -function getLines(term: TestTerminal, limit: number = term.rows): string[] { - const res: string[] = []; - for (let i = 0; i < limit; ++i) { - res.push(term.buffer.lines.get(i).translateToString(true)); - } - return res; -} - -class TestInputHandler extends InputHandler { - public get curAttrData(): IAttributeData { return (this as any)._curAttrData; } -} - -describe('InputHandler', () => { - describe('save and restore cursor', () => { - const terminal = new MockInputHandlingTerminal(); - const bufferService = new MockBufferService(80, 30); - bufferService.buffer.x = 1; - bufferService.buffer.y = 2; - bufferService.buffer.ybase = 0; - const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - inputHandler.curAttrData.fg = 3; - // Save cursor position - inputHandler.saveCursor(); - assert.equal(bufferService.buffer.x, 1); - assert.equal(bufferService.buffer.y, 2); - assert.equal(inputHandler.curAttrData.fg, 3); - // Change cursor position - bufferService.buffer.x = 10; - bufferService.buffer.y = 20; - inputHandler.curAttrData.fg = 30; - // Restore cursor position - inputHandler.restoreCursor(); - assert.equal(bufferService.buffer.x, 1); - assert.equal(bufferService.buffer.y, 2); - assert.equal(inputHandler.curAttrData.fg, 3); - }); - describe('setCursorStyle', () => { - it('should call Terminal.setOption with correct params', () => { - const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - inputHandler.setCursorStyle(Params.fromArray([0])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], true); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([1])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], true); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([2])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], false); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([3])); - assert.equal(optionsService.options['cursorStyle'], 'underline'); - assert.equal(optionsService.options['cursorBlink'], true); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([4])); - assert.equal(optionsService.options['cursorStyle'], 'underline'); - assert.equal(optionsService.options['cursorBlink'], false); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([5])); - assert.equal(optionsService.options['cursorStyle'], 'bar'); - assert.equal(optionsService.options['cursorBlink'], true); - - optionsService.options = clone(DEFAULT_OPTIONS); - inputHandler.setCursorStyle(Params.fromArray([6])); - assert.equal(optionsService.options['cursorStyle'], 'bar'); - assert.equal(optionsService.options['cursorBlink'], false); - }); - }); - describe('setMode', () => { - it('should toggle Terminal.bracketedPasteMode', () => { - const terminal = new MockInputHandlingTerminal(); - terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - // Set bracketed paste mode - inputHandler.setModePrivate(Params.fromArray([2004])); - assert.equal(terminal.bracketedPasteMode, true); - // Reset bracketed paste mode - inputHandler.resetModePrivate(Params.fromArray([2004])); - assert.equal(terminal.bracketedPasteMode, false); - }); - }); - describe('regression tests', function(): void { - function termContent(bufferService: IBufferService, trim: boolean): string[] { - const result = []; - for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i).translateToString(trim)); - return result; - } - - it('insertChars', function(): void { - const term = new Terminal(); - const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - // insert some data in first and second line - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - const line1: IBufferLine = bufferService.buffer.lines.get(0); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); - - // insert one char from params = [0] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.insertChars(Params.fromArray([0])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 123456789'); - - // insert one char from params = [1] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.insertChars(Params.fromArray([1])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 12345678'); - - // insert two chars from params = [2] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.insertChars(Params.fromArray([2])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' 123456'); - - // insert 10 chars from params = [10] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.insertChars(Params.fromArray([10])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); - }); - it('deleteChars', function(): void { - const term = new Terminal(); - const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - // insert some data in first and second line - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - inputHandler.parse(Array(bufferService.cols - 9).join('a')); - inputHandler.parse('1234567890'); - const line1: IBufferLine = bufferService.buffer.lines.get(0); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); - - // delete one char from params = [0] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.deleteChars(Params.fromArray([0])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '234567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '234567890'); - - // insert one char from params = [1] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.deleteChars(Params.fromArray([1])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '34567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '34567890'); - - // insert two chars from params = [2] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.deleteChars(Params.fromArray([2])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '567890 '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a') + '567890'); - - // insert 10 chars from params = [10] - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.deleteChars(Params.fromArray([10])); - expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + ' '); - expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); - }); - it('eraseInLine', function(): void { - const term = new Terminal(); - const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - // fill 6 lines to test 3 different states - inputHandler.parse(Array(bufferService.cols + 1).join('a')); - inputHandler.parse(Array(bufferService.cols + 1).join('a')); - inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params[0] - right erase - bufferService.buffer.y = 0; - bufferService.buffer.x = 70; - inputHandler.eraseInLine(Params.fromArray([0])); - expect(bufferService.buffer.lines.get(0).translateToString(false)).equals(Array(71).join('a') + ' '); - - // params[1] - left erase - bufferService.buffer.y = 1; - bufferService.buffer.x = 70; - inputHandler.eraseInLine(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(1).translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa'); - - // params[1] - left erase - bufferService.buffer.y = 2; - bufferService.buffer.x = 70; - inputHandler.eraseInLine(Params.fromArray([2])); - expect(bufferService.buffer.lines.get(2).translateToString(false)).equals(Array(bufferService.cols + 1).join(' ')); - - }); - it('eraseInDisplay', function(): void { - const term = new Terminal({cols: 80, rows: 7}); - const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - - // fill display with a's - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params [0] - right and below erase - bufferService.buffer.y = 5; - bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([0])); - expect(termContent(bufferService, false)).eql([ - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(40 + 1).join('a') + Array(bufferService.cols - 40 + 1).join(' '), - Array(bufferService.cols + 1).join(' ') - ]); - expect(termContent(bufferService, true)).eql([ - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(bufferService.cols + 1).join('a'), - Array(40 + 1).join('a'), - '' - ]); - - // reset - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params [1] - left and above - bufferService.buffer.y = 5; - bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(termContent(bufferService, false)).eql([ - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'), - Array(bufferService.cols + 1).join('a') - ]); - expect(termContent(bufferService, true)).eql([ - '', - '', - '', - '', - '', - Array(41 + 1).join(' ') + Array(bufferService.cols - 41 + 1).join('a'), - Array(bufferService.cols + 1).join('a') - ]); - - // reset - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params [2] - whole screen - bufferService.buffer.y = 5; - bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([2])); - expect(termContent(bufferService, false)).eql([ - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' '), - Array(bufferService.cols + 1).join(' ') - ]); - expect(termContent(bufferService, true)).eql([ - '', - '', - '', - '', - '', - '', - '' - ]); - - // reset and add a wrapped line - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0 - inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2 - for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params[1] left and above with wrap - // confirm precondition that line 2 is wrapped - expect(bufferService.buffer.lines.get(2).isWrapped).true; - bufferService.buffer.y = 2; - bufferService.buffer.x = 40; - inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(2).isWrapped).false; - - // reset and add a wrapped line - bufferService.buffer.y = 0; - bufferService.buffer.x = 0; - inputHandler.parse(Array(bufferService.cols + 1).join('a')); // line 0 - inputHandler.parse(Array(bufferService.cols + 10).join('a')); // line 1 and 2 - for (let i = 3; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); - - // params[1] left and above with wrap - // confirm precondition that line 2 is wrapped - expect(bufferService.buffer.lines.get(2).isWrapped).true; - bufferService.buffer.y = 1; - bufferService.buffer.x = 90; // Cursor is beyond last column - inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(2).isWrapped).false; - }); - }); - it('convertEol setting', function(): void { - // not converting - const termNotConverting = new Terminal({cols: 15, rows: 10}); - (termNotConverting as any)._inputHandler.parse('Hello\nWorld'); - expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); - expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World '); - expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); - expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World'); - - // converting - const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true}); - (termConverting as any)._inputHandler.parse('Hello\nWorld'); - expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); - expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World '); - expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); - expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World'); - }); - describe('print', () => { - it('should not cause an infinite loop (regression test)', () => { - const term = new Terminal(); - const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - const container = new Uint32Array(10); - container[0] = 0x200B; - inputHandler.print(container, 0, 1); - }); - it('should clear cells to the right on early wrap-around', () => { - const term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); - term.writeSync('12345'); - term.buffer.x = 0; - term.writeSync('¥¥¥'); - assert.deepEqual(getLines(term, 2), ['¥¥', '¥']); - }); - }); - - describe('alt screen', () => { - let term: Terminal; - let bufferService: IBufferService; - let handler: InputHandler; - - beforeEach(() => { - term = new Terminal(); - bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); - }); - it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { - handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); - // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1); - }); - it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => { - handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); - // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1); - }); - it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { - handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('JUNK'); - // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); - // Text color of 'JUNK' should be red - expect((bufferService.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1); - }); - it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { - handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(''); - // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); - }); - it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => { - handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); - expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); - // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); - handler.parse('\x1b[?1049h\x1b[uTEST'); - expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('TEST'); - // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1); - }); - it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => { - handler.parse('\x1b[42m\x1b[?1049h'); - // Buffer should be filled with green background - expect(bufferService.buffer.lines.get(20).loadCell(10, new CellData()).getBgColor()).to.equal(2); - }); - }); - - describe('text attributes', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal(); - }); - it('bold', () => { - term.writeSync('\x1b[1m'); - assert.equal(!!term.curAttrData.isBold(), true); - term.writeSync('\x1b[22m'); - assert.equal(!!term.curAttrData.isBold(), false); - }); - it('dim', () => { - term.writeSync('\x1b[2m'); - assert.equal(!!term.curAttrData.isDim(), true); - term.writeSync('\x1b[22m'); - assert.equal(!!term.curAttrData.isDim(), false); - }); - it('italic', () => { - term.writeSync('\x1b[3m'); - assert.equal(!!term.curAttrData.isItalic(), true); - term.writeSync('\x1b[23m'); - assert.equal(!!term.curAttrData.isItalic(), false); - }); - it('underline', () => { - term.writeSync('\x1b[4m'); - assert.equal(!!term.curAttrData.isUnderline(), true); - term.writeSync('\x1b[24m'); - assert.equal(!!term.curAttrData.isUnderline(), false); - }); - it('blink', () => { - term.writeSync('\x1b[5m'); - assert.equal(!!term.curAttrData.isBlink(), true); - term.writeSync('\x1b[25m'); - assert.equal(!!term.curAttrData.isBlink(), false); - }); - it('inverse', () => { - term.writeSync('\x1b[7m'); - assert.equal(!!term.curAttrData.isInverse(), true); - term.writeSync('\x1b[27m'); - assert.equal(!!term.curAttrData.isInverse(), false); - }); - it('invisible', () => { - term.writeSync('\x1b[8m'); - assert.equal(!!term.curAttrData.isInvisible(), true); - term.writeSync('\x1b[28m'); - assert.equal(!!term.curAttrData.isInvisible(), false); - }); - it('colormode palette 16', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT - // lower 8 colors - for (let i = 0; i < 8; ++i) { - term.writeSync(`\x1b[${i + 30};${i + 40}m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), i); - } - // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getBgColorMode(), 0); - }); - it('colormode palette 256', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT - // lower 8 colors - for (let i = 0; i < 256; ++i) { - term.writeSync(`\x1b[38;5;${i};48;5;${i}m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), i); - } - // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getBgColorMode(), 0); - assert.equal(term.curAttrData.getBgColor(), -1); - }); - it('colormode RGB', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_RGB); - assert.equal(term.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [1, 2, 3]); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_RGB); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getBgColor()), [4, 5, 6]); - // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getBgColorMode(), 0); - assert.equal(term.curAttrData.getBgColor(), -1); - }); - it('colormode transition RGB to 256', () => { - // enter RGB for FG and BG - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), 255); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), 255); - }); - it('colormode transition RGB to 16', () => { - // enter RGB for FG and BG - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), 7); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), 7); - }); - it('colormode transition 16 to 256', () => { - // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); - // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), 255); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), 255); - }); - it('colormode transition 256 to 16', () => { - // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); - // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), 7); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), 7); - }); - it('should zero missing RGB values', () => { - term.writeSync(`\x1b[38;2;1;2;3m`); - term.writeSync(`\x1b[38;2;5m`); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]); - }); - }); - describe('colon notation', () => { - let termColon: TestTerminal; - let termSemicolon: TestTerminal; - beforeEach(() => { - termColon = new TestTerminal(); - termSemicolon = new TestTerminal(); - }); - describe('should equal to semicolon', () => { - it('CSI 38:2::50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38:2::50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:2::50:100: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;m'); - termColon.writeSync('\x1b[38:2::50:100:m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:2::50:: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;;m'); - termColon.writeSync('\x1b[38:2::50::m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:2:::: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;;;m'); - termColon.writeSync('\x1b[38:2::::m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38;2::50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2::50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38;2;50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2;50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38;2;50;100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2;50;100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:5:50 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;50m'); - termColon.writeSync('\x1b[38:5:50m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:5: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;m'); - termColon.writeSync('\x1b[38:5:m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38;5:50 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;50m'); - termColon.writeSync('\x1b[38;5:50m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - }); - describe('should fill early sequence end with default of 0', () => { - it('CSI 38:2 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2m'); - termColon.writeSync('\x1b[38:2m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 38:5 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5m'); - termColon.writeSync('\x1b[38:5m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - }); - describe('should not interfere with leading/following SGR attrs', () => { - it('CSI 1 ; 38:2::50:100:150 ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;150;4m'); - termColon.writeSync('\x1b[1;38:2::50:100:150;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 1 ; 38:2::50:100: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m'); - termColon.writeSync('\x1b[1;38:2::50:100:;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 1 ; 38:2::50:100 ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m'); - termColon.writeSync('\x1b[1;38:2::50:100;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 1 ; 38:2:: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;;;;4m'); - termColon.writeSync('\x1b[1;38:2::;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - it('CSI 1 ; 38;2:: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;;;;4m'); - termColon.writeSync('\x1b[1;38;2::;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); - }); - }); - }); - describe('cursor positioning', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); - }); - it('cursor forward (CUF)', () => { - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[1C'); - assert.deepEqual(getCursor(term), [2, 0]); - term.writeSync('\x1b[4C'); - assert.deepEqual(getCursor(term), [6, 0]); - term.writeSync('\x1b[100C'); - assert.deepEqual(getCursor(term), [9, 0]); - // should not change y - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [9, 4]); - }); - it('cursor backward (CUB)', () => { - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1D'); - assert.deepEqual(getCursor(term), [0, 0]); - // place cursor at end of first line - term.writeSync('\x1b[100C'); - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [8, 0]); - term.writeSync('\x1b[1D'); - assert.deepEqual(getCursor(term), [7, 0]); - term.writeSync('\x1b[4D'); - assert.deepEqual(getCursor(term), [3, 0]); - term.writeSync('\x1b[100D'); - assert.deepEqual(getCursor(term), [0, 0]); - // should not change y - term.buffer.x = 4; - term.buffer.y = 4; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [3, 4]); - }); - it('cursor down (CUD)', () => { - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1B'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4B'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100B'); - assert.deepEqual(getCursor(term), [0, 9]); - // should not change x - term.buffer.x = 8; - term.buffer.y = 0; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [8, 1]); - }); - it('cursor up (CUU)', () => { - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1A'); - assert.deepEqual(getCursor(term), [0, 0]); - // place cursor at beginning of last row - term.writeSync('\x1b[100B'); - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 8]); - term.writeSync('\x1b[1A'); - assert.deepEqual(getCursor(term), [0, 7]); - term.writeSync('\x1b[4A'); - assert.deepEqual(getCursor(term), [0, 3]); - term.writeSync('\x1b[100A'); - assert.deepEqual(getCursor(term), [0, 0]); - // should not change x - term.buffer.x = 8; - term.buffer.y = 9; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [8, 8]); - }); - it('cursor next line (CNL)', () => { - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1E'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4E'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100E'); - assert.deepEqual(getCursor(term), [0, 9]); - // should reset x to zero - term.buffer.x = 8; - term.buffer.y = 0; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); - }); - it('cursor previous line (CPL)', () => { - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1F'); - assert.deepEqual(getCursor(term), [0, 0]); - // place cursor at beginning of last row - term.writeSync('\x1b[100E'); - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); - term.writeSync('\x1b[1F'); - assert.deepEqual(getCursor(term), [0, 7]); - term.writeSync('\x1b[4F'); - assert.deepEqual(getCursor(term), [0, 3]); - term.writeSync('\x1b[100F'); - assert.deepEqual(getCursor(term), [0, 0]); - // should reset x to zero - term.buffer.x = 8; - term.buffer.y = 9; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); - }); - it('cursor character absolute (CHA)', () => { - term.writeSync('\x1b[G'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1G'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2G'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 0]); - term.writeSync('\x1b[100G'); - assert.deepEqual(getCursor(term), [9, 0]); - }); - it('cursor position (CUP)', () => { - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[1H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[1;1H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[8H'); - assert.deepEqual(getCursor(term), [0, 7]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[;8H'); - assert.deepEqual(getCursor(term), [7, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[100;100H'); - assert.deepEqual(getCursor(term), [9, 9]); - }); - it('horizontal position absolute (HPA)', () => { - term.writeSync('\x1b[`'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1`'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2`'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 0]); - term.writeSync('\x1b[100`'); - assert.deepEqual(getCursor(term), [9, 0]); - }); - it('horizontal position relative (HPR)', () => { - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[1a'); - assert.deepEqual(getCursor(term), [2, 0]); - term.writeSync('\x1b[4a'); - assert.deepEqual(getCursor(term), [6, 0]); - term.writeSync('\x1b[100a'); - assert.deepEqual(getCursor(term), [9, 0]); - // should not change y - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [9, 4]); - }); - it('vertical position absolute (VPA)', () => { - term.writeSync('\x1b[d'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1d'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2d'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [0, 4]); - term.writeSync('\x1b[100d'); - assert.deepEqual(getCursor(term), [0, 9]); - // should not change x - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[d'); - assert.deepEqual(getCursor(term), [8, 0]); - }); - it('vertical position relative (VPR)', () => { - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1e'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4e'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100e'); - assert.deepEqual(getCursor(term), [0, 9]); - // should not change x - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [8, 5]); - }); - describe('should clamp cursor into addressible range', () => { - it('CUF', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [1, 0]); - }); - it('CUB', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [8, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('CUD', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [0, 1]); - }); - it('CUU', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [9, 8]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('CNL', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); - }); - it('CPL', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('CHA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 0]); - }); - it('CUP', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5;5H'); - assert.deepEqual(getCursor(term), [4, 4]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5;5H'); - assert.deepEqual(getCursor(term), [4, 4]); - }); - it('HPA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 0]); - }); - it('HPR', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [1, 0]); - }); - it('VPA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [9, 4]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [0, 4]); - }); - it('VPR', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [0, 1]); - }); - it('DCH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[P'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[P'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('DCH - should delete last cell', () => { - term.writeSync('0123456789\x1b[P'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); - }); - it('ECH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[X'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[X'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('ECH - should delete last cell', () => { - term.writeSync('0123456789\x1b[X'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); - }); - it('ICH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[@'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[@'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - it('ICH - should delete last cell', () => { - term.writeSync('0123456789\x1b[@'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); - }); - }); - }); - describe('DECSTBM - scroll margins', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); - }); - it('should default to whole viewport', () => { - term.writeSync('\x1b[r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); - term.writeSync('\x1b[3;7r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 6); - term.writeSync('\x1b[0;0r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); - }); - it('should clamp bottom', () => { - term.writeSync('\x1b[3;1000r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 9); - }); - it('should only apply for top < bottom', () => { - term.writeSync('\x1b[7;2r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); - }); - it('should home cursor', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[2;7r'); - assert.deepEqual(getCursor(term), [0, 0]); - }); - }); - describe('scroll margins', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); - }); - it('scrollUp', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm'); - assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']); - }); - it('scrollDown', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm'); - assert.deepEqual(getLines(term), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']); - }); - it('insertLines - out of margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[2Lm'); - assert.deepEqual(getLines(term), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); - term.writeSync('\x1b[2H\x1b[2Ln'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); - // skip below scrollbottom - term.writeSync('\x1b[7H\x1b[2Lo'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); - term.writeSync('\x1b[8H\x1b[2Lp'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); - term.writeSync('\x1b[100H\x1b[2Lq'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); - }); - it('insertLines - within margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[3H\x1b[2Lm'); - assert.deepEqual(getLines(term), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']); - term.writeSync('\x1b[6H\x1b[2Ln'); - assert.deepEqual(getLines(term), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']); - }); - it('deleteLines - out of margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[2Mm'); - assert.deepEqual(getLines(term), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); - term.writeSync('\x1b[2H\x1b[2Mn'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); - // skip below scrollbottom - term.writeSync('\x1b[7H\x1b[2Mo'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); - term.writeSync('\x1b[8H\x1b[2Mp'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); - term.writeSync('\x1b[100H\x1b[2Mq'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); - }); - it('deleteLines - within margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[6H\x1b[2Mm'); - assert.deepEqual(getLines(term), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']); - term.writeSync('\x1b[3H\x1b[2Mn'); - assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']); - }); - }); - describe('SL/SR/DECIC/DECDC', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); - }); - it('SL (scrollLeft)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[ @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']); - term.writeSync('\x1b[0 @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']); - term.writeSync('\x1b[2 @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']); - }); - it('SR (scrollRight)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[ A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); - term.writeSync('\x1b[0 A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']); - term.writeSync('\x1b[2 A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); - }); - it('insertColumns (DECIC)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[1\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[2\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); - }); - it('deleteColumns (DECDC)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[1\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[2\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']); - }); - }); - it('should parse big chunks in smaller subchunks', () => { - // max single chunk size is hardcoded as 131072 - const calls: any[] = []; - const term = new TestTerminal({cols: 10, rows: 10}); - (term as any)._inputHandler._parser.parse = (data: Uint32Array, length: number) => { - calls.push([data.length, length]); - }; - term.writeSync('12345'); - term.writeSync('a'.repeat(10000)); - term.writeSync('a'.repeat(200000)); - term.writeSync('a'.repeat(300000)); - assert.deepEqual(calls, [ - [4096, 5], - [10000, 10000], - [131072, 131072], [131072, 200000 - 131072], - [131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072] - ]); - }); - describe('windowOptions', () => { - it('all should be disabled by default and not report', () => { - const term = new TestTerminal({cols: 10, rows: 10}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[14t'); - term.writeSync('\x1b[16t'); - term.writeSync('\x1b[18t'); - term.writeSync('\x1b[20t'); - term.writeSync('\x1b[21t'); - assert.deepEqual(stack, []); - }); - it('14 - GetWinSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizePixels: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[14t'); - // does not report in test terminal due to missing renderer - assert.deepEqual(stack, []); - }); - it('16 - GetCellSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getCellSizePixels: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[16t'); - // does not report in test terminal due to missing renderer - assert.deepEqual(stack, []); - }); - it('18 - GetWinSizeChars', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizeChars: true}}); - const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[18t'); - assert.deepEqual(stack, ['\x1b[8;10;10t']); - term.resize(50, 20); - term.writeSync('\x1b[18t'); - assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); - }); - it('22/23 - PushTitle/PopTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); - const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); - assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); - assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); - assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); - }); - it('22/23 - PushTitle/PopTitle with ;1', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); - const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22;1t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22;1t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22;1t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); - assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); - assert.deepEqual(stack, ['1', '2', '3']); - }); - it('22/23 - PushTitle/PopTitle with ;2', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); - const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22;2t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22;2t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22;2t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); - assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); - assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); - }); - it('DECCOLM - should only work with "SetWinLines" (24) enabled', () => { - // disabled - const term = new TestTerminal({cols: 10, rows: 10}); - term.writeSync('\x1b[?3l'); - assert.equal((term as any)._bufferService.cols, 10); - term.writeSync('\x1b[?3h'); - assert.equal((term as any)._bufferService.cols, 10); - // enabled - const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: {setWinLines: true}}); - term2.writeSync('\x1b[?3l'); - assert.equal((term2 as any)._bufferService.cols, 80); - term2.writeSync('\x1b[?3h'); - assert.equal((term2 as any)._bufferService.cols, 132); - }); - }); - describe('should correctly reset cells taken by wide chars', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 5, scrollback: 1}); - term.writeSync('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥'); - }); - it('print', () => { - term.writeSync('\x1b[H#'); - assert.deepEqual(getLines(term), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[1;6H######'); - assert.deepEqual(getLines(term), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;9H#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']); - term.writeSync('\x1b[4;10H#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']); - }); - it('EL', () => { - term.writeSync('\x1b[1;6H\x1b[K#'); - assert.deepEqual(getLines(term), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;5H\x1b[1K'); - assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[1K'); - assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']); - }); - it('ICH', () => { - term.writeSync('\x1b[1;6H\x1b[@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;4H\x1b[2@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;4H\x1b[3@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[4;4H\x1b[4@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']); - }); - it('DCH', () => { - term.writeSync('\x1b[1;6H\x1b[P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;6H\x1b[2P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[3P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); - }); - it('ECH', () => { - term.writeSync('\x1b[1;6H\x1b[X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;6H\x1b[2X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[3X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); - }); - }); - describe('BS with reverseWraparound set/unset', () => { - const ttyBS = '\x08 \x08'; // tty ICANON sends on pressing BS - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); - }); - describe('reverseWraparound unset (default)', () => { - it('cannot delete last cell', () => { - term.writeSync('12345'); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 1), ['123 5']); - term.writeSync(ttyBS.repeat(10)); - assert.deepEqual(getLines(term, 1), [' 5']); - }); - it('cannot access prev line', () => { - term.writeSync('12345'.repeat(2)); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 2), ['12345', '123 5']); - term.writeSync(ttyBS.repeat(10)); - assert.deepEqual(getLines(term, 2), ['12345', ' 5']); - }); - }); - describe('reverseWraparound set', () => { - it('can delete last cell', () => { - term.writeSync('\x1b[?45h'); - term.writeSync('12345'); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 1), ['1234 ']); - term.writeSync(ttyBS.repeat(7)); - assert.deepEqual(getLines(term, 1), [' ']); - }); - it('can access prev line if wrapped', () => { - term.writeSync('\x1b[?45h'); - term.writeSync('12345'.repeat(2)); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 2), ['12345', '1234 ']); - term.writeSync(ttyBS.repeat(7)); - assert.deepEqual(getLines(term, 2), ['12 ', ' ']); - }); - it('should lift isWrapped', () => { - term.writeSync('\x1b[?45h'); - term.writeSync('12345'.repeat(2)); - assert.equal(term.buffer.lines.get(1).isWrapped, true); - term.writeSync(ttyBS.repeat(7)); - assert.equal(term.buffer.lines.get(1).isWrapped, false); - }); - it('stops at hard NLs', () => { - term.writeSync('\x1b[?45h'); - term.writeSync('12345\r\n'); - term.writeSync('12345'.repeat(2)); - term.writeSync(ttyBS.repeat(50)); - assert.deepEqual(getLines(term, 3), ['12345', ' ', ' ']); - assert.equal(term.buffer.x, 0); - assert.equal(term.buffer.y, 1); - }); - it('handles wide chars correctly', () => { - term.writeSync('\x1b[?45h'); - term.writeSync('¥¥¥'); - assert.deepEqual(getLines(term, 2), ['¥¥', '¥']); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 2), ['¥¥', ' ']); - assert.equal(term.buffer.x, 1); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 2), ['¥¥', ' ']); - assert.equal(term.buffer.x, 0); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 2), ['¥ ', ' ']); - assert.equal(term.buffer.x, 3); // x=4 skipped due to early wrap-around - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 2), ['¥ ', ' ']); - assert.equal(term.buffer.x, 2); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 2), [' ', ' ']); - assert.equal(term.buffer.x, 1); - term.writeSync(ttyBS); - assert.deepEqual(getLines(term, 2), [' ', ' ']); - assert.equal(term.buffer.x, 0); - }); - it('should not reverse outside of scroll margins', () => { - // prepare buffer content - term.writeSync('#####abcdefghijklmnopqrstuvwxy'); - assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); - assert.equal(term.buffer.ydisp, 1); - assert.equal(term.buffer.x, 5); - assert.equal(term.buffer.y, 4); - term.writeSync(ttyBS.repeat(100)); - assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']); - - term.writeSync('\x1b[?45h'); - term.writeSync('uvwxy'); - - // set top/bottom to 1/3 (0-based) - term.writeSync('\x1b[2;4r'); - // place cursor below scroll bottom - term.buffer.x = 5; - term.buffer.y = 4; - term.writeSync(ttyBS.repeat(100)); - assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']); - - term.writeSync('uvwxy'); - // place cursor within scroll margins - term.buffer.x = 5; - term.buffer.y = 3; - term.writeSync(ttyBS.repeat(100)); - assert.deepEqual(getLines(term, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']); - assert.equal(term.buffer.x, 0); - assert.equal(term.buffer.y, term.buffer.scrollTop); // stops at 0, scrollTop - - term.writeSync('fghijklmnopqrst'); - // place cursor above scroll top - term.buffer.x = 5; - term.buffer.y = 0; - term.writeSync(ttyBS.repeat(100)); - assert.deepEqual(getLines(term, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']); - }); - }); - }); - describe('DECSTR', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 5, scrollback: 1}); - term.writeSync('01234567890123'); - }); - it('should reset IRM', () => { - term.writeSync('\x1b[4h'); - assert.equal(term.insertMode, true); - term.writeSync('\x1b[!p'); - assert.equal(term.insertMode, false); - }); - it('should reset cursor visibility', () => { - term.writeSync('\x1b[?25l'); - assert.equal((term as any)._coreService.isCursorHidden, true); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._coreService.isCursorHidden, false); - }); - it('should reset scroll margins', () => { - term.writeSync('\x1b[2;4r'); - assert.equal((term as any)._bufferService.buffer.scrollTop, 1); - assert.equal((term as any)._bufferService.buffer.scrollBottom, 3); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._bufferService.buffer.scrollTop, 0); - assert.equal((term as any)._bufferService.buffer.scrollBottom, term.rows - 1); - }); - it('should reset text attributes', () => { - term.writeSync('\x1b[1;2;32;43m'); - assert.equal(!!term.curAttrData.isBold(), true); - term.writeSync('\x1b[!p'); - assert.equal(!!term.curAttrData.isBold(), false); - assert.equal(term.curAttrData.fg, 0); - assert.equal(term.curAttrData.bg, 0); - }); - it('should reset DECSC data', () => { - term.writeSync('\x1b7'); - assert.equal((term as any)._bufferService.buffer.savedX, 4); - assert.equal((term as any)._bufferService.buffer.savedY, 1); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._bufferService.buffer.savedX, 0); - assert.equal((term as any)._bufferService.buffer.savedY, 0); - }); - it('should reset DECOM', () => { - term.writeSync('\x1b[?6h'); - assert.equal((term as any)._coreService.decPrivateModes.origin, true); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._coreService.decPrivateModes.origin, false); - }); - }); -}); From 210fa7f655b96f81f0e5eed158dc1cf039ce2e8d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 3 May 2020 07:04:01 -0700 Subject: [PATCH 9/9] Move commented test into Terminal.test --- src/browser/Terminal.test.ts | 48 +++++++++++++++++++++++++++++++++ src/common/InputHandler.test.ts | 39 --------------------------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 729c0d0f25..f192b03137 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -1484,6 +1484,54 @@ describe('Terminal', () => { assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']); }); }); + + describe('BS with reverseWraparound set/unset', () => { + const ttyBS = '\x08 \x08'; // tty ICANON sends on pressing BS + + beforeEach(() => { + term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); + }); + + describe('reverseWraparound set', () => { + it('should not reverse outside of scroll margins', () => { + // prepare buffer content + term.writeSync('#####abcdefghijklmnopqrstuvwxy'); + assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); + assert.equal(term.buffer.ydisp, 1); + assert.equal(term.buffer.x, 5); + assert.equal(term.buffer.y, 4); + term.writeSync(ttyBS.repeat(100)); + assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']); + + term.writeSync('\x1b[?45h'); + term.writeSync('uvwxy'); + + // set top/bottom to 1/3 (0-based) + term.writeSync('\x1b[2;4r'); + // place cursor below scroll bottom + term.buffer.x = 5; + term.buffer.y = 4; + term.writeSync(ttyBS.repeat(100)); + assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']); + + term.writeSync('uvwxy'); + // place cursor within scroll margins + term.buffer.x = 5; + term.buffer.y = 3; + term.writeSync(ttyBS.repeat(100)); + assert.deepEqual(getLines(term, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']); + assert.equal(term.buffer.x, 0); + assert.equal(term.buffer.y, term.buffer.scrollTop); // stops at 0, scrollTop + + term.writeSync('fghijklmnopqrst'); + // place cursor above scroll top + term.buffer.x = 5; + term.buffer.y = 0; + term.writeSync(ttyBS.repeat(100)); + assert.deepEqual(getLines(term, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']); + }); + }); + }); }); }); diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index d94b9d86a9..6118859048 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1465,45 +1465,6 @@ describe('InputHandler', () => { assert.deepEqual(getLines(bufferService, 2), [' ', ' ']); assert.equal(bufferService.buffer.x, 0); }); - /* - it.only('should not reverse outside of scroll margins', () => { - // prepare buffer content - inputHandler.parse('#####abcdefghijklmnopqrstuvwxy'); - assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']); - assert.equal(bufferService.buffer.ydisp, 1); - assert.equal(bufferService.buffer.x, 5); - assert.equal(bufferService.buffer.y, 4); - inputHandler.parse(ttyBS.repeat(100)); - assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']); - - inputHandler.parse('\x1b[?45h'); - inputHandler.parse('uvwxy'); - - // set top/bottom to 1/3 (0-based) - inputHandler.parse('\x1b[2;4r'); - // place cursor below scroll bottom - bufferService.buffer.x = 5; - bufferService.buffer.y = 4; - inputHandler.parse(ttyBS.repeat(100)); - assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']); - - inputHandler.parse('uvwxy'); - // place cursor within scroll margins - bufferService.buffer.x = 5; - bufferService.buffer.y = 3; - inputHandler.parse(ttyBS.repeat(100)); - assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']); - assert.equal(bufferService.buffer.x, 0); - assert.equal(bufferService.buffer.y, bufferService.buffer.scrollTop); // stops at 0, scrollTop - - inputHandler.parse('fghijklmnopqrst'); - // place cursor above scroll top - bufferService.buffer.x = 5; - bufferService.buffer.y = 0; - inputHandler.parse(ttyBS.repeat(100)); - assert.deepEqual(getLines(bufferService, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']); - }); - */ }); });