diff --git a/src/browser/Terminal2.test.ts b/src/browser/Terminal2.test.ts index 44540c6e2..4965e8f36 100644 --- a/src/browser/Terminal2.test.ts +++ b/src/browser/Terminal2.test.ts @@ -17,6 +17,7 @@ const ROWS = 25; const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '../..')}); const SKIP_FILES = [ + 't0055-EL.in', // EL/ED handle cursor at cols differently (see #3362) 't0084-CBT.in', 't0101-NLM.in', 't0103-reverse_wrap.in', // not comparable, we deviate from xterm reverse wrap on purpose diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index 676b1e453..902640f90 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -1913,6 +1913,92 @@ describe('InputHandler', () => { }); }); }); + + // issue #3362 and #2979 + describe('EL/ED cursor at buffer.cols', () => { + beforeEach(() => { + bufferService.resize(10, 5); + }); + describe('cursor should stay at cols / does not overflow', () => { + it('EL0', async () => { + await inputHandler.parseP('##########\x1b[0K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + it('EL1', async () => { + await inputHandler.parseP('##########\x1b[1K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('EL2', async () => { + await inputHandler.parseP('##########\x1b[2K'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED0', async () => { + await inputHandler.parseP('##########\x1b[0J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + it('ED1', async () => { + await inputHandler.parseP('##########\x1b[1J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED2', async () => { + await inputHandler.parseP('##########\x1b[2J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['', '', '', '', '']); + }); + it('ED3', async () => { + await inputHandler.parseP('##########\x1b[3J'); + assert.equal(bufferService.buffer.x, 10); + assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']); + }); + }); + describe('following sequence keeps working', () => { + // sequences to test (cursor related ones) + const SEQ = [ + /* ICH */ '\x1b[10@', + /* SL */ '\x1b[10 @', + /* CUU */ '\x1b[10A', + /* SR */ '\x1b[10 A', + /* CUD */ '\x1b[10B', + /* CUF */ '\x1b[10C', + /* CUB */ '\x1b[10D', + /* CNL */ '\x1b[10E', + /* CPL */ '\x1b[10F', + /* CHA */ '\x1b[10G', + /* CUP */ '\x1b[10;10H', + /* CHT */ '\x1b[10I', + /* IL */ '\x1b[10L', + /* DL */ '\x1b[10M', + /* DCH */ '\x1b[10P', + /* SU */ '\x1b[10S', + /* SD */ '\x1b[10T', + /* ECH */ '\x1b[10X', + /* CBT */ '\x1b[10Z', + /* HPA */ '\x1b[10`', + /* HPR */ '\x1b[10a', + /* REP */ '\x1b[10b', + /* VPA */ '\x1b[10d', + /* VPR */ '\x1b[10e', + /* HVP */ '\x1b[10;10f', + /* TBC */ '\x1b[0g', + /* SCOSC */ '\x1b[s', + /* DECIC */ '\x1b[10\'}', + /* DECDC */ '\x1b[10\'~' + ]; + it('cursor never advances beyond cols', async () => { + for (const seq of SEQ) { + await inputHandler.parseP('##########\x1b[2J' + seq); + assert.equal(bufferService.buffer.x <= bufferService.cols, true); + inputHandler.reset(); + bufferService.reset(); + } + }); + }); + }); }); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index a2b5a7829..d3b29a702 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -1247,7 +1247,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #P[Protection attributes are not supported.] CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED." */ public eraseInDisplay(params: IParams): boolean { - this._restrictCursor(); + this._restrictCursor(this._bufferService.cols); let j; switch (params.params[0]) { case 0: @@ -1319,7 +1319,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #P[Protection attributes are not supported.] CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL." */ public eraseInLine(params: IParams): boolean { - this._restrictCursor(); + this._restrictCursor(this._bufferService.cols); switch (params.params[0]) { case 0: this._eraseInBufferLine(this._bufferService.buffer.y, this._bufferService.buffer.x, this._bufferService.cols);