From ee6fa6ca002e84ee6ce6587b4a47664098e383e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 31 Oct 2018 12:42:56 +0100 Subject: [PATCH 01/15] change NULL_CHAR and trim --- src/Buffer.ts | 11 +++++++--- src/BufferLine.ts | 44 ++++++++++++++++++++++++++++++++++++- src/Terminal.integration.ts | 2 +- src/Types.ts | 2 ++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index e54f752b1c..7c5d2652a3 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -16,9 +16,13 @@ export const CHAR_DATA_WIDTH_INDEX = 2; export const CHAR_DATA_CODE_INDEX = 3; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 -export const NULL_CELL_CHAR = ' '; -export const NULL_CELL_WIDTH = 1; -export const NULL_CELL_CODE = 32; +// export const NULL_CELL_CHAR = ' '; +// export const NULL_CELL_WIDTH = 1; +// export const NULL_CELL_CODE = 32; + +export const NULL_CELL_CHAR = ''; +export const NULL_CELL_WIDTH = 0; +export const NULL_CELL_CODE = 0; /** * This class represents a terminal buffer (an internal state of the terminal), where the @@ -273,6 +277,7 @@ export class Buffer implements IBuffer { if (!line) { return ''; } + return line.translateToString(trimRight, startCol, endCol); // Initialize column and index values. Column values represent the actual // cell column, indexes represent the index in the string. Indexes are diff --git a/src/BufferLine.ts b/src/BufferLine.ts index f1ea9cf064..e1cb6a4fdc 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -3,7 +3,7 @@ * @license MIT */ import { CharData, IBufferLine } from './Types'; -import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; +import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; /** * Class representing a terminal line. @@ -108,6 +108,27 @@ export class BufferLine implements IBufferLine { newLine.copyFrom(this); return newLine; } + + public getTrimmedLength(): number { + for (let i = this.length - 1; i >= 0; --i) { + const ch = this.get(i); + if (ch[CHAR_DATA_CHAR_INDEX] !== '') { + return i + ch[CHAR_DATA_WIDTH_INDEX] - 1; + } + } + return 0; + } + + public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = null): string { + let length = endCol || this.length; + if (trimRight) + length = Math.min(length, this.getTrimmedLength()); + let result = ''; + for (let i = startCol; i < length; ++i) { + result += this.get(i)[CHAR_DATA_CHAR_INDEX] || ' '; + } + return result; + } } /** typed array slots taken by one cell */ @@ -279,4 +300,25 @@ export class BufferLineTypedArray implements IBufferLine { newLine.isWrapped = this.isWrapped; return newLine; } + + public getTrimmedLength(): number { + for (let i = this.length - 1; i >= 0; --i) { + if (this._data[i * CELL_SIZE + Cell.STRING] !== 0) { // 0 ==> ''.charCodeAt(0) ==> NaN ==> 0 + return i + 1; + } + } + return 0; + } + + public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = null): string { + let length = endCol || this.length; + if (trimRight) + length = Math.min(length, this.getTrimmedLength()); + let result = ''; + for (let i = startCol; i < length; ++i) { + const stringData = this._data[i * CELL_SIZE + Cell.STRING]; + result += (stringData & 0x80000000) ? this._combined[i] : (stringData) ? String.fromCharCode(stringData) : ' '; + } + return result; + } } diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 66fd350298..21c4a2d04e 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -67,7 +67,7 @@ function terminalToString(term: Terminal): string { 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).get(cell)[CHAR_DATA_CHAR_INDEX]; + lineText += term.buffer.lines.get(line).get(cell)[CHAR_DATA_CHAR_INDEX] || ' '; } // rtrim empty cells as xterm does lineText = lineText.replace(/\s+$/, ''); diff --git a/src/Types.ts b/src/Types.ts index e857842616..b0ccf88d59 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -524,6 +524,8 @@ export interface IBufferLine { fill(fillCharData: CharData): void; copyFrom(line: IBufferLine): void; clone(): IBufferLine; + getTrimmedLength(): number; + translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string; } export interface IBufferLineConstructor { From 62f1d64af4c7a1eea239309867ca890426be3273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 31 Oct 2018 14:26:54 +0100 Subject: [PATCH 02/15] fix trimmedLength for TypedArray --- src/BufferLine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index e1cb6a4fdc..54207142f9 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -304,7 +304,7 @@ export class BufferLineTypedArray implements IBufferLine { public getTrimmedLength(): number { for (let i = this.length - 1; i >= 0; --i) { if (this._data[i * CELL_SIZE + Cell.STRING] !== 0) { // 0 ==> ''.charCodeAt(0) ==> NaN ==> 0 - return i + 1; + return i + this._data[i * CELL_SIZE + Cell.WIDTH] - 1; } } return 0; From 1c111d3e6060ad4f28e23ab87d33166327cd6a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 31 Oct 2018 14:53:55 +0100 Subject: [PATCH 03/15] skip empty cells after fullwidth --- src/BufferLine.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 54207142f9..c4b625884e 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -124,8 +124,9 @@ export class BufferLine implements IBufferLine { if (trimRight) length = Math.min(length, this.getTrimmedLength()); let result = ''; - for (let i = startCol; i < length; ++i) { - result += this.get(i)[CHAR_DATA_CHAR_INDEX] || ' '; + while (startCol < endCol) { + result += this.get(startCol)[CHAR_DATA_CHAR_INDEX] || ' '; + startCol += this.get(startCol)[CHAR_DATA_WIDTH_INDEX] || 1; } return result; } @@ -315,9 +316,10 @@ export class BufferLineTypedArray implements IBufferLine { if (trimRight) length = Math.min(length, this.getTrimmedLength()); let result = ''; - for (let i = startCol; i < length; ++i) { - const stringData = this._data[i * CELL_SIZE + Cell.STRING]; - result += (stringData & 0x80000000) ? this._combined[i] : (stringData) ? String.fromCharCode(stringData) : ' '; + while (startCol < endCol) { + const stringData = this._data[startCol * CELL_SIZE + Cell.STRING]; + result += (stringData & 0x80000000) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : ' '; + startCol += this._data[startCol * CELL_SIZE + Cell.WIDTH] || 1; } return result; } From 8ffcd65cff8529b1ad36ef5a2a0a7e8dc5d00cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 31 Oct 2018 15:11:18 +0100 Subject: [PATCH 04/15] fix buffer tests --- src/Buffer.test.ts | 6 +++--- src/Buffer.ts | 5 ++--- src/BufferLine.ts | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index db8a460d09..bb1a08d8ed 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -509,10 +509,10 @@ describe('Buffer', () => { // --> fixable after resolving #1685 terminal.writeSync(input); // TODO: reenable after fix - // const s = terminal.buffer.contents(true).toArray()[0]; - // assert.equal(input, s); + const s = terminal.buffer.iterator(true).next().content; + assert.equal(input, s); for (let i = 10; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i + 1); // TODO: remove +1 after fix + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); // TODO: remove +1 after fix const j = (i - 0) << 1; assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex); } diff --git a/src/Buffer.ts b/src/Buffer.ts index 7c5d2652a3..39dc5d57da 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -21,7 +21,7 @@ export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 // export const NULL_CELL_CODE = 32; export const NULL_CELL_CHAR = ''; -export const NULL_CELL_WIDTH = 0; +export const NULL_CELL_WIDTH = 1; export const NULL_CELL_CODE = 0; /** @@ -488,8 +488,7 @@ export class BufferStringIterator implements IBufferStringIterator { range.last = Math.min(range.last, this._buffer.lines.length); let result = ''; for (let i = range.first; i <= range.last; ++i) { - // TODO: always apply trimRight after fixing #1685 - result += this._buffer.translateBufferLineToString(i, (this._trimRight) ? i === range.last : false); + result += this._buffer.translateBufferLineToString(i, this._trimRight); } this._current = range.last + 1; return {range: range, content: result}; diff --git a/src/BufferLine.ts b/src/BufferLine.ts index c4b625884e..e35b67d677 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -113,7 +113,7 @@ export class BufferLine implements IBufferLine { for (let i = this.length - 1; i >= 0; --i) { const ch = this.get(i); if (ch[CHAR_DATA_CHAR_INDEX] !== '') { - return i + ch[CHAR_DATA_WIDTH_INDEX] - 1; + return i + ch[CHAR_DATA_WIDTH_INDEX]; } } return 0; @@ -124,7 +124,7 @@ export class BufferLine implements IBufferLine { if (trimRight) length = Math.min(length, this.getTrimmedLength()); let result = ''; - while (startCol < endCol) { + while (startCol < length) { result += this.get(startCol)[CHAR_DATA_CHAR_INDEX] || ' '; startCol += this.get(startCol)[CHAR_DATA_WIDTH_INDEX] || 1; } @@ -305,7 +305,7 @@ export class BufferLineTypedArray implements IBufferLine { public getTrimmedLength(): number { for (let i = this.length - 1; i >= 0; --i) { if (this._data[i * CELL_SIZE + Cell.STRING] !== 0) { // 0 ==> ''.charCodeAt(0) ==> NaN ==> 0 - return i + this._data[i * CELL_SIZE + Cell.WIDTH] - 1; + return i + this._data[i * CELL_SIZE + Cell.WIDTH]; } } return 0; @@ -316,7 +316,7 @@ export class BufferLineTypedArray implements IBufferLine { if (trimRight) length = Math.min(length, this.getTrimmedLength()); let result = ''; - while (startCol < endCol) { + while (startCol < length) { const stringData = this._data[startCol * CELL_SIZE + Cell.STRING]; result += (stringData & 0x80000000) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : ' '; startCol += this._data[startCol * CELL_SIZE + Cell.WIDTH] || 1; From 745e228e982d5092ac64202e2f4e51848ca9dbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 31 Oct 2018 15:26:40 +0100 Subject: [PATCH 05/15] fix input handler and terminal tests --- src/InputHandler.test.ts | 10 +++++----- src/Terminal.test.ts | 42 ++++++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index aaaf57c3f6..46f8795ad2 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -210,7 +210,7 @@ describe('InputHandler', () => { describe('regression tests', function(): void { function lineContent(line: IBufferLine): string { let content = ''; - for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX]; + for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX] || ' '; return content; } @@ -476,12 +476,12 @@ describe('InputHandler', () => { const termNotConverting = new Terminal({cols: 15, rows: 10}); (termNotConverting as any)._inputHandler.parse('Hello\nWorld'); for (let i = 0; i < termNotConverting.cols; ++i) { - s += termNotConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX]; + s += termNotConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX] || ' '; } expect(s).equals('Hello '); s = ''; for (let i = 0; i < termNotConverting.cols; ++i) { - s += termNotConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX]; + s += termNotConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX] || ' '; } expect(s).equals(' World '); @@ -490,12 +490,12 @@ describe('InputHandler', () => { const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true}); (termConverting as any)._inputHandler.parse('Hello\nWorld'); for (let i = 0; i < termConverting.cols; ++i) { - s += termConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX]; + s += termConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX] || ' '; } expect(s).equals('Hello '); s = ''; for (let i = 0; i < termConverting.cols; ++i) { - s += termConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX]; + s += termConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX] || ' '; } expect(s).equals('World '); }); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index fd59144c60..da4a39c090 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -462,7 +462,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.length, INIT_ROWS + 1); assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a'); assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(INIT_ROWS).get(0)[CHAR_DATA_CHAR_INDEX], ' '); + assert.equal(term.buffer.lines.get(INIT_ROWS).get(0)[CHAR_DATA_CHAR_INDEX], ''); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { @@ -491,7 +491,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'b'); assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'c'); assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index'); assert.equal(term.buffer.lines.get(5).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); }); @@ -509,7 +509,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a'); assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer'); assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index'); assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); }); }); @@ -530,9 +530,9 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.length, INIT_ROWS); // 'a' gets pushed out of buffer assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], ' '); + assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], ''); assert.equal(term.buffer.lines.get(INIT_ROWS - 2).get(0)[CHAR_DATA_CHAR_INDEX], 'c'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], ' '); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], ''); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { @@ -560,7 +560,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b'); assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c'); assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index'); assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); }); @@ -578,7 +578,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a'); assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer'); assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], '', 'a blank line should be added at scrollBottom\'s index'); assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); }); }); @@ -776,7 +776,7 @@ describe('term.js addons', () => { expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); term.reset(); } }); @@ -787,7 +787,7 @@ describe('term.js addons', () => { term.write(high + String.fromCharCode(i)); expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(''); term.reset(); } }); @@ -800,7 +800,7 @@ describe('term.js addons', () => { expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a'); expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); term.reset(); } }); @@ -813,7 +813,7 @@ describe('term.js addons', () => { // auto wraparound mode should cut off the rest of the line expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a'); expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); term.reset(); } }); @@ -826,7 +826,7 @@ describe('term.js addons', () => { expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); term.reset(); } }); @@ -845,8 +845,8 @@ describe('term.js addons', () => { expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2); expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' '); - expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX].length).eql(0); expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_WIDTH_INDEX]).eql(1); }); it('multiple combined é', () => { @@ -928,8 +928,8 @@ describe('term.js addons', () => { } } let tchar = term.buffer.lines.get(0).get(term.cols - 1); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' '); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); @@ -953,8 +953,8 @@ describe('term.js addons', () => { } } let tchar = term.buffer.lines.get(0).get(term.cols - 1); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' '); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); @@ -998,8 +998,8 @@ describe('term.js addons', () => { } } let tchar = term.buffer.lines.get(0).get(term.cols - 1); - expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' '); - expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); + expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); @@ -1063,7 +1063,7 @@ describe('term.js addons', () => { expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a'); expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(' '); // fullwidth char got replaced + expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(''); // fullwidth char got replaced term.write('b'); expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('b'); From cabd4a721c9cb73e0b694b6b1fe27beed040f4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 31 Oct 2018 15:30:29 +0100 Subject: [PATCH 06/15] fix dom renderer --- src/BufferLine.ts | 6 ++++-- src/renderer/dom/DomRendererRowFactory.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index e35b67d677..3bf7fec46e 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -121,8 +121,9 @@ export class BufferLine implements IBufferLine { public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = null): string { let length = endCol || this.length; - if (trimRight) + if (trimRight) { length = Math.min(length, this.getTrimmedLength()); + } let result = ''; while (startCol < length) { result += this.get(startCol)[CHAR_DATA_CHAR_INDEX] || ' '; @@ -313,8 +314,9 @@ export class BufferLineTypedArray implements IBufferLine { public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = null): string { let length = endCol || this.length; - if (trimRight) + if (trimRight) { length = Math.min(length, this.getTrimmedLength()); + } let result = ''; while (startCol < length) { const stringData = this._data[startCol * CELL_SIZE + Cell.STRING]; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 4bb5990223..ce94ce338c 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -31,7 +31,7 @@ export class DomRendererRowFactory { } const charData = lineData.get(x); - const char: string = charData[CHAR_DATA_CHAR_INDEX]; + const char: string = charData[CHAR_DATA_CHAR_INDEX] || ' '; const attr: number = charData[CHAR_DATA_ATTR_INDEX]; const width: number = charData[CHAR_DATA_WIDTH_INDEX]; From 200b59b47ca3f77b3b0652d7e95e9ed2898f26dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 31 Oct 2018 15:35:30 +0100 Subject: [PATCH 07/15] fix canvas renderer --- src/renderer/TextRenderLayer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 7f10e7c9e1..08850b72db 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -90,6 +90,11 @@ export class TextRenderLayer extends BaseRenderLayer { continue; } + // Simply skip empty cells... + if (!chars) { + continue; + } + // Process any joined character ranges as needed. Because of how the // ranges are produced, we know that they are valid for the characters // and attributes of our input. From 5395503031708bd12b2918239074fb0ea3ce6748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 31 Oct 2018 15:47:35 +0100 Subject: [PATCH 08/15] apply code and char replacement to renderer --- src/renderer/TextRenderLayer.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 08850b72db..24c948eb4a 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -72,11 +72,11 @@ export class TextRenderLayer extends BaseRenderLayer { const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; for (let x = 0; x < terminal.cols; x++) { const charData = line.get(x); - let code: number = charData[CHAR_DATA_CODE_INDEX]; + let code: number = charData[CHAR_DATA_CODE_INDEX] || 32; // Can either represent character(s) for a single cell or multiple cells // if indicated by a character joiner. - let chars: string = charData[CHAR_DATA_CHAR_INDEX]; + let chars: string = charData[CHAR_DATA_CHAR_INDEX] || ' '; const attr: number = charData[CHAR_DATA_ATTR_INDEX]; let width: number = charData[CHAR_DATA_WIDTH_INDEX]; @@ -90,11 +90,6 @@ export class TextRenderLayer extends BaseRenderLayer { continue; } - // Simply skip empty cells... - if (!chars) { - continue; - } - // Process any joined character ranges as needed. Because of how the // ranges are produced, we know that they are valid for the characters // and attributes of our input. From 67df9a5cdc84db7446cf73662f73f770a3ed1cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 8 Nov 2018 04:03:48 +0100 Subject: [PATCH 09/15] cleanup --- src/Buffer.test.ts | 3 +-- src/Buffer.ts | 54 ---------------------------------------------- src/BufferLine.ts | 11 ++++++---- 3 files changed, 8 insertions(+), 60 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index bb1a08d8ed..c1585a7cb6 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -508,11 +508,10 @@ describe('Buffer', () => { // the dangling last cell is wrongly added in the string // --> fixable after resolving #1685 terminal.writeSync(input); - // TODO: reenable after fix const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); for (let i = 10; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); // TODO: remove +1 after fix + const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); const j = (i - 0) << 1; assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex); } diff --git a/src/Buffer.ts b/src/Buffer.ts index 39dc5d57da..63b0f8d211 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -271,65 +271,11 @@ export class Buffer implements IBuffer { * @param endCol The column to end at. */ public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol: number = null): string { - // Get full line - let lineString = ''; const line = this.lines.get(lineIndex); if (!line) { return ''; } return line.translateToString(trimRight, startCol, endCol); - - // Initialize column and index values. Column values represent the actual - // cell column, indexes represent the index in the string. Indexes are - // needed here because some chars are 0 characters long (eg. after wide - // chars) and some chars are longer than 1 characters long (eg. emojis). - let startIndex = startCol; - // Only set endCol to the line length when it is null. 0 is a valid column. - if (endCol === null) { - endCol = line.length; - } - let endIndex = endCol; - - for (let i = 0; i < line.length; i++) { - const char = line.get(i); - lineString += char[CHAR_DATA_CHAR_INDEX]; - // Adjust start and end cols for wide characters if they affect their - // column indexes - if (char[CHAR_DATA_WIDTH_INDEX] === 0) { - if (startCol >= i) { - startIndex--; - } - if (endCol > i) { - endIndex--; - } - } else { - // Adjust the columns to take glyphs that are represented by multiple - // code points into account. - if (char[CHAR_DATA_CHAR_INDEX].length > 1) { - if (startCol > i) { - startIndex += char[CHAR_DATA_CHAR_INDEX].length - 1; - } - if (endCol > i) { - endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1; - } - } - } - } - - // Calculate the final end col by trimming whitespace on the right of the - // line if needed. - if (trimRight) { - const rightWhitespaceIndex = lineString.search(/\s+$/); - if (rightWhitespaceIndex !== -1) { - endIndex = Math.min(endIndex, rightWhitespaceIndex); - } - // Return the empty string if only trimmed whitespace is selected - if (endIndex <= startIndex) { - return ''; - } - } - - return lineString.substring(startIndex, endIndex); } public getWrappedRangeForLine(y: number): { first: number, last: number } { diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 3bf7fec46e..12a0bf5d0a 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -143,6 +143,9 @@ const enum Cell { WIDTH = 2 } +/** single vs. combined char distinction */ +const COMBINED = 0x80000000; + /** * Typed array based bufferline implementation. * Note: Unlike the JS variant the access to the data @@ -177,11 +180,11 @@ export class BufferLineTypedArray implements IBufferLine { const stringData = this._data[index * CELL_SIZE + Cell.STRING]; return [ this._data[index * CELL_SIZE + Cell.FLAGS], - (stringData & 0x80000000) + (stringData & COMBINED) ? this._combined[index] : (stringData) ? String.fromCharCode(stringData) : '', this._data[index * CELL_SIZE + Cell.WIDTH], - (stringData & 0x80000000) + (stringData & COMBINED) ? this._combined[index].charCodeAt(this._combined[index].length - 1) : stringData ]; @@ -191,7 +194,7 @@ export class BufferLineTypedArray implements IBufferLine { this._data[index * CELL_SIZE + Cell.FLAGS] = value[0]; if (value[1].length > 1) { this._combined[index] = value[1]; - this._data[index * CELL_SIZE + Cell.STRING] = index | 0x80000000; + this._data[index * CELL_SIZE + Cell.STRING] = index | COMBINED; } else { this._data[index * CELL_SIZE + Cell.STRING] = value[1].charCodeAt(0); } @@ -320,7 +323,7 @@ export class BufferLineTypedArray implements IBufferLine { let result = ''; while (startCol < length) { const stringData = this._data[startCol * CELL_SIZE + Cell.STRING]; - result += (stringData & 0x80000000) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : ' '; + result += (stringData & COMBINED) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : ' '; startCol += this._data[startCol * CELL_SIZE + Cell.WIDTH] || 1; } return result; From 2f9c04c3af9fe08b196856b93aee3a22a22a9bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 8 Nov 2018 05:00:08 +0100 Subject: [PATCH 10/15] cleanup test cases --- src/InputHandler.test.ts | 415 ++++++++++++--------------------------- 1 file changed, 124 insertions(+), 291 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 46f8795ad2..eacc54db67 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -6,130 +6,9 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal } from './utils/TestUtils.test'; -import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, CHAR_DATA_CHAR_INDEX } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './Types'; - -// TODO: This and the sections related to this object in associated tests can be -// removed safely after InputHandler refactors are finished -class OldInputHandler extends InputHandler { - public eraseInLine(params: number[]): void { - switch (params[0]) { - case 0: - this.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); - break; - case 1: - this.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); - break; - case 2: - this.eraseLine(this._terminal.buffer.y); - break; - } - } - - public eraseInDisplay(params: number[]): void { - let j; - switch (params[0]) { - case 0: - this.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y); - j = this._terminal.buffer.y + 1; - for (; j < this._terminal.rows; j++) { - this.eraseLine(j); - } - break; - case 1: - this.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y); - j = this._terminal.buffer.y; - while (j--) { - this.eraseLine(j); - } - break; - case 2: - j = this._terminal.rows; - while (j--) this.eraseLine(j); - break; - case 3: - // Clear scrollback (everything not in viewport) - const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows; - if (scrollBackSize > 0) { - this._terminal.buffer.lines.trimStart(scrollBackSize); - this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0); - this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0); - // Force a scroll event to refresh viewport - this._terminal.emit('scroll', 0); - } - break; - } - } - - /** - * Erase in the identified line everything from "x" to the end of the line (right). - * @param x The column from which to start erasing to the end of the line. - * @param y The line in which to operate. - */ - public eraseRight(x: number, y: number): void { - const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y); - if (!line) { - return; - } - line.replaceCells(x, this._terminal.cols, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); - this._terminal.updateRange(y); - } - - /** - * Erase in the identified line everything from "x" to the start of the line (left). - * @param x The column from which to start erasing to the start of the line. - * @param y The line in which to operate. - */ - public eraseLeft(x: number, y: number): void { - const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y); - if (!line) { - return; - } - line.replaceCells(0, x + 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); - this._terminal.updateRange(y); - } - - /** - * Erase all content in the given line - * @param y The line to erase all of its contents. - */ - public eraseLine(y: number): void { - this.eraseRight(0, y); - } - - public insertChars(params: number[]): void { - let param = params[0]; - if (param < 1) param = 1; - - // make buffer local for faster access - const buffer = this._terminal.buffer; - - const row = buffer.y + buffer.ybase; - let j = buffer.x; - while (param-- && j < this._terminal.cols) { - buffer.lines.get(row).insertCells(j++, 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); - } - } - - public deleteChars(params: number[]): void { - let param: number = params[0]; - if (param < 1) { - param = 1; - } - - // make buffer local for faster access - const buffer = this._terminal.buffer; - - const row = buffer.y + buffer.ybase; - while (param--) { - buffer.lines.get(row).deleteCells(buffer.x, 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); - } - this._terminal.updateRange(buffer.y); - } -} - describe('InputHandler', () => { describe('save and restore cursor', () => { const terminal = new MockInputHandlingTerminal(); @@ -208,296 +87,250 @@ describe('InputHandler', () => { }); }); describe('regression tests', function(): void { - function lineContent(line: IBufferLine): string { - let content = ''; - for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX] || ' '; - return content; - } - - function termContent(term: Terminal): string[] { + function termContent(term: Terminal, trim: boolean): string[] { const result = []; - for (let i = 0; i < term.rows; ++i) result.push(lineContent(term.buffer.lines.get(i))); + for (let i = 0; i < term.rows; ++i) result.push(term.buffer.lines.get(i).translateToString(trim)); return result; } it('insertChars', function(): void { const term = new Terminal(); const inputHandler = new InputHandler(term); - const oldInputHandler = new OldInputHandler(term); // insert some data in first and second line inputHandler.parse(Array(term.cols - 9).join('a')); inputHandler.parse('1234567890'); inputHandler.parse(Array(term.cols - 9).join('a')); inputHandler.parse('1234567890'); - const line1: IBufferLine = term.buffer.lines.get(0); // line for old variant - const line2: IBufferLine = term.buffer.lines.get(1); // line for new variant - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '1234567890'); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '1234567890'); + const line1: IBufferLine = term.buffer.lines.get(0); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '1234567890'); // insert one char from params = [0] term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.insertChars([0]); - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 123456789'); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.insertChars([0]); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 123456789'); - expect(lineContent(line2)).equals(lineContent(line1)); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456789'); // insert one char from params = [1] term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.insertChars([1]); - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 12345678'); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.insertChars([1]); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 12345678'); - expect(lineContent(line2)).equals(lineContent(line1)); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 12345678'); // insert two chars from params = [2] term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.insertChars([2]); - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 123456'); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.insertChars([2]); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 123456'); - expect(lineContent(line2)).equals(lineContent(line1)); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456'); // insert 10 chars from params = [10] term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.insertChars([10]); - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' '); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.insertChars([10]); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' '); - expect(lineContent(line2)).equals(lineContent(line1)); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' '); + expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a')); }); it('deleteChars', function(): void { const term = new Terminal(); const inputHandler = new InputHandler(term); - const oldInputHandler = new OldInputHandler(term); // insert some data in first and second line inputHandler.parse(Array(term.cols - 9).join('a')); inputHandler.parse('1234567890'); inputHandler.parse(Array(term.cols - 9).join('a')); inputHandler.parse('1234567890'); - const line1: IBufferLine = term.buffer.lines.get(0); // line for old variant - const line2: IBufferLine = term.buffer.lines.get(1); // line for new variant - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '1234567890'); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '1234567890'); + const line1: IBufferLine = term.buffer.lines.get(0); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '1234567890'); // delete one char from params = [0] term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.deleteChars([0]); - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '234567890 '); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.deleteChars([0]); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '234567890 '); - expect(lineContent(line2)).equals(lineContent(line1)); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '234567890 '); + expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '234567890'); // insert one char from params = [1] term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.deleteChars([1]); - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '34567890 '); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.deleteChars([1]); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '34567890 '); - expect(lineContent(line2)).equals(lineContent(line1)); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '34567890 '); + expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '34567890'); // insert two chars from params = [2] term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.deleteChars([2]); - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '567890 '); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.deleteChars([2]); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '567890 '); - expect(lineContent(line2)).equals(lineContent(line1)); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '567890 '); + expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '567890'); // insert 10 chars from params = [10] term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.deleteChars([10]); - expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' '); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.deleteChars([10]); - expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' '); - expect(lineContent(line2)).equals(lineContent(line1)); + expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' '); + expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a')); }); it('eraseInLine', function(): void { const term = new Terminal(); const inputHandler = new InputHandler(term); - const oldInputHandler = new OldInputHandler(term); // fill 6 lines to test 3 different states inputHandler.parse(Array(term.cols + 1).join('a')); inputHandler.parse(Array(term.cols + 1).join('a')); inputHandler.parse(Array(term.cols + 1).join('a')); - inputHandler.parse(Array(term.cols + 1).join('a')); - inputHandler.parse(Array(term.cols + 1).join('a')); - inputHandler.parse(Array(term.cols + 1).join('a')); // params[0] - right erase term.buffer.y = 0; term.buffer.x = 70; - oldInputHandler.eraseInLine([0]); - expect(lineContent(term.buffer.lines.get(0))).equals(Array(71).join('a') + ' '); - term.buffer.y = 1; - term.buffer.x = 70; inputHandler.eraseInLine([0]); - expect(lineContent(term.buffer.lines.get(1))).equals(Array(71).join('a') + ' '); + expect(term.buffer.lines.get(0).translateToString(false)).equals(Array(71).join('a') + ' '); // params[1] - left erase - term.buffer.y = 2; - term.buffer.x = 70; - oldInputHandler.eraseInLine([1]); - expect(lineContent(term.buffer.lines.get(2))).equals(Array(71).join(' ') + ' aaaaaaaaa'); - term.buffer.y = 3; + term.buffer.y = 1; term.buffer.x = 70; inputHandler.eraseInLine([1]); - expect(lineContent(term.buffer.lines.get(3))).equals(Array(71).join(' ') + ' aaaaaaaaa'); + expect(term.buffer.lines.get(1).translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa'); // params[1] - left erase - term.buffer.y = 4; - term.buffer.x = 70; - oldInputHandler.eraseInLine([2]); - expect(lineContent(term.buffer.lines.get(4))).equals(Array(term.cols + 1).join(' ')); - term.buffer.y = 5; + term.buffer.y = 2; term.buffer.x = 70; inputHandler.eraseInLine([2]); - expect(lineContent(term.buffer.lines.get(5))).equals(Array(term.cols + 1).join(' ')); + expect(term.buffer.lines.get(2).translateToString(false)).equals(Array(term.cols + 1).join(' ')); }); it('eraseInDisplay', function(): void { - const termOld = new Terminal(); - const inputHandlerOld = new OldInputHandler(termOld); - const termNew = new Terminal(); - const inputHandlerNew = new InputHandler(termNew); + const term = new Terminal({cols: 80, rows: 7}); + const inputHandler = new InputHandler(term); // fill display with a's - for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a')); - for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a')); - const data = []; - for (let i = 0; i < termOld.rows; ++i) data.push(Array(termOld.cols + 1).join('a')); - expect(termContent(termOld)).eql(data); - expect(termContent(termOld)).eql(termContent(termNew)); + for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); // params [0] - right and below erase - termOld.buffer.y = 5; - termOld.buffer.x = 40; - inputHandlerOld.eraseInDisplay([0]); - termNew.buffer.y = 5; - termNew.buffer.x = 40; - inputHandlerNew.eraseInDisplay([0]); - expect(termContent(termNew)).eql(termContent(termOld)); + term.buffer.y = 5; + term.buffer.x = 40; + inputHandler.eraseInDisplay([0]); + expect(termContent(term, false)).eql([ + Array(term.cols + 1).join('a'), + Array(term.cols + 1).join('a'), + Array(term.cols + 1).join('a'), + Array(term.cols + 1).join('a'), + Array(term.cols + 1).join('a'), + Array(40 + 1).join('a') + Array(term.cols - 40 + 1).join(' '), + Array(term.cols + 1).join(' ') + ]); + expect(termContent(term, true)).eql([ + Array(term.cols + 1).join('a'), + Array(term.cols + 1).join('a'), + Array(term.cols + 1).join('a'), + Array(term.cols + 1).join('a'), + Array(term.cols + 1).join('a'), + Array(40 + 1).join('a'), + '' + ]); // reset - termOld.buffer.y = 0; - termOld.buffer.x = 0; - termNew.buffer.y = 0; - termNew.buffer.x = 0; - for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a')); - for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a')); + term.buffer.y = 0; + term.buffer.x = 0; + for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); // params [1] - left and above - termOld.buffer.y = 5; - termOld.buffer.x = 40; - inputHandlerOld.eraseInDisplay([1]); - termNew.buffer.y = 5; - termNew.buffer.x = 40; - inputHandlerNew.eraseInDisplay([1]); - expect(termContent(termNew)).eql(termContent(termOld)); + term.buffer.y = 5; + term.buffer.x = 40; + inputHandler.eraseInDisplay([1]); + expect(termContent(term, false)).eql([ + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(41 + 1).join(' ') + Array(term.cols - 41 + 1).join('a'), + Array(term.cols + 1).join('a') + ]); + expect(termContent(term, true)).eql([ + '', + '', + '', + '', + '', + Array(41 + 1).join(' ') + Array(term.cols - 41 + 1).join('a'), + Array(term.cols + 1).join('a') + ]); // reset - termOld.buffer.y = 0; - termOld.buffer.x = 0; - termNew.buffer.y = 0; - termNew.buffer.x = 0; - for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a')); - for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a')); + term.buffer.y = 0; + term.buffer.x = 0; + for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); // params [2] - whole screen - termOld.buffer.y = 5; - termOld.buffer.x = 40; - inputHandlerOld.eraseInDisplay([2]); - termNew.buffer.y = 5; - termNew.buffer.x = 40; - inputHandlerNew.eraseInDisplay([2]); - expect(termContent(termNew)).eql(termContent(termOld)); + term.buffer.y = 5; + term.buffer.x = 40; + inputHandler.eraseInDisplay([2]); + expect(termContent(term, false)).eql([ + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' '), + Array(term.cols + 1).join(' ') + ]); + expect(termContent(term, true)).eql([ + '', + '', + '', + '', + '', + '', + '' + ]); // reset and add a wrapped line - termNew.buffer.y = 0; - termNew.buffer.x = 0; - inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); // line 0 - inputHandlerNew.parse(Array(termNew.cols + 10).join('a')); // line 1 and 2 - for (let i = 3; i < termOld.rows; ++i) inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); + term.buffer.y = 0; + term.buffer.x = 0; + inputHandler.parse(Array(term.cols + 1).join('a')); // line 0 + inputHandler.parse(Array(term.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); // params[1] left and above with wrap // confirm precondition that line 2 is wrapped - expect(termNew.buffer.lines.get(2).isWrapped).true; - termNew.buffer.y = 2; - termNew.buffer.x = 40; - inputHandlerNew.eraseInDisplay([1]); - expect(termNew.buffer.lines.get(2).isWrapped).false; + expect(term.buffer.lines.get(2).isWrapped).true; + term.buffer.y = 2; + term.buffer.x = 40; + inputHandler.eraseInDisplay([1]); + expect(term.buffer.lines.get(2).isWrapped).false; // reset and add a wrapped line - termNew.buffer.y = 0; - termNew.buffer.x = 0; - inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); // line 0 - inputHandlerNew.parse(Array(termNew.cols + 10).join('a')); // line 1 and 2 - for (let i = 3; i < termOld.rows; ++i) inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); + term.buffer.y = 0; + term.buffer.x = 0; + inputHandler.parse(Array(term.cols + 1).join('a')); // line 0 + inputHandler.parse(Array(term.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a')); // params[1] left and above with wrap // confirm precondition that line 2 is wrapped - expect(termNew.buffer.lines.get(2).isWrapped).true; - termNew.buffer.y = 1; - termNew.buffer.x = 90; // Cursor is beyond last column - inputHandlerNew.eraseInDisplay([1]); - expect(termNew.buffer.lines.get(2).isWrapped).false; + expect(term.buffer.lines.get(2).isWrapped).true; + term.buffer.y = 1; + term.buffer.x = 90; // Cursor is beyond last column + inputHandler.eraseInDisplay([1]); + expect(term.buffer.lines.get(2).isWrapped).false; }); }); it('convertEol setting', function(): void { // not converting - let s = ''; const termNotConverting = new Terminal({cols: 15, rows: 10}); (termNotConverting as any)._inputHandler.parse('Hello\nWorld'); - for (let i = 0; i < termNotConverting.cols; ++i) { - s += termNotConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX] || ' '; - } - expect(s).equals('Hello '); - s = ''; - for (let i = 0; i < termNotConverting.cols; ++i) { - s += termNotConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX] || ' '; - } - expect(s).equals(' World '); + expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); + expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World '); + expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); + expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World'); // converting - s = ''; const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true}); (termConverting as any)._inputHandler.parse('Hello\nWorld'); - for (let i = 0; i < termConverting.cols; ++i) { - s += termConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX] || ' '; - } - expect(s).equals('Hello '); - s = ''; - for (let i = 0; i < termConverting.cols; ++i) { - s += termConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX] || ' '; - } - expect(s).equals('World '); + expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); + expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World '); + expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); + expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World'); }); describe('print', () => { it('should not cause an infinite loop (regression test)', () => { From a15b866013d3f3c165258d6ca8e8e49a564d8489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 8 Nov 2018 05:25:12 +0100 Subject: [PATCH 11/15] test cases for getTrimLength and translateToString --- src/BufferLine.test.ts | 128 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index b824cc38a2..93b2759f83 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -5,7 +5,7 @@ import * as chai from 'chai'; import { BufferLine } from './BufferLine'; import { CharData, IBufferLine } from './Types'; -import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer'; +import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './Buffer'; class TestBufferLine extends BufferLine { @@ -200,4 +200,130 @@ describe('BufferLine', function(): void { chai.expect(line.toArray()).eql(Array(7).fill([1, 'a', 0, 'a'.charCodeAt(0)])); }); }); + describe('getTrimLength', function(): void { + it('empty line', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + chai.expect(line.getTrimmedLength()).equal(0); + }); + it('ASCII', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); + chai.expect(line.getTrimmedLength()).equal(3); + }); + it('surrogate', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); + chai.expect(line.getTrimmedLength()).equal(3); + }); + it('combining', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + chai.expect(line.getTrimmedLength()).equal(3); + }); + it('fullwidth', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, '1', 2, '1'.charCodeAt(0)]); + line.set(3, [0, '', 0, undefined]); + chai.expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth + }); + }); + describe('translateToString with and w\'o trimming', function(): void { + it('empty line', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + chai.expect(line.translateToString(false)).equal(' '); + chai.expect(line.translateToString(true)).equal(''); + }); + it('ASCII', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(5, [1, 'a', 1, 'a'.charCodeAt(0)]); + chai.expect(line.translateToString(false)).equal('a a aa '); + chai.expect(line.translateToString(true)).equal('a a aa'); + chai.expect(line.translateToString(false, 0, 5)).equal('a a a'); + chai.expect(line.translateToString(false, 0, 4)).equal('a a '); + chai.expect(line.translateToString(false, 0, 3)).equal('a a'); + chai.expect(line.translateToString(true, 0, 5)).equal('a a a'); + chai.expect(line.translateToString(true, 0, 4)).equal('a a '); + chai.expect(line.translateToString(true, 0, 3)).equal('a a'); + + }); + it('surrogate', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); + line.set(4, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); + line.set(5, [1, '𝄞', 1, '𝄞'.charCodeAt(0)]); + chai.expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 '); + chai.expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞'); + chai.expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞'); + chai.expect(line.translateToString(false, 0, 4)).equal('a 𝄞 '); + chai.expect(line.translateToString(false, 0, 3)).equal('a 𝄞'); + chai.expect(line.translateToString(true, 0, 5)).equal('a 𝄞 𝄞'); + chai.expect(line.translateToString(true, 0, 4)).equal('a 𝄞 '); + chai.expect(line.translateToString(true, 0, 3)).equal('a 𝄞'); + }); + it('combining', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + line.set(4, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + line.set(5, [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]); + chai.expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 '); + chai.expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301'); + chai.expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301'); + chai.expect(line.translateToString(false, 0, 4)).equal('a e\u0301 '); + chai.expect(line.translateToString(false, 0, 3)).equal('a e\u0301'); + chai.expect(line.translateToString(true, 0, 5)).equal('a e\u0301 e\u0301'); + chai.expect(line.translateToString(true, 0, 4)).equal('a e\u0301 '); + chai.expect(line.translateToString(true, 0, 3)).equal('a e\u0301'); + }); + it('fullwidth', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, '1', 2, '1'.charCodeAt(0)]); + line.set(3, [0, '', 0, undefined]); + line.set(5, [1, '1', 2, '1'.charCodeAt(0)]); + line.set(6, [0, '', 0, undefined]); + line.set(7, [1, '1', 2, '1'.charCodeAt(0)]); + line.set(8, [0, '', 0, undefined]); + chai.expect(line.translateToString(false)).equal('a 1 11 '); + chai.expect(line.translateToString(true)).equal('a 1 11'); + chai.expect(line.translateToString(false, 0, 7)).equal('a 1 1'); + chai.expect(line.translateToString(false, 0, 6)).equal('a 1 1'); + chai.expect(line.translateToString(false, 0, 5)).equal('a 1 '); + chai.expect(line.translateToString(false, 0, 4)).equal('a 1'); + chai.expect(line.translateToString(false, 0, 3)).equal('a 1'); + chai.expect(line.translateToString(false, 0, 2)).equal('a '); + chai.expect(line.translateToString(true, 0, 7)).equal('a 1 1'); + chai.expect(line.translateToString(true, 0, 6)).equal('a 1 1'); + chai.expect(line.translateToString(true, 0, 5)).equal('a 1 '); + chai.expect(line.translateToString(true, 0, 4)).equal('a 1'); + chai.expect(line.translateToString(true, 0, 3)).equal('a 1'); + chai.expect(line.translateToString(true, 0, 2)).equal('a '); + }); + it('space at end', function(): void { + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE], false); + line.set(0, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(2, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(4, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(5, [1, 'a', 1, 'a'.charCodeAt(0)]); + line.set(6, [1, ' ', 1, ' '.charCodeAt(0)]); + chai.expect(line.translateToString(false)).equal('a a aa '); + chai.expect(line.translateToString(true)).equal('a a aa '); + }); + it('should always return some sane value', function(): void { + // sanity check - broken line with invalid out of bound null width cells + // this can atm happen with deleting/inserting chars in inputhandler by "breaking" + // fullwidth pairs --> needs to be fixed after settling BufferLine impl + const line = new TestBufferLine(10, [DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE], false); + chai.expect(line.translateToString(false)).equal(' '); + chai.expect(line.translateToString(true)).equal(''); + }); + }); }); From 99e4a9649f4babfc9716cc4a7ddeee5c177a56b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 29 Nov 2018 21:48:48 +0100 Subject: [PATCH 12/15] fix failing test cases --- src/InputHandler.test.ts | 28 +++++++++++----------------- src/addons/search/search.test.ts | 5 +++-- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index bc2b10c270..85efa0ee8e 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -6,7 +6,7 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal } from './utils/TestUtils.test'; -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } from './Buffer'; +import { CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } from './Buffer'; import { Terminal } from './Terminal'; import { IBufferLine } from './Types'; @@ -345,34 +345,28 @@ describe('InputHandler', () => { let term: Terminal; let handler: InputHandler; - function lineContent(line: IBufferLine): string { - let content = ''; - for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX]; - return content; - } - beforeEach(() => { term = new Terminal(); handler = new InputHandler(term); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); - expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' ')); - expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' ')); + expect(term.buffer.translateBufferLineToString(0, true)).to.equal(''); + expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); // Text color of 'TEST' should be red expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); }); it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => { handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); - expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' ')); - expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' ')); + expect(term.buffer.translateBufferLineToString(0, true)).to.equal(''); + expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); // Text color of 'TEST' should be red expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); }); it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); - expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' ')); - expect(lineContent(term.buffer.lines.get(1))).to.equal('JUNK' + Array(term.cols - 3).join(' ')); + expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); + expect(term.buffer.translateBufferLineToString(1, true)).to.equal('JUNK'); // Text color of 'TEST' should be default expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); // Text color of 'JUNK' should be red @@ -380,18 +374,18 @@ describe('InputHandler', () => { }); it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); - expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' ')); - expect(lineContent(term.buffer.lines.get(1))).to.equal(Array(term.cols + 1).join(' ')); + expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); + expect(term.buffer.translateBufferLineToString(1, true)).to.equal(''); // Text color of 'TEST' should be default expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); }); it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => { handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); - expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' ')); + expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); // Text color of 'TEST' should be default expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR); handler.parse('\x1b[?1049h\x1b[uTEST'); - expect(lineContent(term.buffer.lines.get(1))).to.equal('TEST' + Array(term.cols - 3).join(' ')); + expect(term.buffer.translateBufferLineToString(1, true)).to.equal('TEST'); // Text color of 'TEST' should be red expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1); }); diff --git a/src/addons/search/search.test.ts b/src/addons/search/search.test.ts index 3e0b815456..ab31e1f65b 100644 --- a/src/addons/search/search.test.ts +++ b/src/addons/search/search.test.ts @@ -116,10 +116,11 @@ describe('search addon', () => { expect(tilda2).eql({col: 0, row: 3, term: '~'}); }); it('should not select empty lines', () => { + // with addition of a real null char printing spaces is not considered empty anymore search.apply(MockTerminal); const term = new MockTerminal({cols: 20, rows: 3}); - term.core.write(' '); - term.pushWriteData(); + // with addition of a null char printing spaces is not considered empty anymore + // therefore we write nothing here const line = term.searchHelper.findInLine('^.*$', 0, { regex: true }); expect(line).eql(undefined); }); From 9d4beb024a506552328741392fc280b4d9c1ba90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 29 Nov 2018 22:11:38 +0100 Subject: [PATCH 13/15] keep whitespace chars --- src/Buffer.ts | 8 ++++---- src/BufferLine.ts | 14 +++++++------- src/Terminal.integration.ts | 4 ++-- src/renderer/TextRenderLayer.ts | 6 +++--- src/renderer/dom/DomRendererRowFactory.ts | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index f9eed90fb1..07a5442c4a 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -17,14 +17,14 @@ export const CHAR_DATA_WIDTH_INDEX = 2; export const CHAR_DATA_CODE_INDEX = 3; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 -// export const NULL_CELL_CHAR = ' '; -// export const NULL_CELL_WIDTH = 1; -// export const NULL_CELL_CODE = 32; - export const NULL_CELL_CHAR = ''; export const NULL_CELL_WIDTH = 1; export const NULL_CELL_CODE = 0; +export const WHITESPACE_CELL_CHAR = ' '; +export const WHITESPACE_CELL_WIDTH = 1; +export const WHITESPACE_CELL_CODE = 32; + /** * This class represents a terminal buffer (an internal state of the terminal), where the * following information is stored (in high-level): diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 53a194c5c6..5a462d27bb 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -3,7 +3,7 @@ * @license MIT */ import { CharData, IBufferLine } from './Types'; -import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer'; +import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR } from './Buffer'; /** * Class representing a terminal line. @@ -123,7 +123,7 @@ export class BufferLine implements IBufferLine { } let result = ''; while (startCol < length) { - result += this.get(startCol)[CHAR_DATA_CHAR_INDEX] || ' '; + result += this.get(startCol)[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; startCol += this.get(startCol)[CHAR_DATA_WIDTH_INDEX] || 1; } return result; @@ -141,7 +141,7 @@ const enum Cell { } /** single vs. combined char distinction */ -const COMBINED = 0x80000000; +const IS_COMBINED_BIT_MASK = 0x80000000; /** * Typed array based bufferline implementation. @@ -177,11 +177,11 @@ export class BufferLineTypedArray implements IBufferLine { const stringData = this._data[index * CELL_SIZE + Cell.STRING]; return [ this._data[index * CELL_SIZE + Cell.FLAGS], - (stringData & COMBINED) + (stringData & IS_COMBINED_BIT_MASK) ? this._combined[index] : (stringData) ? String.fromCharCode(stringData) : '', this._data[index * CELL_SIZE + Cell.WIDTH], - (stringData & COMBINED) + (stringData & IS_COMBINED_BIT_MASK) ? this._combined[index].charCodeAt(this._combined[index].length - 1) : stringData ]; @@ -191,7 +191,7 @@ export class BufferLineTypedArray implements IBufferLine { this._data[index * CELL_SIZE + Cell.FLAGS] = value[0]; if (value[1].length > 1) { this._combined[index] = value[1]; - this._data[index * CELL_SIZE + Cell.STRING] = index | COMBINED; + this._data[index * CELL_SIZE + Cell.STRING] = index | IS_COMBINED_BIT_MASK; } else { this._data[index * CELL_SIZE + Cell.STRING] = value[1].charCodeAt(0); } @@ -320,7 +320,7 @@ export class BufferLineTypedArray implements IBufferLine { let result = ''; while (startCol < length) { const stringData = this._data[startCol * CELL_SIZE + Cell.STRING]; - result += (stringData & COMBINED) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : ' '; + result += (stringData & IS_COMBINED_BIT_MASK) ? this._combined[startCol] : (stringData) ? String.fromCharCode(stringData) : WHITESPACE_CELL_CHAR; startCol += this._data[startCol * CELL_SIZE + Cell.WIDTH] || 1; } return result; diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index 21c4a2d04e..d2a5cd7c5e 100644 --- a/src/Terminal.integration.ts +++ b/src/Terminal.integration.ts @@ -13,7 +13,7 @@ import * as path from 'path'; import * as pty from 'node-pty'; import { assert } from 'chai'; import { Terminal } from './Terminal'; -import { CHAR_DATA_CHAR_INDEX } from './Buffer'; +import { CHAR_DATA_CHAR_INDEX, WHITESPACE_CELL_CHAR } from './Buffer'; import { IViewport } from './Types'; class TestTerminal extends Terminal { @@ -67,7 +67,7 @@ function terminalToString(term: Terminal): string { 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).get(cell)[CHAR_DATA_CHAR_INDEX] || ' '; + lineText += term.buffer.lines.get(line).get(cell)[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; } // rtrim empty cells as xterm does lineText = lineText.replace(/\s+$/, ''); diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 7ab2d23875..ade2dd4c51 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE } from '../Buffer'; +import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../Buffer'; import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { CharData, ITerminal } from '../Types'; import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from './atlas/Types'; @@ -73,11 +73,11 @@ export class TextRenderLayer extends BaseRenderLayer { const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; for (let x = 0; x < terminal.cols; x++) { const charData = line.get(x); - let code: number = charData[CHAR_DATA_CODE_INDEX] || 32; + let code: number = charData[CHAR_DATA_CODE_INDEX] || WHITESPACE_CELL_CODE; // Can either represent character(s) for a single cell or multiple cells // if indicated by a character joiner. - let chars: string = charData[CHAR_DATA_CHAR_INDEX] || ' '; + let chars: string = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; const attr: number = charData[CHAR_DATA_ATTR_INDEX]; let width: number = charData[CHAR_DATA_WIDTH_INDEX]; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 4a3715c511..8db1dcf858 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from '../../Buffer'; +import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../Buffer'; import { FLAGS } from '../Types'; import { IBufferLine } from '../../Types'; import { DEFAULT_COLOR, INVERTED_DEFAULT_COLOR } from '../atlas/Types'; @@ -41,7 +41,7 @@ export class DomRendererRowFactory { for (let x = 0; x < lineLength; x++) { const charData = lineData.get(x); - const char: string = charData[CHAR_DATA_CHAR_INDEX] || ' '; + const char: string = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; const attr: number = charData[CHAR_DATA_ATTR_INDEX]; const width: number = charData[CHAR_DATA_WIDTH_INDEX]; From 62be3047d8430fa424ed877fde370fa067ff124e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 15 Dec 2018 00:01:18 +0100 Subject: [PATCH 14/15] infer types fix --- src/renderer/dom/DomRendererRowFactory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index 8db1dcf858..8bcde39a05 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -41,9 +41,9 @@ export class DomRendererRowFactory { for (let x = 0; x < lineLength; x++) { const charData = lineData.get(x); - const char: string = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; - const attr: number = charData[CHAR_DATA_ATTR_INDEX]; - const width: number = charData[CHAR_DATA_WIDTH_INDEX]; + const char = charData[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR; + const attr = charData[CHAR_DATA_ATTR_INDEX]; + const width = charData[CHAR_DATA_WIDTH_INDEX]; // The character to the left is a wide character, drawing is owned by the char at x-1 if (width === 0) { From 3927364f0ec9565e624c4aa6c902397c6eaccf38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sat, 15 Dec 2018 00:03:54 +0100 Subject: [PATCH 15/15] remove nonsense comments --- src/addons/search/search.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/addons/search/search.test.ts b/src/addons/search/search.test.ts index ab31e1f65b..a70fd115e2 100644 --- a/src/addons/search/search.test.ts +++ b/src/addons/search/search.test.ts @@ -116,11 +116,8 @@ describe('search addon', () => { expect(tilda2).eql({col: 0, row: 3, term: '~'}); }); it('should not select empty lines', () => { - // with addition of a real null char printing spaces is not considered empty anymore search.apply(MockTerminal); const term = new MockTerminal({cols: 20, rows: 3}); - // with addition of a null char printing spaces is not considered empty anymore - // therefore we write nothing here const line = term.searchHelper.findInLine('^.*$', 0, { regex: true }); expect(line).eql(undefined); });