diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 902d54b0..81f3ba4c 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -155,8 +155,12 @@ describe('Buffer', () => { assert.equal(buffer.lines.maxLength, INIT_ROWS); buffer.y = INIT_ROWS - 1; buffer.fillViewportRows(); - buffer.lines.get(5).get(0)[1] = 'a'; - buffer.lines.get(INIT_ROWS - 1).get(0)[1] = 'b'; + let chData = buffer.lines.get(5).get(0); + chData[1] = 'a'; + buffer.lines.get(5).set(0, chData); + chData = buffer.lines.get(INIT_ROWS - 1).get(0); + chData[1] = 'b'; + buffer.lines.get(INIT_ROWS - 1).set(0, chData); buffer.resize(INIT_COLS, INIT_ROWS - 5); assert.equal(buffer.lines.get(0).get(0)[1], 'a'); assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).get(0)[1], 'b'); diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 10f4815f..8b0bcbb3 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -10,7 +10,11 @@ import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, CHAR_DATA_ATTR_INDEX, class TestBufferLine extends BufferLine { public toArray(): CharData[] { - return this._data; + const result = []; + for (let i = 0; i < this.length; ++i) { + result.push(this.get(i)); + } + return result; } } @@ -27,9 +31,9 @@ describe('BufferLine', function(): void { chai.expect(line.length).equals(10); chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); chai.expect(line.isWrapped).equals(true); - line = new TestBufferLine(10, [123, 'a', 456, 789], true); + line = new TestBufferLine(10, [123, 'a', 456, 'a'.charCodeAt(0)], true); chai.expect(line.length).equals(10); - chai.expect(line.get(0)).eql([123, 'a', 456, 789]); + chai.expect(line.get(0)).eql([123, 'a', 456, 'a'.charCodeAt(0)]); chai.expect(line.isWrapped).equals(true); }); it('TerminalLine.blankLine', function(): void { @@ -44,30 +48,46 @@ describe('BufferLine', function(): void { }); it('insertCells', function(): void { const line = new TestBufferLine(3); - line.set(0, [1, 'a', 0, 0]); - line.set(1, [2, 'b', 0, 0]); - line.set(2, [3, 'c', 0, 0]); - line.insertCells(1, 3, [4, 'd', 0, 0]); - chai.expect(line.toArray()).eql([[1, 'a', 0, 0], [4, 'd', 0, 0], [4, 'd', 0, 0]]); + line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); + line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); + line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); + line.insertCells(1, 3, [4, 'd', 0, 'd'.charCodeAt(0)]); + chai.expect(line.toArray()).eql([ + [1, 'a', 0, 'a'.charCodeAt(0)], + [4, 'd', 0, 'd'.charCodeAt(0)], + [4, 'd', 0, 'd'.charCodeAt(0)] + ]); }); it('deleteCells', function(): void { const line = new TestBufferLine(5); - line.set(0, [1, 'a', 0, 0]); - line.set(1, [2, 'b', 0, 0]); - line.set(2, [3, 'c', 0, 0]); - line.set(3, [4, 'd', 0, 0]); - line.set(4, [5, 'e', 0, 0]); - line.deleteCells(1, 2, [6, 'f', 0, 0]); - chai.expect(line.toArray()).eql([[1, 'a', 0, 0], [4, 'd', 0, 0], [5, 'e', 0, 0], [6, 'f', 0, 0], [6, 'f', 0, 0]]); + line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); + line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); + line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); + line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); + line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); + line.deleteCells(1, 2, [6, 'f', 0, 'f'.charCodeAt(0)]); + chai.expect(line.toArray()).eql([ + [1, 'a', 0, 'a'.charCodeAt(0)], + [4, 'd', 0, 'd'.charCodeAt(0)], + [5, 'e', 0, 'e'.charCodeAt(0)], + [6, 'f', 0, 'f'.charCodeAt(0)], + [6, 'f', 0, 'f'.charCodeAt(0)] + ]); }); it('replaceCells', function(): void { const line = new TestBufferLine(5); - line.set(0, [1, 'a', 0, 0]); - line.set(1, [2, 'b', 0, 0]); - line.set(2, [3, 'c', 0, 0]); - line.set(3, [4, 'd', 0, 0]); - line.set(4, [5, 'e', 0, 0]); - line.replaceCells(2, 4, [6, 'f', 0, 0]); - chai.expect(line.toArray()).eql([[1, 'a', 0, 0], [2, 'b', 0, 0], [6, 'f', 0, 0], [6, 'f', 0, 0], [5, 'e', 0, 0]]); + line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]); + line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]); + line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]); + line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]); + line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]); + line.replaceCells(2, 4, [6, 'f', 0, 'f'.charCodeAt(0)]); + chai.expect(line.toArray()).eql([ + [1, 'a', 0, 'a'.charCodeAt(0)], + [2, 'b', 0, 'b'.charCodeAt(0)], + [6, 'f', 0, 'f'.charCodeAt(0)], + [6, 'f', 0, 'f'.charCodeAt(0)], + [5, 'e', 0, 'e'.charCodeAt(0)] + ]); }); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index bc54067f..369210c4 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -8,10 +8,10 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; /** * Class representing a terminal line. */ -export class BufferLine implements IBufferLine { +export class BufferLineOld implements IBufferLine { static blankLine(cols: number, attr: number, isWrapped?: boolean): IBufferLine { const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - return new BufferLine(cols, ch, isWrapped); + return new BufferLineOld(cols, ch, isWrapped); } protected _data: CharData[]; public isWrapped = false; @@ -107,3 +107,119 @@ export class BufferLine implements IBufferLine { this.length = cols; } } + +const enum Cell { + FLAGS = 0, + STRING = 1, + WIDTH = 2, + SIZE = 3 +} + +export class BufferLine implements IBufferLine { + static blankLine(cols: number, attr: number, isWrapped?: boolean): IBufferLine { + const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; + return new BufferLine(cols, ch, isWrapped); + } + protected _data: Uint32Array | null = null; + protected _combined: {[index: number]: string} = {}; + public length: number; + + constructor(cols?: number, ch?: CharData, public isWrapped: boolean = false) { + this.length = cols || 0; + if (cols) { + if (!ch) { + ch = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; + } + this._data = new Uint32Array(cols * Cell.SIZE); + for (let i = 0; i < cols; ++i) { + this.set(i, ch); + } + } + } + + public get(index: number): CharData { + const stringData = this._data[index * Cell.SIZE + Cell.STRING]; + return [ + this._data[index * Cell.SIZE + Cell.FLAGS], + (stringData & 0x80000000) + ? this._combined[index] + : (stringData) ? String.fromCharCode(stringData) : '', + this._data[index * Cell.SIZE + Cell.WIDTH], + stringData & ~0x80000000 + ]; + } + + public set(index: number, value: CharData): void { + 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; + } else { + this._data[index * Cell.SIZE + Cell.STRING] = value[1].charCodeAt(0); + } + this._data[index * Cell.SIZE + Cell.WIDTH] = value[2]; + } + + public insertCells(pos: number, n: number, fill: CharData): void { + pos %= this.length; + if (n < this.length - pos) { + for (let i = this.length - pos - n - 1; i >= 0; --i) { + this.set(pos + n + i, this.get(pos + i)); + } + for (let i = 0; i < n; ++i) { + this.set(pos + i, fill); + } + } else { + for (let i = pos; i < this.length; ++i) { + this.set(i, fill); + } + } + } + + public deleteCells(pos: number, n: number, fill: CharData): void { + pos %= this.length; + if (n < this.length - pos) { + for (let i = 0; i < this.length - pos - n; ++i) { + this.set(pos + i, this.get(pos + n + i)); + } + for (let i = this.length - n; i < this.length; ++i) { + this.set(i, fill); + } + } else { + for (let i = pos; i < this.length; ++i) { + this.set(i, fill); + } + } + } + + public replaceCells(start: number, end: number, fill: CharData): void { + while (start < end && start < this.length) { + this.set(start++, fill); + } + } + + public resize(cols: number, fill: CharData, shrink: boolean = false): void { + if (cols === this.length) { + return; + } + if (cols > this.length) { + const data = new Uint32Array(cols * Cell.SIZE); + if (this._data) { + data.set(this._data); + } + this._data = data; + for (let i = this.length; i < cols; ++i) { + this.set(i, fill); + } + } else if (shrink) { + if (cols) { + const data = new Uint32Array(cols * Cell.SIZE); + data.set(this._data.subarray(0, this.length)); + this._data = data; + } else { + this._data = null; + } + } + this.length = cols; + } +} diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 83ab9a68..789751d1 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -338,8 +338,8 @@ describe('term.js addons', () => { describe('scroll() function', () => { describe('when scrollback > 0', () => { it('should create a new line and scroll', () => { - term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b'; + term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); + term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); @@ -349,9 +349,9 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); + term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); + term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(); @@ -361,11 +361,11 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c'; - term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX] = 'd'; - term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX] = 'e'; + term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); + term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); + term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); + term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]); + term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]); term.buffer.y = 3; term.buffer.scrollBottom = 3; term.scroll(); @@ -379,11 +379,11 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c'; - term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX] = 'd'; - term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX] = 'e'; + term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); + term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); + term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); + term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]); + term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; @@ -404,9 +404,9 @@ describe('term.js addons', () => { }); it('should create a new line and shift everything up', () => { - term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); + term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); + term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line assert.equal(term.buffer.lines.length, INIT_ROWS); term.scroll(); @@ -419,9 +419,9 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c'; + term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); + term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); + term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(); @@ -431,11 +431,11 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c'; - term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX] = 'd'; - term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX] = 'e'; + term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); + term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); + term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); + term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]); + term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]); term.buffer.y = 3; term.buffer.scrollBottom = 3; term.scroll(); @@ -448,11 +448,11 @@ describe('term.js addons', () => { }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX] = 'c'; - term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX] = 'd'; - term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX] = 'e'; + term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]); + term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]); + term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]); + term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]); + term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3;