From d858022f23fc45f32935dd20ec4f1cb4d98336fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 4 Jul 2019 17:45:10 +0200 Subject: [PATCH 01/31] fix several escape sequence files --- src/InputHandler.ts | 14 ++++++++++++ src/Terminal2.test.ts | 51 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7250bdb5..abfc00a6 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -540,6 +540,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Backspace (Ctrl-H). */ public backspace(): void { + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } if (this._terminal.buffer.x > 0) { this._terminal.buffer.x--; } @@ -580,6 +583,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ public insertChars(params: number[]): void { + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( this._terminal.buffer.x, params[0] || 1, @@ -845,6 +851,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Selective Erase All. */ public eraseInLine(params: number[]): void { + if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } switch (params[0]) { case 0: this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols); @@ -886,6 +895,7 @@ export class InputHandler extends Disposable implements IInputHandler { // this.maxRange(); this._terminal.updateRange(buffer.y); this._terminal.updateRange(buffer.scrollBottom); + buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? } /** @@ -916,6 +926,7 @@ export class InputHandler extends Disposable implements IInputHandler { // this.maxRange(); this._terminal.updateRange(buffer.y); this._terminal.updateRange(buffer.scrollBottom); + buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? } /** @@ -1169,6 +1180,9 @@ export class InputHandler extends Disposable implements IInputHandler { * [1,1]) (HVP). */ public hVPosition(params: number[]): void { + if (params.length < 2) { + params.push(1); + } if (params[0] < 1) params[0] = 1; if (params[1] < 1) params[1] = 1; diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index c8164c54..91568c41 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -105,20 +105,53 @@ if (os.platform() !== 'win32') { // omit stack trace for escape sequence files Error.stackTraceLimit = 0; const files = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')}); - // for (let i = 0; i < files.length; ++i) console.debug(i, files[i]); // only successful tests for now - const skip = [ - 10, 16, 17, 19, 32, 34, 35, 36, 39, - 40, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, - 63, 68 + const skipFilename = [ + // 't0008-BS.in', + // 't0014-CAN.in', + // 't0015-SUB.in', + // 't0017-SD.in', + // 't0035-HVP.in', + // 't0050-ICH.in', + // 't0051-IL.in', + // 't0052-DL.in', + // 't0055-EL.in', + 't0056-ED.in', + 't0060-DECSC.in', + 't0061-CSI_s.in', + 't0070-DECSTBM_LF.in', + 't0071-DECSTBM_IND.in', + 't0072-DECSTBM_NEL.in', + 't0074-DECSTBM_SU_SD.in', + 't0075-DECSTBM_CUU_CUD.in', + 't0076-DECSTBM_IL_DL.in', + 't0077-DECSTBM_quirks.in', + 't0080-HT.in', + 't0082-HTS.in', + 't0083-CHT.in', + 't0084-CBT.in', + // 't0090-alt_screen.in', + 't0091-alt_screen_ED3.in', + // 't0092-alt_screen_DECSC.in', + // 't0100-IRM.in', + 't0101-NLM.in', + 't0103-reverse_wrap.in', + 't0504-vim.in' ]; - // These are failing on macOS only if (os.platform() === 'darwin') { - skip.push(3, 7, 11, 67); + // These are failing on macOS only + skipFilename.push( + 't0003-line_wrap.in', + 't0005-CR.in', + 't0009-NEL.in', + 't0503-zsh_ls_color.in' + ); } for (let i = 0; i < files.length; i++) { - if (skip.indexOf(i) >= 0) { + // if (skip.indexOf(i) >= 0) { + // continue; + // } + if (skipFilename.indexOf(files[i].split('/').slice(-1)[0]) >= 0) { continue; } ((filename: string) => { From 82ff113107a7d60504dd5cd56321e152fed380f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 4 Jul 2019 23:50:56 +0200 Subject: [PATCH 02/31] add DECALN --- src/InputHandler.ts | 29 ++++++++++++++++++++++++++++- src/Types.d.ts | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index abfc00a6..754cc4a0 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -15,7 +15,7 @@ import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from ' import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types'; -import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags } from 'common/buffer/Constants'; +import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { ICoreService } from 'common/services/Services'; @@ -280,6 +280,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setEscHandler('.' + flag, () => this.selectCharset('.' + flag)); this._parser.setEscHandler('/' + flag, () => this.selectCharset('/' + flag)); // TODO: supported? } + this._parser.setEscHandler('#8', () => this.screenAlignmentPattern()); /** * error handler @@ -2100,4 +2101,30 @@ export class InputHandler extends Disposable implements IInputHandler { public setgLevel(level: number): void { this._terminal.setgLevel(level); // TODO: save to move from terminal? } + + /** + * ESC # 8 + * DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html) + * This control function fills the complete screen area with + * a test pattern (E) used for adjusting screen alignment. + */ + public screenAlignmentPattern(): void { + // prepare cell data + const cell = new CellData(); + cell.content = 1 << Content.WIDTH_SHIFT | 'E'.charCodeAt(0); + cell.fg = this._terminal.curAttrData.fg; + cell.bg = this._terminal.curAttrData.bg; + + const buffer = this._terminal.buffer; + + this.cursorPosition([1, 1]); + for (let yOffset = 0; yOffset < this._terminal.rows; ++yOffset) { + let row = buffer.y + buffer.ybase + yOffset; + buffer.lines.get(row).fill(cell); + buffer.lines.get(row).isWrapped = false; + } + this._terminal.updateRange(0); + this._terminal.updateRange(this._terminal.rows); + this.cursorPosition([1, 1]); + } } diff --git a/src/Types.d.ts b/src/Types.d.ts index eaf6196b..0a45f792 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -168,6 +168,7 @@ export interface IInputHandler { ESC | ESC } ESC ~ */ setgLevel(level: number): void; + /** ESC # 8 */ screenAlignmentPattern(): void; } export interface ILinkMatcher { From 44035d14ab12bb5661db711593745481baedc969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 5 Jul 2019 00:56:57 +0200 Subject: [PATCH 03/31] fix no wraparound mode --- fixtures/escape_sequence_files/t0102-DECAWM.text | 2 +- src/InputHandler.ts | 4 ++-- src/Terminal.test.ts | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/fixtures/escape_sequence_files/t0102-DECAWM.text b/fixtures/escape_sequence_files/t0102-DECAWM.text index c1f22de2..6621f2b8 100644 --- a/fixtures/escape_sequence_files/t0102-DECAWM.text +++ b/fixtures/escape_sequence_files/t0102-DECAWM.text @@ -2,7 +2,7 @@ efgh -------- set: wraparound ----------------------------------------------abcd efgh --------- unset: no wraparound -------------------------------------------abcd +-------- unset: no wraparound -------------------------------------------abch this should be immediately below "no wraparound" diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 754cc4a0..f97e4ba3 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -431,12 +431,12 @@ export class InputHandler extends Disposable implements IInputHandler { // row changed, get it again bufferRow = buffer.lines.get(buffer.y + buffer.ybase); } else { + buffer.x = cols - 1; if (chWidth === 2) { // FIXME: check for xterm behavior // What to do here? We got a wide char that does not fit into last cell continue; } - // FIXME: Do we have to set buffer.x to cols - 1, if not wrapping? } } @@ -2119,7 +2119,7 @@ export class InputHandler extends Disposable implements IInputHandler { this.cursorPosition([1, 1]); for (let yOffset = 0; yOffset < this._terminal.rows; ++yOffset) { - let row = buffer.y + buffer.ybase + yOffset; + const row = buffer.y + buffer.ybase + yOffset; buffer.lines.get(row).fill(cell); buffer.lines.get(row).isWrapped = false; } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index f50b32a7..dad8d46a 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -8,6 +8,7 @@ import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; +import { wcwidth } from 'common/CharWidth'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -750,10 +751,14 @@ describe('Terminal', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.wraparoundMode = false; + const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000); + if (width !== 1) { + continue; + } term.write('a' + high + String.fromCharCode(i)); // auto wraparound mode should cut off the rest of the line - expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a'); - expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(1); + expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(2); expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql(''); term.reset(); } From d18b7996244547b5abedeb273d109cd3a207594a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Fri, 5 Jul 2019 02:04:42 +0200 Subject: [PATCH 04/31] fix HT, HTS, CHT --- src/InputHandler.ts | 6 ++++++ src/Terminal2.test.ts | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f97e4ba3..18eb9fa6 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -554,6 +554,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Horizontal Tab (HT) (Ctrl-I). */ public tab(): void { + if (this._terminal.buffer.x >= this._terminal.cols) { + return; + } const originalX = this._terminal.buffer.x; this._terminal.buffer.x = this._terminal.buffer.nextStop(); if (this._terminal.options.screenReaderMode) { @@ -746,6 +749,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). */ public cursorForwardTab(params: number[]): void { + if (this._terminal.buffer.x >= this._terminal.cols) { + return; + } let param = params[0] || 1; while (param--) { this._terminal.buffer.x = this._terminal.buffer.nextStop(); diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 91568c41..6cc6f733 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -126,9 +126,9 @@ if (os.platform() !== 'win32') { 't0075-DECSTBM_CUU_CUD.in', 't0076-DECSTBM_IL_DL.in', 't0077-DECSTBM_quirks.in', - 't0080-HT.in', - 't0082-HTS.in', - 't0083-CHT.in', + // 't0080-HT.in', + // 't0082-HTS.in', + // 't0083-CHT.in', 't0084-CBT.in', // 't0090-alt_screen.in', 't0091-alt_screen_ED3.in', From eb7b8f90207acbe964d6b0ab60109a0df8e643df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 6 Jul 2019 15:59:18 +0200 Subject: [PATCH 05/31] restrict cursor movements --- src/InputHandler.test.ts | 339 +++++++++++++++++++++++++++++++++++++++ src/InputHandler.ts | 122 ++++++-------- 2 files changed, 387 insertions(+), 74 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index e93fc945..553997cd 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -546,4 +546,343 @@ describe('InputHandler', () => { assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]); }); }); + describe('cursor positioning', () => { + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal({cols: 10, rows: 10}); + }); + function getCursor(term: TestTerminal): number[] { + return [ + term.buffer.x, + term.buffer.y + ]; + } + 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]); + }); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 18eb9fa6..ed7812f2 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -598,6 +598,22 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(this._terminal.buffer.y); } + // restrict cursor changes to addressible cols/rows + private _restrictCursor(): void { + // cols + if (this._terminal.buffer.x < 0) { + this._terminal.buffer.x = 0; + } else if (this._terminal.buffer.x >= this._terminal.cols) { + this._terminal.buffer.x = this._terminal.cols - 1; + } + // rows + if (this._terminal.buffer.y < 0) { + this._terminal.buffer.y = 0; + } else if (this._terminal.buffer.y >= this._terminal.rows) { + this._terminal.buffer.y = this._terminal.rows - 1; + } + } + /** * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). @@ -607,10 +623,9 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.y -= param; - if (this._terminal.buffer.y < 0) { - this._terminal.buffer.y = 0; - } + this._restrictCursor(); } /** @@ -622,14 +637,9 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - // If the end of the line is hit, prevent this action from wrapping around to the next line. - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } + this._restrictCursor(); } /** @@ -641,10 +651,9 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.x += param; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } + this._restrictCursor(); } /** @@ -656,49 +665,42 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - // If the end of the line is hit, prevent this action from wrapping around to the next line. - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } + this._restrictCursor(); this._terminal.buffer.x -= param; - if (this._terminal.buffer.x < 0) { - this._terminal.buffer.x = 0; - } + this._restrictCursor(); } /** * CSI Ps E * Cursor Next Line Ps Times (default = 1) (CNL). - * same as CSI Ps B ? + * Other than cursorDown (CUD) also set the cursor to first column. */ public cursorNextLine(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } this._terminal.buffer.x = 0; + this._restrictCursor(); } /** * CSI Ps F - * Cursor Preceding Line Ps Times (default = 1) (CNL). - * reuse CSI Ps A ? + * Cursor Previous Line Ps Times (default = 1) (CPL). + * Other than cursorUp (CUU) also set the cursor to first column. */ public cursorPrecedingLine(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.y -= param; - if (this._terminal.buffer.y < 0) { - this._terminal.buffer.y = 0; - } this._terminal.buffer.x = 0; + this._restrictCursor(); } @@ -711,7 +713,9 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.x = param - 1; + this._restrictCursor(); } /** @@ -720,7 +724,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public cursorPosition(params: number[]): void { let col: number; - let row: number = params[0] - 1; + const row: number = params[0] - 1; if (params.length >= 2) { col = params[1] - 1; @@ -728,20 +732,10 @@ export class InputHandler extends Disposable implements IInputHandler { col = 0; } - if (row < 0) { - row = 0; - } else if (row >= this._terminal.rows) { - row = this._terminal.rows - 1; - } - - if (col < 0) { - col = 0; - } else if (col >= this._terminal.cols) { - col = this._terminal.cols - 1; - } - + this._restrictCursor(); this._terminal.buffer.x = col; this._terminal.buffer.y = row; + this._restrictCursor(); } /** @@ -1017,32 +1011,31 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Pm ` Character Position Absolute * [column] (default = [row,1]) (HPA). + * Currently same functionality as CHA. */ public charPosAbsolute(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.x = param - 1; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } + this._restrictCursor(); } /** * CSI Pm a Character Position Relative * [columns] (default = [row,col+1]) (HPR) - * reuse CSI Ps C ? + * Currently same functionality as CUF. */ public hPositionRelative(params: number[]): void { let param = params[0]; if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.x += param; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } + this._restrictCursor(); } /** @@ -1155,10 +1148,9 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.y = param - 1; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } + this._restrictCursor(); } /** @@ -1171,37 +1163,19 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } + this._restrictCursor(); this._terminal.buffer.y += param; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - // If the end of the line is hit, prevent this action from wrapping around to the next line. - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x--; - } + this._restrictCursor(); } /** * CSI Ps ; Ps f * Horizontal and Vertical Position [row;column] (default = * [1,1]) (HVP). + * Same as CUP. */ public hVPosition(params: number[]): void { - if (params.length < 2) { - params.push(1); - } - if (params[0] < 1) params[0] = 1; - if (params[1] < 1) params[1] = 1; - - this._terminal.buffer.y = params[0] - 1; - if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - - this._terminal.buffer.x = params[1] - 1; - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } + this.cursorPosition(params); } /** From a2bf5fe975c927e1a6f85ff83cc32e8233646eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 6 Jul 2019 16:13:21 +0200 Subject: [PATCH 06/31] fix typo --- src/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ed7812f2..6ae2cdeb 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -598,7 +598,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(this._terminal.buffer.y); } - // restrict cursor changes to addressible cols/rows + // restrict cursor changes to addressable cols/rows private _restrictCursor(): void { // cols if (this._terminal.buffer.x < 0) { From fa9857aedf66ea19c08108acb20895cea5e1b3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 6 Jul 2019 16:47:48 +0200 Subject: [PATCH 07/31] simplify cursor movements --- src/InputHandler.ts | 69 ++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 6ae2cdeb..1ac02944 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -598,7 +598,11 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(this._terminal.buffer.y); } - // restrict cursor changes to addressable cols/rows + /** + * FIXME: + * - create Cursor class living on Buffer + * - move these private cursor methods to Cursor class as API + */ private _restrictCursor(): void { // cols if (this._terminal.buffer.x < 0) { @@ -614,6 +618,19 @@ export class InputHandler extends Disposable implements IInputHandler { } } + private _setCursor(x: number, y: number): void { + this._terminal.buffer.x = x; + this._terminal.buffer.y = y; + this._restrictCursor(); + } + + private _moveCursor(x: number, y: number): void { + // for relative changes we have to make sure we are within 0 .. cols/rows - 1 + // before calculating the new position + this._restrictCursor(); + this._setCursor(this._terminal.buffer.x + x, this._terminal.buffer.y + y); + } + /** * CSI Ps A * Cursor Up Ps Times (default = 1) (CUU). @@ -623,9 +640,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.y -= param; - this._restrictCursor(); + this._moveCursor(0, -param); } /** @@ -637,9 +652,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.y += param; - this._restrictCursor(); + this._moveCursor(0, param); } /** @@ -651,9 +664,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.x += param; - this._restrictCursor(); + this._moveCursor(param, 0); } /** @@ -665,9 +676,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.x -= param; - this._restrictCursor(); + this._moveCursor(-param, 0); } /** @@ -680,10 +689,8 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.y += param; + this._moveCursor(0, param); this._terminal.buffer.x = 0; - this._restrictCursor(); } @@ -697,10 +704,8 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.y -= param; + this._moveCursor(0, -param); this._terminal.buffer.x = 0; - this._restrictCursor(); } @@ -713,9 +718,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.x = param - 1; - this._restrictCursor(); + this._setCursor(param - 1, this._terminal.buffer.y); } /** @@ -731,11 +734,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else { col = 0; } - - this._restrictCursor(); - this._terminal.buffer.x = col; - this._terminal.buffer.y = row; - this._restrictCursor(); + this._setCursor(col, row); } /** @@ -1018,9 +1017,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.x = param - 1; - this._restrictCursor(); + this._setCursor(param - 1, this._terminal.buffer.y); } /** @@ -1033,9 +1030,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.x += param; - this._restrictCursor(); + this._moveCursor(param, 0); } /** @@ -1148,9 +1143,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.y = param - 1; - this._restrictCursor(); + this._setCursor(this._terminal.buffer.x, param - 1); } /** @@ -1163,9 +1156,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (param < 1) { param = 1; } - this._restrictCursor(); - this._terminal.buffer.y += param; - this._restrictCursor(); + this._moveCursor(0, param); } /** From 6d22772d16273e583aa205a95255f0ef2f54211c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 6 Jul 2019 18:09:38 +0200 Subject: [PATCH 08/31] fix REP to be in line with vttest --- src/common/parser/EscapeSequenceParser.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index d8d4e02e..9725b8bc 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -457,10 +457,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } break; case ParserAction.EXECUTE: - this.precedingCodepoint = 0; callback = this._executeHandlers[code]; if (callback) callback(); else this._executeHandlerFb(code); + this.precedingCodepoint = 0; break; case ParserAction.IGNORE: break; @@ -479,10 +479,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP // inject values: currently not implemented break; case ParserAction.CSI_DISPATCH: - // dont reset preceding codepoint for REP itself - if (code !== 98) { // 'b' - this.precedingCodepoint = 0; - } // Trigger CSI Handler const handlers = this._csiHandlers[code]; let j = handlers ? handlers.length - 1 : -1; @@ -495,6 +491,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP if (j < 0) { this._csiHandlerFb(collect, params, code); } + this.precedingCodepoint = 0; break; case ParserAction.PARAM: // inner loop: digits (0x30 - 0x39) and ; (0x3b) @@ -508,10 +505,10 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP collect += String.fromCharCode(code); break; case ParserAction.ESC_DISPATCH: - this.precedingCodepoint = 0; callback = this._escHandlers[collect + String.fromCharCode(code)]; if (callback) callback(collect, code); else this._escHandlerFb(collect, code); + this.precedingCodepoint = 0; break; case ParserAction.CLEAR: osc = ''; @@ -519,7 +516,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP collect = ''; break; case ParserAction.DCS_HOOK: - this.precedingCodepoint = 0; dcsHandler = this._dcsHandlers[collect + String.fromCharCode(code)]; if (!dcsHandler) dcsHandler = this._dcsHandlerFb; dcsHandler.hook(collect, params, code); @@ -546,6 +542,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP osc = ''; params = [0]; collect = ''; + this.precedingCodepoint = 0; break; case ParserAction.OSC_START: osc = ''; @@ -561,7 +558,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } break; case ParserAction.OSC_END: - this.precedingCodepoint = 0; if (osc && code !== 0x18 && code !== 0x1a) { // NOTE: OSC subparsing is not part of the original parser // we do basic identifier parsing here to offer a jump table for OSC as well @@ -592,6 +588,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP osc = ''; params = [0]; collect = ''; + this.precedingCodepoint = 0; break; } currentState = transition & TableAccess.TRANSITION_STATE_MASK; From 204847b134a675bfc48fbe811c87cf93fcb6fb28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 19:53:02 +0200 Subject: [PATCH 09/31] fix DECSC --- .../escape_sequence_files/t0600-vttest1.in | 35 +++++++++++++++++++ src/InputHandler.ts | 3 ++ src/Terminal2.test.ts | 7 ++-- 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 fixtures/escape_sequence_files/t0600-vttest1.in diff --git a/fixtures/escape_sequence_files/t0600-vttest1.in b/fixtures/escape_sequence_files/t0600-vttest1.in new file mode 100644 index 00000000..dff924d4 --- /dev/null +++ b/fixtures/escape_sequence_files/t0600-vttest1.in @@ -0,0 +1,35 @@ +Test of autowrap, mixing control and print characters. + + +The left/right margins should have letters in order: + + +[?6hAa +aBB b +C cC + +DdEe +eFF f +G gG + +HhIi +iJJ j +K kK + +LlMm +mNN n +O oO + +PpQq +qRR r +S sS + +TtUu +uVV v +W wW + +XxYy +yZZ z +[?6lPush + + diff --git a/src/InputHandler.ts b/src/InputHandler.ts index b6a82150..bc9528e0 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1249,6 +1249,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 3: // 132 col mode this._terminal.savedCols = this._terminal.cols; this._terminal.resize(132, this._terminal.rows); + this._terminal.reset(); break; case 6: this._terminal.originMode = true; @@ -1447,6 +1448,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.resize(this._terminal.savedCols, this._terminal.rows); } delete this._terminal.savedCols; + this._terminal.reset(); break; case 6: this._terminal.originMode = false; @@ -1919,6 +1921,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.y = Math.max(this._terminal.buffer.savedY - this._terminal.buffer.ybase, 0); this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg; this._terminal.curAttrData.bg = this._terminal.buffer.savedCurAttrData.bg; + this._restrictCursor(); } diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 6cc6f733..fe291656 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -117,8 +117,8 @@ if (os.platform() !== 'win32') { // 't0052-DL.in', // 't0055-EL.in', 't0056-ED.in', - 't0060-DECSC.in', - 't0061-CSI_s.in', + // 't0060-DECSC.in', + // 't0061-CSI_s.in', 't0070-DECSTBM_LF.in', 't0071-DECSTBM_IND.in', 't0072-DECSTBM_NEL.in', @@ -136,7 +136,8 @@ if (os.platform() !== 'win32') { // 't0100-IRM.in', 't0101-NLM.in', 't0103-reverse_wrap.in', - 't0504-vim.in' + 't0504-vim.in', + 't0600-vttest1.in' // FIXME: fix height and create .text ]; if (os.platform() === 'darwin') { // These are failing on macOS only From cd1d194dbdfdb1d722d12ebeb03a36e5f3239743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 20:39:37 +0200 Subject: [PATCH 10/31] apply cursor restrictions --- src/InputHandler.ts | 15 +++++++++------ src/Terminal2.test.ts | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index bc9528e0..e7b21c0f 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -541,9 +541,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Backspace (Ctrl-H). */ public backspace(): void { - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } + this._restrictCursor(); if (this._terminal.buffer.x > 0) { this._terminal.buffer.x--; } @@ -587,9 +585,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps (Blank) Character(s) (default = 1) (ICH). */ public insertChars(params: IParams): void { - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } + this._restrictCursor(); this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( this._terminal.buffer.x, params.params[0] || 1, @@ -841,6 +837,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps Line(s) (default = 1) (IL). */ public insertLines(params: IParams): void { + this._restrictCursor(); let param = params.params[0] || 1; // make buffer local for faster access @@ -868,6 +865,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Delete Ps Line(s) (default = 1) (DL). */ public deleteLines(params: IParams): void { + this._restrictCursor(); let param = params.params[0] || 1; // make buffer local for faster access @@ -959,6 +957,9 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). */ public cursorBackwardTab(params: IParams): void { + if (this._terminal.buffer.x >= this._terminal.cols) { + return; + } let param = params.params[0] || 1; // make buffer local for faster access @@ -2017,6 +2018,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Moves the cursor down one line in the same column. */ public index(): void { + this._restrictCursor(); this._terminal.index(); // TODO: save to move from terminal? } @@ -2039,6 +2041,7 @@ export class InputHandler extends Disposable implements IInputHandler { * the page scrolls down. */ public reverseIndex(): void { + this._restrictCursor(); this._terminal.reverseIndex(); // TODO: save to move from terminal? } diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index fe291656..8c560761 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -116,7 +116,7 @@ if (os.platform() !== 'win32') { // 't0051-IL.in', // 't0052-DL.in', // 't0055-EL.in', - 't0056-ED.in', + // 't0056-ED.in', // 't0060-DECSC.in', // 't0061-CSI_s.in', 't0070-DECSTBM_LF.in', From d26a64372745e297278c9ddf2c332d0665660b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 20:45:54 +0200 Subject: [PATCH 11/31] use fill for _resetBufferLine --- src/InputHandler.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e7b21c0f..96832dfe 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -740,7 +740,9 @@ export class InputHandler extends Disposable implements IInputHandler { * @param y row index */ private _resetBufferLine(y: number): void { - this._eraseInBufferLine(y, 0, this._terminal.cols, true); + const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y); + line.fill(this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())); + line.isWrapped = false; } /** @@ -756,6 +758,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Selective Erase All. */ public eraseInDisplay(params: IParams): void { + this._restrictCursor(); let j; switch (params.params[0]) { case 0: @@ -815,9 +818,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Ps = 2 -> Selective Erase All. */ public eraseInLine(params: IParams): void { - if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } + this._restrictCursor(); switch (params.params[0]) { case 0: this._eraseInBufferLine(this._terminal.buffer.y, this._terminal.buffer.x, this._terminal.cols); From 9a64c549f8fe34c7c2fe77163f1af50258337c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 20:54:12 +0200 Subject: [PATCH 12/31] fix ED3 test --- .../t0091-alt_screen_ED3.text | 48 +++++++++---------- src/Terminal2.test.ts | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/fixtures/escape_sequence_files/t0091-alt_screen_ED3.text b/fixtures/escape_sequence_files/t0091-alt_screen_ED3.text index 4a326a40..e481aec1 100644 --- a/fixtures/escape_sequence_files/t0091-alt_screen_ED3.text +++ b/fixtures/escape_sequence_files/t0091-alt_screen_ED3.text @@ -1,25 +1,25 @@ - n - o - p - q - r - s - t - u - v - w - x - y - z - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 +o +p +q +r +s +t +u +v +w +x +y +z +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 + +11 - 11 diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 8c560761..9ad84736 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -131,7 +131,7 @@ if (os.platform() !== 'win32') { // 't0083-CHT.in', 't0084-CBT.in', // 't0090-alt_screen.in', - 't0091-alt_screen_ED3.in', + // 't0091-alt_screen_ED3.in', // 't0092-alt_screen_DECSC.in', // 't0100-IRM.in', 't0101-NLM.in', From 926c298371c085324a973fc5fb9476bea66184a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 21:10:38 +0200 Subject: [PATCH 13/31] fix DCH --- src/InputHandler.test.ts | 14 ++++++++++++++ src/InputHandler.ts | 16 ++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index a39ad9b9..2ebac006 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1034,6 +1034,20 @@ describe('InputHandler', () => { 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 '); + }); }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 96832dfe..7cb6cd2d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -895,12 +895,16 @@ export class InputHandler extends Disposable implements IInputHandler { * Delete Ps Character(s) (default = 1) (DCH). */ public deleteChars(params: IParams): void { - this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells( - this._terminal.buffer.x, - params.params[0] || 1, - this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) - ); - this._terminal.updateRange(this._terminal.buffer.y); + this._restrictCursor(); + const line = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase); + if (line) { + line.deleteCells( + this._terminal.buffer.x, + params.params[0] || 1, + this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) + ); + this._terminal.updateRange(this._terminal.buffer.y); + } } /** From 7e7730d176f5037ea7baf7848a91eeb100902f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 21:15:14 +0200 Subject: [PATCH 14/31] fix ECH --- src/InputHandler.test.ts | 14 ++++++++++++++ src/InputHandler.ts | 16 ++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 2ebac006..4329fa1a 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1048,6 +1048,20 @@ describe('InputHandler', () => { 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 '); + }); }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7cb6cd2d..718efcc8 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -950,12 +950,16 @@ export class InputHandler extends Disposable implements IInputHandler { * Erase Ps Character(s) (default = 1) (ECH). */ public eraseChars(params: IParams): void { - this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells( - this._terminal.buffer.x, - this._terminal.buffer.x + (params.params[0] || 1), - this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) - ); - this._terminal.updateRange(this._terminal.buffer.y); + this._restrictCursor(); + const line = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase); + if (line) { + line.replaceCells( + this._terminal.buffer.x, + this._terminal.buffer.x + (params.params[0] || 1), + this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) + ); + this._terminal.updateRange(this._terminal.buffer.y); + } } /** From 9d4cafc4c9983ebf73422d8f122329240c3988e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 21:18:03 +0200 Subject: [PATCH 15/31] fix ICH --- src/InputHandler.test.ts | 14 ++++++++++++++ src/InputHandler.ts | 15 +++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 4329fa1a..541547f8 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1062,6 +1062,20 @@ describe('InputHandler', () => { 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 '); + }); }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 718efcc8..5fae95f9 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -586,12 +586,15 @@ export class InputHandler extends Disposable implements IInputHandler { */ public insertChars(params: IParams): void { this._restrictCursor(); - this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells( - this._terminal.buffer.x, - params.params[0] || 1, - this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) - ); - this._terminal.updateRange(this._terminal.buffer.y); + const line = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase); + if (line) { + line.insertCells( + this._terminal.buffer.x, + params.params[0] || 1, + this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) + ); + this._terminal.updateRange(this._terminal.buffer.y); + } } /** From ef58f1fef8569b5616076774ba223d33a1c12388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 22:19:45 +0200 Subject: [PATCH 16/31] add vttest wrapping testfile --- fixtures/escape_sequence_files/run_tests.py | 73 +++++++++++++++++++ .../{t0600-vttest1.in => t0300-vttest1.in} | 0 .../escape_sequence_files/t0300-vttest1.text | 25 +++++++ src/Terminal2.test.ts | 3 +- 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 fixtures/escape_sequence_files/run_tests.py rename fixtures/escape_sequence_files/{t0600-vttest1.in => t0300-vttest1.in} (100%) create mode 100644 fixtures/escape_sequence_files/t0300-vttest1.text diff --git a/fixtures/escape_sequence_files/run_tests.py b/fixtures/escape_sequence_files/run_tests.py new file mode 100644 index 00000000..08efbf3f --- /dev/null +++ b/fixtures/escape_sequence_files/run_tests.py @@ -0,0 +1,73 @@ +from glob import glob +import os +import sys +import termios +import atexit + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def enable_echo(fd, enabled): + (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(fd) + if enabled: + lflag |= termios.ECHO + else: + lflag &= ~termios.ECHO + new_attr = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] + termios.tcsetattr(fd, termios.TCSANOW, new_attr) + +atexit.register(enable_echo, sys.stdin.fileno(), True) + +output = [] + + +def log(append=False, *s): + if append: + output[-1] += ' ' + ' '.join(str(part) for part in s) + else: + output.append(' '.join(str(part) for part in s)) + + +def reset_terminal(): + sys.stdout.write('\x1bc\x1b[H') + sys.stdout.flush() + + +def test(): + count = 0 + passed = 0 + for i, testfile in enumerate(sorted(glob(os.path.join(BASE_DIR, '*.in')))): + count += 1 + log(False, os.path.basename(testfile)) + reset_terminal() + with open(testfile) as test: + sys.stdout.write('\x1b]0;%s\x07' % os.path.basename(testfile)) + sys.stdout.write(test.read()+'\x1bt') + sys.stdout.flush() + with open(os.path.join(os.path.dirname(testfile), + os.path.basename(testfile).split('.')[0]+'.text')) as expected: + terminal_output = sys.stdin.read() + if not terminal_output: + # we are in xterm + continue + if terminal_output != expected.read(): + log(True, '\x1b[31merror\x1b[0m') + with open(os.path.join(os.path.dirname(testfile), 'output', + os.path.basename(testfile)), 'w') as t_out: + t_out.write(terminal_output) + else: + passed += 1 + log(True, '\x1b[32mpass\x1b[0m') + return count, passed + + +if __name__ == '__main__': + enable_echo(sys.stdin.fileno(), False) + count, passed = test() + enable_echo(sys.stdin.fileno(), True) + reset_terminal() + for i in range(len(output)/2+1): + if not (i+1) % 25: + sys.stdin.read() + print ''.join(i.ljust(40) for i in output[i*2:i*2+2]) + print '\x1b[33mcoverage: %s/%s (%d%%) tests passed.\x1b[0m' % (passed, count, passed*100/count) diff --git a/fixtures/escape_sequence_files/t0600-vttest1.in b/fixtures/escape_sequence_files/t0300-vttest1.in similarity index 100% rename from fixtures/escape_sequence_files/t0600-vttest1.in rename to fixtures/escape_sequence_files/t0300-vttest1.in diff --git a/fixtures/escape_sequence_files/t0300-vttest1.text b/fixtures/escape_sequence_files/t0300-vttest1.text new file mode 100644 index 00000000..d4dd93d7 --- /dev/null +++ b/fixtures/escape_sequence_files/t0300-vttest1.text @@ -0,0 +1,25 @@ +Test of autowrap, mixing control and print characters. + +I i +J j +K k +L l +M m +N n +O o +P p +Q q +R r +S s +T t +U u +V v +W w +X x +Y y +Z z + +Push + + + diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 9ad84736..f0d54e5e 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -137,7 +137,8 @@ if (os.platform() !== 'win32') { 't0101-NLM.in', 't0103-reverse_wrap.in', 't0504-vim.in', - 't0600-vttest1.in' // FIXME: fix height and create .text + // vttest related files + 't0300-vttest1.in' ]; if (os.platform() === 'darwin') { // These are failing on macOS only From 46f89cd4ffaf9b01155f9ca699451c2252b9dd05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 22:22:19 +0200 Subject: [PATCH 17/31] fix NPE --- src/Terminal.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index ccec42e8..ccbca340 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1846,6 +1846,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * ESC M Reverse Index (RI is 0x8d). * * Move the cursor up one row, inserting a new blank line if necessary. + * FIXME: This method is seriously broken. */ public reverseIndex(): void { if (this.buffer.y === this.buffer.scrollTop) { @@ -1859,6 +1860,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.updateRange(this.buffer.scrollBottom); } else { this.buffer.y--; + (this._inputHandler as any)._restrictCursor(); // quickfix to not run out of bounds } } From e2366fa83d9a1c302e5716f777b4e29e5d032431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 7 Jul 2019 23:37:02 +0200 Subject: [PATCH 18/31] rewrite DECSTBM --- src/InputHandler.test.ts | 27 +++++++++++++++++++++++++++ src/InputHandler.ts | 17 +++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 541547f8..68550c9f 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1078,4 +1078,31 @@ describe('InputHandler', () => { }); }); }); + 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); + }); + }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 5fae95f9..ca4fa2da 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1904,10 +1904,19 @@ export class InputHandler extends Disposable implements IInputHandler { if (collect) { return; } - this._terminal.buffer.scrollTop = (params.params[0] || 1) - 1; - this._terminal.buffer.scrollBottom = (params.length > 1 && params.params[1] && params.params[1] <= this._terminal.rows ? params.params[1] : this._terminal.rows) - 1; - this._terminal.buffer.x = 0; - this._terminal.buffer.y = 0; + + const top = params.params[0] || 1; + let bottom: number; + + if (params.length < 2 || (bottom = params.params[1]) > this._terminal.rows || bottom === 0) { + bottom = this._terminal.rows; + } + + if (bottom > top) { + this._terminal.buffer.scrollTop = top - 1; + this._terminal.buffer.scrollBottom = bottom - 1; + this._setCursor(0, 0); + } } From 41cc99a9199eded8729d959e0f69baf1a2b4425b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Jul 2019 00:19:21 +0200 Subject: [PATCH 19/31] DECSTBM SU/SD --- .../t0074-DECSTBM_SU_SD.text | 39 ++++++++++--------- src/InputHandler.test.ts | 21 ++++++++++ src/Terminal2.test.ts | 2 +- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.text b/fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.text index 948e5d43..02caf57d 100644 --- a/fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.text +++ b/fixtures/escape_sequence_files/t0074-DECSTBM_SU_SD.text @@ -1,24 +1,25 @@ a - b - c - d - f - g - h - i +b +c +d +f +g +h +i - j - k - l - m +j +k +l +m - n - o - p - q - r - s - w - x +n +o +p +q +r +s +w +x + diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 68550c9f..c21ca5f9 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -1105,4 +1105,25 @@ describe('InputHandler', () => { assert.equal(term.buffer.scrollBottom, 9); }); }); + describe('scrolling', () => { + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal({cols: 10, rows: 10}); + }); + 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; + } + it('scrollUp with margins', () => { + 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 with margins', () => { + 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']); + }); + }); }); diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index f0d54e5e..b65a2e59 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -122,7 +122,7 @@ if (os.platform() !== 'win32') { 't0070-DECSTBM_LF.in', 't0071-DECSTBM_IND.in', 't0072-DECSTBM_NEL.in', - 't0074-DECSTBM_SU_SD.in', + // 't0074-DECSTBM_SU_SD.in', 't0075-DECSTBM_CUU_CUD.in', 't0076-DECSTBM_IL_DL.in', 't0077-DECSTBM_quirks.in', From 8654133b150dec67dad814afe863a4b1c772d288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Jul 2019 00:39:05 +0200 Subject: [PATCH 20/31] move leftover sequence methods to InputHandler --- src/InputHandler.ts | 24 +++++++++++++++++--- src/Terminal.ts | 51 +------------------------------------------ src/TestUtils.test.ts | 9 -------- src/Types.d.ts | 3 --- 4 files changed, 22 insertions(+), 65 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ca4fa2da..7bdd5fe4 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2040,7 +2040,12 @@ export class InputHandler extends Disposable implements IInputHandler { */ public index(): void { this._restrictCursor(); - this._terminal.index(); // TODO: save to move from terminal? + this._terminal.buffer.y++; + if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { + this._terminal.buffer.y--; + this._terminal.scroll(); + } + this._restrictCursor(); } /** @@ -2051,7 +2056,7 @@ export class InputHandler extends Disposable implements IInputHandler { * the value of the active column when the terminal receives an HTS. */ public tabSet(): void { - this._terminal.tabSet(); // TODO: save to move from terminal? + this._terminal.buffer.tabs[this._terminal.buffer.x] = true; } /** @@ -2063,7 +2068,20 @@ export class InputHandler extends Disposable implements IInputHandler { */ public reverseIndex(): void { this._restrictCursor(); - this._terminal.reverseIndex(); // TODO: save to move from terminal? + const buffer = this._terminal.buffer; + if (buffer.y === buffer.scrollTop) { + // possibly move the code below to term.reverseScroll(); + // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' + // blankLine(true) is xterm/linux behavior + const scrollRegionHeight = buffer.scrollBottom - buffer.scrollTop; + buffer.lines.shiftElements(buffer.y + buffer.ybase, scrollRegionHeight, 1); + buffer.lines.set(buffer.y + buffer.ybase, buffer.getBlankLine(this._terminal.eraseAttrData())); + this._terminal.updateRange(buffer.scrollTop); + this._terminal.updateRange(buffer.scrollBottom); + } else { + buffer.y--; + this._restrictCursor(); // quickfix to not run out of bounds + } } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index ccbca340..d40d622f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1824,48 +1824,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } /** - * ESC - */ - - /** - * ESC D Index (IND is 0x84). - */ - public index(): void { - this.buffer.y++; - if (this.buffer.y > this.buffer.scrollBottom) { - this.buffer.y--; - this.scroll(); - } - // If the end of the line is hit, prevent this action from wrapping around to the next line. - if (this.buffer.x >= this.cols) { - this.buffer.x--; - } - } - - /** - * ESC M Reverse Index (RI is 0x8d). - * - * Move the cursor up one row, inserting a new blank line if necessary. - * FIXME: This method is seriously broken. - */ - public reverseIndex(): void { - if (this.buffer.y === this.buffer.scrollTop) { - // possibly move the code below to term.reverseScroll(); - // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' - // blankLine(true) is xterm/linux behavior - const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop; - this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1); - this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.buffer.getBlankLine(this.eraseAttrData())); - this.updateRange(this.buffer.scrollTop); - this.updateRange(this.buffer.scrollBottom); - } else { - this.buffer.y--; - (this._inputHandler as any)._restrictCursor(); // quickfix to not run out of bounds - } - } - - /** - * ESC c Full Reset (RIS). + * Full reset of the terminal. */ public reset(): void { /** @@ -1907,14 +1866,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } } - - /** - * ESC H Tab Set (HTS is 0x88). - */ - public tabSet(): void { - this.buffer.tabs[this.buffer.x] = true; - } - // TODO: Remove cancel function and cancelEvents option public cancel(ev: Event, force?: boolean): boolean { if (!this.options.cancelEvents && !force) { diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 62ae609e..15f30565 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -295,21 +295,12 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { addDisposableListener(type: string, handler: XtermListener): IDisposable { throw new Error('Method not implemented.'); } - tabSet(): void { - throw new Error('Method not implemented.'); - } handler(data: string): void { throw new Error('Method not implemented.'); } handleTitle(title: string): void { throw new Error('Method not implemented.'); } - index(): void { - throw new Error('Method not implemented.'); - } - reverseIndex(): void { - throw new Error('Method not implemented.'); - } } export class MockBuffer implements IBuffer { diff --git a/src/Types.d.ts b/src/Types.d.ts index 1ebc800e..0614d626 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -70,10 +70,7 @@ export interface IInputHandlingTerminal { showCursor(): void; refresh(start: number, end: number): void; error(text: string, data?: any): void; - tabSet(): void; handleTitle(title: string): void; - index(): void; - reverseIndex(): void; } export interface IViewport extends IDisposable { From 5122fdc441e247b330003db559d5754f701ed97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Jul 2019 00:53:13 +0200 Subject: [PATCH 21/31] document wonky usage of Terminal.reset --- src/Terminal.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index d40d622f..c2b79ea3 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1824,7 +1824,12 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } /** - * Full reset of the terminal. + * Reset terminal. + * Note: Calling this directly from JS is synchronous but does not clear + * input buffers and does not reset the parser, thus the terminal will + * continue to apply pending input data. + * If you need in band reset (synchronous with input data) consider + * using DECSTR (soft reset, CSI ! p) or RIS instead (hard reset, ESC c). */ public reset(): void { /** From bc867bf12b56f78f94983ff11cc79d8fab4a2572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Jul 2019 02:11:46 +0200 Subject: [PATCH 22/31] partial fix DECSTBM IL/DL --- .../t0076-DECSTBM_IL_DL.text | 11 +-- src/InputHandler.test.ts | 75 ++++++++++++++++--- src/InputHandler.ts | 8 ++ src/Terminal2.test.ts | 4 +- 4 files changed, 82 insertions(+), 16 deletions(-) diff --git a/fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.text b/fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.text index 92c10331..f89893ba 100644 --- a/fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.text +++ b/fixtures/escape_sequence_files/t0076-DECSTBM_IL_DL.text @@ -1,3 +1,4 @@ + 6 C 8 ^^^^ 9 vvvv DL on line 11, expected: ACD_ 10 A @@ -12,14 +13,14 @@ 19 vvvv IL on line 21, expected: A_ 20 A + 22 ^^^^ - -23 vvvv IL on line 24, expected: _A +24 A 25 B -26 ^^^^ -28 A +27 vvvv DL on line 28, expected: B_ +28 A 29 B 30 ^^^^ 31 -32 +32 \ No newline at end of file diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index c21ca5f9..6d4a3ca8 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -15,6 +15,13 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { Params } from 'common/parser/Params'; import { MockCoreService } from 'common/TestUtils.test'; +function getCursor(term: TestTerminal): number[] { + return [ + term.buffer.x, + term.buffer.y + ]; +} + describe('InputHandler', () => { describe('save and restore cursor', () => { const terminal = new MockInputHandlingTerminal(); @@ -702,12 +709,6 @@ describe('InputHandler', () => { beforeEach(() => { term = new TestTerminal({cols: 10, rows: 10}); }); - function getCursor(term: TestTerminal): number[] { - return [ - term.buffer.x, - term.buffer.y - ]; - } it('cursor forward (CUF)', () => { term.writeSync('\x1b[C'); assert.deepEqual(getCursor(term), [1, 0]); @@ -1104,8 +1105,14 @@ describe('InputHandler', () => { 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('scrolling', () => { + describe('scroll margins', () => { let term: TestTerminal; beforeEach(() => { term = new TestTerminal({cols: 10, rows: 10}); @@ -1117,13 +1124,63 @@ describe('InputHandler', () => { } return res; } - it('scrollUp with margins', () => { + 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 with margins', () => { + 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']); + }); }); }); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7bdd5fe4..1f1dee2e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -847,6 +847,10 @@ export class InputHandler extends Disposable implements IInputHandler { // make buffer local for faster access const buffer = this._terminal.buffer; + if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { + return; + } + const row: number = buffer.y + buffer.ybase; const scrollBottomRowsOffset = this._terminal.rows - 1 - buffer.scrollBottom; @@ -875,6 +879,10 @@ export class InputHandler extends Disposable implements IInputHandler { // make buffer local for faster access const buffer = this._terminal.buffer; + if (buffer.y > buffer.scrollBottom || buffer.y < buffer.scrollTop) { + return; + } + const row: number = buffer.y + buffer.ybase; let j: number; diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index b65a2e59..0555368b 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -119,12 +119,12 @@ if (os.platform() !== 'win32') { // 't0056-ED.in', // 't0060-DECSC.in', // 't0061-CSI_s.in', - 't0070-DECSTBM_LF.in', + 't0070-DECSTBM_LF.in', // lineFeed not working correctly 't0071-DECSTBM_IND.in', 't0072-DECSTBM_NEL.in', // 't0074-DECSTBM_SU_SD.in', 't0075-DECSTBM_CUU_CUD.in', - 't0076-DECSTBM_IL_DL.in', + 't0076-DECSTBM_IL_DL.in', // not working due to lineFeed 't0077-DECSTBM_quirks.in', // 't0080-HT.in', // 't0082-HTS.in', From cb25e2d4e0ce1ddd67260c4242b2902aac147d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Jul 2019 02:42:02 +0200 Subject: [PATCH 23/31] save/restore charset in DECSC/DECRC --- src/InputHandler.ts | 5 +++++ src/TestUtils.test.ts | 3 ++- src/common/buffer/Buffer.ts | 4 +++- src/common/buffer/Types.d.ts | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 1f1dee2e..14d18dfd 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1938,6 +1938,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.savedY = this._terminal.buffer.ybase + this._terminal.buffer.y; this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg; this._terminal.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg; + this._terminal.buffer.savedCharset = this._terminal.charset; } @@ -1951,6 +1952,10 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.y = Math.max(this._terminal.buffer.savedY - this._terminal.buffer.ybase, 0); this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg; this._terminal.curAttrData.bg = this._terminal.buffer.savedCurAttrData.bg; + this._terminal.charset = (this as any)._savedCharset; + if (this._terminal.buffer.savedCharset) { + this._terminal.charset = this._terminal.buffer.savedCharset; + } this._restrictCursor(); } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 15f30565..e9a76f7f 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -6,7 +6,7 @@ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions, ILinkifier, ILinkMatcherOptions } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; @@ -320,6 +320,7 @@ export class MockBuffer implements IBuffer { scrollTop: number; savedY: number; savedX: number; + savedCharset: ICharset | null; savedCurAttrData = new AttributeData(); translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string { return Buffer.prototype.translateBufferLineToString.apply(this, arguments); diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 9c36a5a4..1b3e5d86 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -5,13 +5,14 @@ import { CircularList, IInsertEvent } from 'common/CircularList'; import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICharset } from 'common/Types'; import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from 'common/buffer/Constants'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from 'common/buffer/BufferReflow'; import { Marker } from 'common/buffer/Marker'; import { IOptionsService, IBufferService } from 'common/services/Services'; +import { DEFAULT_CHARSET } from 'common/data/Charsets'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 @@ -35,6 +36,7 @@ export class Buffer implements IBuffer { public savedY: number = 0; public savedX: number = 0; public savedCurAttrData = DEFAULT_ATTR_DATA.clone(); + public savedCharset: ICharset | null = DEFAULT_CHARSET; public markers: Marker[] = []; private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]); diff --git a/src/common/buffer/Types.d.ts b/src/common/buffer/Types.d.ts index 2606df2f..532230ec 100644 --- a/src/common/buffer/Types.d.ts +++ b/src/common/buffer/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker } from 'common/Types'; +import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker, ICharset } from 'common/Types'; import { IEvent } from 'common/EventEmitter'; // BufferIndex denotes a position in the buffer: [rowIndex, colIndex] @@ -31,6 +31,7 @@ export interface IBuffer { hasScrollback: boolean; savedY: number; savedX: number; + savedCharset: ICharset | null; savedCurAttrData: IAttributeData; isCursorInViewport: boolean; markers: IMarker[]; From e91c223bb097b8dd52423137dc63bde8e4dfe486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Jul 2019 23:09:53 +0200 Subject: [PATCH 24/31] respect DECSTBM margins in DECOM mode --- src/InputHandler.ts | 16 ++++++++++++++-- src/Terminal2.test.ts | 5 +++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 14d18dfd..658b9445 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -615,11 +615,23 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (this._terminal.buffer.y >= this._terminal.rows) { this._terminal.buffer.y = this._terminal.rows - 1; } + if (this._terminal.originMode) { + if (this._terminal.buffer.y < this._terminal.buffer.scrollTop) { + this._terminal.buffer.y = this._terminal.buffer.scrollTop; + } else if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { + this._terminal.buffer.y = this._terminal.buffer.scrollBottom; + } + } } private _setCursor(x: number, y: number): void { - this._terminal.buffer.x = x; - this._terminal.buffer.y = y; + if (this._terminal.originMode) { + this._terminal.buffer.x = x; + this._terminal.buffer.y = this._terminal.buffer.scrollTop + y; + } else { + this._terminal.buffer.x = x; + this._terminal.buffer.y = y; + } this._restrictCursor(); } diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 0555368b..e1af13fc 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -136,9 +136,10 @@ if (os.platform() !== 'win32') { // 't0100-IRM.in', 't0101-NLM.in', 't0103-reverse_wrap.in', - 't0504-vim.in', + 't0504-vim.in' + // vttest related files - 't0300-vttest1.in' + // 't0300-vttest1.in' ]; if (os.platform() === 'darwin') { // These are failing on macOS only From 3d6f87ea7f071a3ddd740553d8908521fba68c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Jul 2019 23:22:46 +0200 Subject: [PATCH 25/31] reset cursor on DECOM --- src/InputHandler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 658b9445..8e78a5b2 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1286,6 +1286,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 6: this._terminal.originMode = true; + this._setCursor(0, 0); break; case 7: this._terminal.wraparoundMode = true; @@ -1485,6 +1486,7 @@ export class InputHandler extends Disposable implements IInputHandler { break; case 6: this._terminal.originMode = false; + this._setCursor(0, 0); break; case 7: this._terminal.wraparoundMode = false; From 691a8addfc1ad81e0ed503b11966afcbdf7bd966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 8 Jul 2019 23:59:46 +0200 Subject: [PATCH 26/31] cleanup; doc; move cursor methods next to each other --- src/InputHandler.ts | 213 +++++++++++++++++++++++------------------- src/Terminal2.test.ts | 3 - 2 files changed, 115 insertions(+), 101 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 8e78a5b2..5d8c4922 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -581,26 +581,15 @@ export class InputHandler extends Disposable implements IInputHandler { } /** - * CSI Ps @ - * Insert Ps (Blank) Character(s) (default = 1) (ICH). + * Cursor movements. + * + * TODO: + * - create Cursor class living on Buffer + * - move private cursor methods to Cursor class as API */ - public insertChars(params: IParams): void { - this._restrictCursor(); - const line = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase); - if (line) { - line.insertCells( - this._terminal.buffer.x, - params.params[0] || 1, - this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) - ); - this._terminal.updateRange(this._terminal.buffer.y); - } - } /** - * FIXME: - * - create Cursor class living on Buffer - * - move these private cursor methods to Cursor class as API + * Restrict cursor to viewport size / scroll margin (origin mode). */ private _restrictCursor(): void { // cols @@ -624,6 +613,9 @@ export class InputHandler extends Disposable implements IInputHandler { } } + /** + * Set absolute cursor position. + */ private _setCursor(x: number, y: number): void { if (this._terminal.originMode) { this._terminal.buffer.x = x; @@ -635,6 +627,9 @@ export class InputHandler extends Disposable implements IInputHandler { this._restrictCursor(); } + /** + * Set relative cursor position. + */ private _moveCursor(x: number, y: number): void { // for relative changes we have to make sure we are within 0 .. cols/rows - 1 // before calculating the new position @@ -684,7 +679,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.x = 0; } - /** * CSI Ps F * Cursor Previous Line Ps Times (default = 1) (CPL). @@ -695,7 +689,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.buffer.x = 0; } - /** * CSI Ps G * Cursor Character Absolute [column] (default = [row,1]) (CHA). @@ -716,6 +709,68 @@ export class InputHandler extends Disposable implements IInputHandler { (params.params[0] || 1) - 1); } + /** + * CSI Pm ` Character Position Absolute + * [column] (default = [row,1]) (HPA). + * Currently same functionality as CHA. + */ + public charPosAbsolute(params: IParams): void { + this._setCursor((params.params[0] || 1) - 1, this._terminal.buffer.y); + } + + /** + * CSI Pm a Character Position Relative + * [columns] (default = [row,col+1]) (HPR) + * Currently same functionality as CUF. + */ + public hPositionRelative(params: IParams): void { + this._moveCursor(params.params[0] || 1, 0); + } + + /** + * CSI Pm d Vertical Position Absolute (VPA) + * [row] (default = [1,column]) + */ + public linePosAbsolute(params: IParams): void { + this._setCursor(this._terminal.buffer.x, (params.params[0] || 1) - 1); + } + + /** + * CSI Pm e Vertical Position Relative (VPR) + * [rows] (default = [row+1,column]) + * reuse CSI Ps B ? + */ + public vPositionRelative(params: IParams): void { + this._moveCursor(0, params.params[0] || 1); + } + + /** + * CSI Ps ; Ps f + * Horizontal and Vertical Position [row;column] (default = + * [1,1]) (HVP). + * Same as CUP. + */ + public hVPosition(params: IParams): void { + this.cursorPosition(params); + } + + /** + * CSI Ps g Tab Clear (TBC). + * Ps = 0 -> Clear Current Column (default). + * Ps = 3 -> Clear All. + * Potentially: + * Ps = 2 -> Clear Stops on Line. + * http://vt100.net/annarbor/aaa-ug/section6.html + */ + public tabClear(params: IParams): void { + const param = params.params[0]; + if (param === 0) { + delete this._terminal.buffer.tabs[this._terminal.buffer.x]; + } else if (param === 3) { + this._terminal.buffer.tabs = {}; + } + } + /** * CSI Ps I * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). @@ -730,6 +785,24 @@ export class InputHandler extends Disposable implements IInputHandler { } } + /** + * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). + */ + public cursorBackwardTab(params: IParams): void { + if (this._terminal.buffer.x >= this._terminal.cols) { + return; + } + let param = params.params[0] || 1; + + // make buffer local for faster access + const buffer = this._terminal.buffer; + + while (param--) { + buffer.x = buffer.prevStop(); + } + } + + /** * Helper method to erase cells in a terminal row. * The cell gets replaced with the eraseChar of the terminal. @@ -913,6 +986,23 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? } + /** + * CSI Ps @ + * Insert Ps (Blank) Character(s) (default = 1) (ICH). + */ + public insertChars(params: IParams): void { + this._restrictCursor(); + const line = this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase); + if (line) { + line.insertCells( + this._terminal.buffer.x, + params.params[0] || 1, + this._terminal.buffer.getNullCell(this._terminal.eraseAttrData()) + ); + this._terminal.updateRange(this._terminal.buffer.y); + } + } + /** * CSI Ps P * Delete Ps Character(s) (default = 1) (DCH). @@ -985,41 +1075,6 @@ export class InputHandler extends Disposable implements IInputHandler { } } - /** - * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). - */ - public cursorBackwardTab(params: IParams): void { - if (this._terminal.buffer.x >= this._terminal.cols) { - return; - } - let param = params.params[0] || 1; - - // make buffer local for faster access - const buffer = this._terminal.buffer; - - while (param--) { - buffer.x = buffer.prevStop(); - } - } - - /** - * CSI Pm ` Character Position Absolute - * [column] (default = [row,1]) (HPA). - * Currently same functionality as CHA. - */ - public charPosAbsolute(params: IParams): void { - this._setCursor((params.params[0] || 1) - 1, this._terminal.buffer.y); - } - - /** - * CSI Pm a Character Position Relative - * [columns] (default = [row,col+1]) (HPR) - * Currently same functionality as CUF. - */ - public hPositionRelative(params: IParams): void { - this._moveCursor(params.params[0] || 1, 0); - } - /** * CSI Ps b Repeat the preceding graphic character Ps times (REP). * From ECMA 48 (@see http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf) @@ -1121,50 +1176,6 @@ export class InputHandler extends Disposable implements IInputHandler { } } - /** - * CSI Pm d Vertical Position Absolute (VPA) - * [row] (default = [1,column]) - */ - public linePosAbsolute(params: IParams): void { - this._setCursor(this._terminal.buffer.x, (params.params[0] || 1) - 1); - } - - /** - * CSI Pm e Vertical Position Relative (VPR) - * [rows] (default = [row+1,column]) - * reuse CSI Ps B ? - */ - public vPositionRelative(params: IParams): void { - this._moveCursor(0, params.params[0] || 1); - } - - /** - * CSI Ps ; Ps f - * Horizontal and Vertical Position [row;column] (default = - * [1,1]) (HVP). - * Same as CUP. - */ - public hVPosition(params: IParams): void { - this.cursorPosition(params); - } - - /** - * CSI Ps g Tab Clear (TBC). - * Ps = 0 -> Clear Current Column (default). - * Ps = 3 -> Clear All. - * Potentially: - * Ps = 2 -> Clear Stops on Line. - * http://vt100.net/annarbor/aaa-ug/section6.html - */ - public tabClear(params: IParams): void { - const param = params.params[0]; - if (param === 0) { - delete this._terminal.buffer.tabs[this._terminal.buffer.x]; - } else if (param === 3) { - this._terminal.buffer.tabs = {}; - } - } - /** * CSI Pm h Set Mode (SM). * Ps = 2 -> Keyboard Action Mode (AM). @@ -1280,6 +1291,7 @@ export class InputHandler extends Disposable implements IInputHandler { // set VT100 mode here break; case 3: // 132 col mode + // TODO: move DECCOLM into compat addon this._terminal.savedCols = this._terminal.cols; this._terminal.resize(132, this._terminal.rows); this._terminal.reset(); @@ -1478,6 +1490,9 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreService.decPrivateModes.applicationCursorKeys = false; break; case 3: + // TODO: move DECCOLM into compat addon + // Note: This impl currently does not enforce col 80, instead reverts + // to previous terminal width before entering DECCOLM 132 if (this._terminal.cols === 132 && this._terminal.savedCols) { this._terminal.resize(this._terminal.savedCols, this._terminal.rows); } @@ -2140,6 +2155,8 @@ export class InputHandler extends Disposable implements IInputHandler { * DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html) * This control function fills the complete screen area with * a test pattern (E) used for adjusting screen alignment. + * + * TODO: move DECALN into compat addon */ public screenAlignmentPattern(): void { // prepare cell data diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index e1af13fc..11728cd9 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -151,9 +151,6 @@ if (os.platform() !== 'win32') { ); } for (let i = 0; i < files.length; i++) { - // if (skip.indexOf(i) >= 0) { - // continue; - // } if (skipFilename.indexOf(files[i].split('/').slice(-1)[0]) >= 0) { continue; } From 9d2418ece63fbf7ffa831e0b1a47f3f8243b6ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 9 Jul 2019 21:41:17 +0200 Subject: [PATCH 27/31] remove comment --- src/InputHandler.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 5d8c4922..5e34c8b6 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -580,14 +580,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.setgLevel(0); } - /** - * Cursor movements. - * - * TODO: - * - create Cursor class living on Buffer - * - move private cursor methods to Cursor class as API - */ - /** * Restrict cursor to viewport size / scroll margin (origin mode). */ From ab25997192422dcd5ed0b0d5369818fced222d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 9 Jul 2019 22:30:00 +0200 Subject: [PATCH 28/31] change to max/min --- src/InputHandler.test.ts | 2 ++ src/InputHandler.ts | 23 ++++------------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 6d4a3ca8..d5f0ecd6 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -25,6 +25,8 @@ function getCursor(term: TestTerminal): number[] { describe('InputHandler', () => { describe('save and restore cursor', () => { const terminal = new MockInputHandlingTerminal(); + terminal.cols = 80; + terminal.rows = 30; terminal.buffer.x = 1; terminal.buffer.y = 2; terminal.buffer.ybase = 0; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 5e34c8b6..150ed8bb 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -584,25 +584,10 @@ export class InputHandler extends Disposable implements IInputHandler { * Restrict cursor to viewport size / scroll margin (origin mode). */ private _restrictCursor(): void { - // cols - if (this._terminal.buffer.x < 0) { - this._terminal.buffer.x = 0; - } else if (this._terminal.buffer.x >= this._terminal.cols) { - this._terminal.buffer.x = this._terminal.cols - 1; - } - // rows - if (this._terminal.buffer.y < 0) { - this._terminal.buffer.y = 0; - } else if (this._terminal.buffer.y >= this._terminal.rows) { - this._terminal.buffer.y = this._terminal.rows - 1; - } - if (this._terminal.originMode) { - if (this._terminal.buffer.y < this._terminal.buffer.scrollTop) { - this._terminal.buffer.y = this._terminal.buffer.scrollTop; - } else if (this._terminal.buffer.y > this._terminal.buffer.scrollBottom) { - this._terminal.buffer.y = this._terminal.buffer.scrollBottom; - } - } + this._terminal.buffer.x = Math.min(this._terminal.cols - 1, Math.max(0, this._terminal.buffer.x)); + this._terminal.buffer.y = this._terminal.originMode + ? Math.min(this._terminal.buffer.scrollBottom, Math.max(this._terminal.buffer.scrollTop, this._terminal.buffer.y)) + : Math.min(this._terminal.rows - 1, Math.max(0, this._terminal.buffer.y)); } /** From cc3bb83ced8c5d935bacc955ad8584d39b73e06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 10 Jul 2019 14:57:49 +0200 Subject: [PATCH 29/31] getting rid of native access in test file --- src/Terminal2.test.ts | 257 ++++++++++++++++++------------------------ 1 file changed, 112 insertions(+), 145 deletions(-) diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 11728cd9..178a669d 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -1,45 +1,126 @@ /** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT - * - * This file contains integration tests for xterm.js. */ import * as glob from 'glob'; -import * as fs from 'fs'; -import * as os from 'os'; import * as path from 'path'; +import * as os from 'os'; +import * as fs from 'fs'; import * as pty from 'node-pty'; import { Terminal } from './Terminal'; -import { IViewport } from './Types'; -import { CellData } from 'common/buffer/CellData'; -import { WHITESPACE_CELL_CHAR } from 'common/buffer/Constants'; -class TestTerminal extends Terminal { - innerWrite(): void { this._innerWrite(); } +// all test files expect terminal in 80x25 +const COLS = 80; +const ROWS = 25; + +const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')}); +const SKIP_FILES = [ + // 't0008-BS.in', + // 't0014-CAN.in', + // 't0015-SUB.in', + // 't0017-SD.in', + // 't0035-HVP.in', + // 't0050-ICH.in', + // 't0051-IL.in', + // 't0052-DL.in', + // 't0055-EL.in', + // 't0056-ED.in', + // 't0060-DECSC.in', + // 't0061-CSI_s.in', + 't0070-DECSTBM_LF.in', // lineFeed not working correctly + 't0071-DECSTBM_IND.in', + 't0072-DECSTBM_NEL.in', + // 't0074-DECSTBM_SU_SD.in', + 't0075-DECSTBM_CUU_CUD.in', + 't0076-DECSTBM_IL_DL.in', // not working due to lineFeed + 't0077-DECSTBM_quirks.in', + // 't0080-HT.in', + // 't0082-HTS.in', + // 't0083-CHT.in', + 't0084-CBT.in', + // 't0090-alt_screen.in', + // 't0091-alt_screen_ED3.in', + // 't0092-alt_screen_DECSC.in', + // 't0100-IRM.in', + 't0101-NLM.in', + 't0103-reverse_wrap.in', + 't0504-vim.in' + + // vttest related files + // 't0300-vttest1.in' +]; +if (os.platform() === 'darwin') { + // These are failing on macOS only (termios related?) + SKIP_FILES.push( + 't0003-line_wrap.in', + 't0005-CR.in', + 't0009-NEL.in', + 't0503-zsh_ls_color.in' + ); } +// filter skipFilenames +const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1); -let primitivePty: any; -// fake sychronous pty write - read -// we just pipe the data from slave to master as a child program would do -// pty.js opens pipe fds with O_NONBLOCK -// just wait 10ms instead of setting fds to blocking mode -function ptyWriteRead(data: string, cb: (result: string) => void): void { - fs.writeSync(primitivePty.slave, data); - setTimeout(() => { - const b = new Buffer(64000); - const bytes = fs.readSync(primitivePty.master, b, 0, 64000, null); - cb(b.toString('utf8', 0, bytes)); +describe('Escape Sequence Files', function(): void { + this.timeout(20000); + + let ptyTerm: any = null; + let slaveEnd: any = null; + let term: Terminal; + let customHandler: any = null; + + before(() => { + ptyTerm = (pty as any).open({cols: COLS, rows: ROWS}); + slaveEnd = ptyTerm._slave; + term = new Terminal({cols: COLS, rows: ROWS}); + ptyTerm._master.on('data', (data: string) => term.write(data)); }); -} -// make sure raw pty is at x=0 and has no pending data -function ptyReset(cb: (result: string) => void): void { - ptyWriteRead('\r\n', cb); -} + after(() => { + ptyTerm.end(); + }); + + FILES.forEach(filename => { + it(filename.split('/').slice(-1)[0], async () => { + // reset terminal and handler + if (customHandler) { + customHandler.dispose(); + } + slaveEnd.write('\r\n'); + term.reset(); + slaveEnd.write('\x1bc\x1b[H'); + + // register handler to trigger viewport scraping, wait for it to finish + let content = ''; + await new Promise(resolve => { + customHandler = term.addOscHandler(12345, () => { + // grab terminal viewport content + content = terminalToString(term); + resolve(); + return true; + }); + // write file to slave + slaveEnd.write(fs.readFileSync(filename, 'utf8')); + // trigger custom sequence + slaveEnd.write('\x1b]12345;\x07'); + }); + + // compare with expected output (right trimmed) + const expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8'); + const expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n'); + if (content !== expectedRightTrimmed) { + throw new Error(formatError(fs.readFileSync(filename, 'utf8'), content, expected)); + } + }); + }); +}); + +/** + * Helpers + */ -/* debug helpers */ // generate colorful noisy output to compare xterm and emulator cell states function formatError(input: string, output: string, expected: string): string { function addLineNumber(start: number, color: string): (s: string) => string { @@ -51,10 +132,10 @@ function formatError(input: string, output: string, expected: string): string { } const line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890'; let s = ''; - s += '\n\x1b[34m' + JSON.stringify(input); - s += '\n\x1b[33m ' + line80 + '\n'; + s += `\n\x1b[34m${JSON.stringify(input)}`; + s += `\n\x1b[33m ${line80}\n`; s += output.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n'); - s += '\n\x1b[33m ' + line80 + '\n'; + s += `\n\x1b[33m ${line80}\n`; s += expected.split('\n').map(addLineNumber(0, '\x1b[32m')).join('\n'); return s; } @@ -64,10 +145,7 @@ function terminalToString(term: Terminal): string { let result = ''; let lineText = ''; for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) { - lineText = ''; - for (let cell = 0; cell < term.cols; ++cell) { - lineText += term.buffer.lines.get(line).loadCell(cell, new CellData()).getChars() || WHITESPACE_CELL_CHAR; - } + lineText = term.buffer.lines.get(line).translateToString(true); // rtrim empty cells as xterm does lineText = lineText.replace(/\s+$/, ''); result += lineText; @@ -75,114 +153,3 @@ function terminalToString(term: Terminal): string { } return result; } - -// Skip tests on Windows since pty.open isn't supported -if (os.platform() !== 'win32') { - const consoleLog = console.log; - - // expect files need terminal at 80x25! - const cols = 80; - const rows = 25; - - /** some helpers for pty interaction */ - // we need a pty in between to get the termios decorations - // for the basic test cases a raw pty device is enough - primitivePty = (pty).native.open(cols, rows); - - /** tests */ - describe('xterm output comparison', function(): void { - this.timeout(10000); - let xterm: TestTerminal; - - beforeEach(() => { - xterm = new TestTerminal({ cols: cols, rows: rows }); - xterm.refresh = () => {}; - xterm.viewport = { - syncScrollArea: () => {} - }; - }); - - // omit stack trace for escape sequence files - Error.stackTraceLimit = 0; - const files = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')}); - // only successful tests for now - const skipFilename = [ - // 't0008-BS.in', - // 't0014-CAN.in', - // 't0015-SUB.in', - // 't0017-SD.in', - // 't0035-HVP.in', - // 't0050-ICH.in', - // 't0051-IL.in', - // 't0052-DL.in', - // 't0055-EL.in', - // 't0056-ED.in', - // 't0060-DECSC.in', - // 't0061-CSI_s.in', - 't0070-DECSTBM_LF.in', // lineFeed not working correctly - 't0071-DECSTBM_IND.in', - 't0072-DECSTBM_NEL.in', - // 't0074-DECSTBM_SU_SD.in', - 't0075-DECSTBM_CUU_CUD.in', - 't0076-DECSTBM_IL_DL.in', // not working due to lineFeed - 't0077-DECSTBM_quirks.in', - // 't0080-HT.in', - // 't0082-HTS.in', - // 't0083-CHT.in', - 't0084-CBT.in', - // 't0090-alt_screen.in', - // 't0091-alt_screen_ED3.in', - // 't0092-alt_screen_DECSC.in', - // 't0100-IRM.in', - 't0101-NLM.in', - 't0103-reverse_wrap.in', - 't0504-vim.in' - - // vttest related files - // 't0300-vttest1.in' - ]; - if (os.platform() === 'darwin') { - // These are failing on macOS only - skipFilename.push( - 't0003-line_wrap.in', - 't0005-CR.in', - 't0009-NEL.in', - 't0503-zsh_ls_color.in' - ); - } - for (let i = 0; i < files.length; i++) { - if (skipFilename.indexOf(files[i].split('/').slice(-1)[0]) >= 0) { - continue; - } - ((filename: string) => { - const inFile = fs.readFileSync(filename, 'utf8'); - it(filename.split('/').slice(-1)[0], done => { - ptyReset(() => { - ptyWriteRead(inFile, fromPty => { - // uncomment this to get log from terminal - // console.log = function(){}; - - // Perform a synchronous .write(data) - xterm.writeBuffer.push(fromPty); - xterm.innerWrite(); - - const fromEmulator = terminalToString(xterm); - console.log = consoleLog; - const expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8'); - - // Some of the tests have whitespace on the right of lines, we trim all the linex - // from xterm.js so ignore this for now at least. - const expectedRightTrimmed = expected.split('\n').map(l => l.replace(/\s+$/, '')).join('\n'); - if (fromEmulator !== expectedRightTrimmed) { - // uncomment to get noisy output - throw new Error(formatError(inFile, fromEmulator, expected)); - // throw new Error('mismatch'); - } - done(); - }); - }); - }); - })(files[i]); - } - }); -} From 24b26c73118776bfd9e995bf0cc29363830cc9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 10 Jul 2019 23:39:05 +0200 Subject: [PATCH 30/31] force closing of pty --- src/Terminal2.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 178a669d..ad306f32 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -79,7 +79,8 @@ describe('Escape Sequence Files', function(): void { }); after(() => { - ptyTerm.end(); + ptyTerm._master.end(); + ptyTerm._master.destroy(); }); FILES.forEach(filename => { From eb707bb1d7a1473635c4e0052c709304c9675ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 11 Jul 2019 02:41:20 +0200 Subject: [PATCH 31/31] better typing in test file --- src/Terminal2.test.ts | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index ad306f32..cbd790d5 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -9,6 +9,7 @@ import * as os from 'os'; import * as fs from 'fs'; import * as pty from 'node-pty'; import { Terminal } from './Terminal'; +import { IDisposable } from 'xterm'; // all test files expect terminal in 80x25 const COLS = 80; @@ -16,39 +17,16 @@ const ROWS = 25; const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')}); const SKIP_FILES = [ - // 't0008-BS.in', - // 't0014-CAN.in', - // 't0015-SUB.in', - // 't0017-SD.in', - // 't0035-HVP.in', - // 't0050-ICH.in', - // 't0051-IL.in', - // 't0052-DL.in', - // 't0055-EL.in', - // 't0056-ED.in', - // 't0060-DECSC.in', - // 't0061-CSI_s.in', 't0070-DECSTBM_LF.in', // lineFeed not working correctly 't0071-DECSTBM_IND.in', 't0072-DECSTBM_NEL.in', - // 't0074-DECSTBM_SU_SD.in', 't0075-DECSTBM_CUU_CUD.in', 't0076-DECSTBM_IL_DL.in', // not working due to lineFeed 't0077-DECSTBM_quirks.in', - // 't0080-HT.in', - // 't0082-HTS.in', - // 't0083-CHT.in', 't0084-CBT.in', - // 't0090-alt_screen.in', - // 't0091-alt_screen_ED3.in', - // 't0092-alt_screen_DECSC.in', - // 't0100-IRM.in', 't0101-NLM.in', 't0103-reverse_wrap.in', 't0504-vim.in' - - // vttest related files - // 't0300-vttest1.in' ]; if (os.platform() === 'darwin') { // These are failing on macOS only (termios related?) @@ -66,10 +44,10 @@ const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slic describe('Escape Sequence Files', function(): void { this.timeout(20000); - let ptyTerm: any = null; - let slaveEnd: any = null; + let ptyTerm: any; + let slaveEnd: any; let term: Terminal; - let customHandler: any = null; + let customHandler: IDisposable | undefined; before(() => { ptyTerm = (pty as any).open({cols: COLS, rows: ROWS}); @@ -95,8 +73,9 @@ describe('Escape Sequence Files', function(): void { // register handler to trigger viewport scraping, wait for it to finish let content = ''; + const OSC_CODE = 12345; await new Promise(resolve => { - customHandler = term.addOscHandler(12345, () => { + customHandler = term.addOscHandler(OSC_CODE, () => { // grab terminal viewport content content = terminalToString(term); resolve(); @@ -105,7 +84,7 @@ describe('Escape Sequence Files', function(): void { // write file to slave slaveEnd.write(fs.readFileSync(filename, 'utf8')); // trigger custom sequence - slaveEnd.write('\x1b]12345;\x07'); + slaveEnd.write(`\x1b]${OSC_CODE};\x07`); }); // compare with expected output (right trimmed)