diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index a51e5456..82d5bd2b 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -8,6 +8,7 @@ import { ITerminal } from './Types'; import { Buffer } from './Buffer'; import { CircularList } from './common/CircularList'; import { MockTerminal } from './utils/TestUtils.test'; +import { TerminalLine } from './TerminalLine'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -36,13 +37,13 @@ describe('Buffer', () => { describe('fillViewportRows', () => { it('should fill the buffer with blank lines based on the size of the viewport', () => { - const blankLineChar = terminal.blankLine()[0]; + const blankLineChar = terminal.blankLine().get(0); buffer.fillViewportRows(); assert.equal(buffer.lines.length, INIT_ROWS); for (let y = 0; y < INIT_ROWS; y++) { assert.equal(buffer.lines.get(y).length, INIT_COLS); for (let x = 0; x < INIT_COLS; x++) { - assert.deepEqual(buffer.lines.get(y)[x], blankLineChar); + assert.deepEqual(buffer.lines.get(y).get(x), blankLineChar); } } }); @@ -154,11 +155,11 @@ describe('Buffer', () => { assert.equal(buffer.lines.maxLength, INIT_ROWS); buffer.y = INIT_ROWS - 1; buffer.fillViewportRows(); - buffer.lines.get(5)[0][1] = 'a'; - buffer.lines.get(INIT_ROWS - 1)[0][1] = 'b'; + buffer.lines.get(5).get(0)[1] = 'a'; + buffer.lines.get(INIT_ROWS - 1).get(0)[1] = 'b'; buffer.resize(INIT_COLS, INIT_ROWS - 5); - assert.equal(buffer.lines.get(0)[0][1], 'a'); - assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5)[0][1], 'b'); + assert.equal(buffer.lines.get(0).get(0)[1], 'a'); + assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).get(0)[1], 'b'); }); }); }); @@ -272,34 +273,43 @@ describe('Buffer', () => { describe ('translateBufferLineToString', () => { it('should handle selecting a section of ascii text', () => { - buffer.lines.set(0, [ + const line = new TerminalLine(); + const data: [number, string, number, number][] = [ [ null, 'a', 1, 'a'.charCodeAt(0)], [ null, 'b', 1, 'b'.charCodeAt(0)], [ null, 'c', 1, 'c'.charCodeAt(0)], [ null, 'd', 1, 'd'.charCodeAt(0)] - ]); + ]; + for (let i = 0; i < data.length; ++i) line.push(data[i]); + buffer.lines.set(0, line); const str = buffer.translateBufferLineToString(0, true, 0, 2); assert.equal(str, 'ab'); }); it('should handle a cut-off double width character by including it', () => { - buffer.lines.set(0, [ + const line = new TerminalLine(); + const data: [number, string, number, number][] = [ [ null, '語', 2, 35486 ], [ null, '', 0, null], [ null, 'a', 1, 'a'.charCodeAt(0)] - ]); + ]; + for (let i = 0; i < data.length; ++i) line.push(data[i]); + buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); assert.equal(str1, '語'); }); it('should handle a zero width character in the middle of the string by not including it', () => { - buffer.lines.set(0, [ + const line = new TerminalLine(); + const data: [number, string, number, number][] = [ [ null, '語', 2, '語'.charCodeAt(0) ], [ null, '', 0, null], [ null, 'a', 1, 'a'.charCodeAt(0)] - ]); + ]; + for (let i = 0; i < data.length; ++i) line.push(data[i]); + buffer.lines.set(0, line); const str0 = buffer.translateBufferLineToString(0, true, 0, 1); assert.equal(str0, '語'); @@ -312,10 +322,13 @@ describe('Buffer', () => { }); it('should handle single width emojis', () => { - buffer.lines.set(0, [ + const line = new TerminalLine(); + const data: [number, string, number, number][] = [ [ null, '😁', 1, '😁'.charCodeAt(0) ], [ null, 'a', 1, 'a'.charCodeAt(0)] - ]); + ]; + for (let i = 0; i < data.length; ++i) line.push(data[i]); + buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); assert.equal(str1, '😁'); @@ -325,10 +338,13 @@ describe('Buffer', () => { }); it('should handle double width emojis', () => { - buffer.lines.set(0, [ + const line = new TerminalLine(); + let data: [number, string, number, number][] = [ [ null, '😁', 2, '😁'.charCodeAt(0) ], [ null, '', 0, null] - ]); + ]; + for (let i = 0; i < data.length; ++i) line.push(data[i]); + buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); assert.equal(str1, '😁'); @@ -336,11 +352,14 @@ describe('Buffer', () => { const str2 = buffer.translateBufferLineToString(0, true, 0, 2); assert.equal(str2, '😁'); - buffer.lines.set(0, [ + const line2 = new TerminalLine(); + data = [ [ null, '😁', 2, '😁'.charCodeAt(0) ], [ null, '', 0, null], [ null, 'a', 1, 'a'.charCodeAt(0)] - ]); + ]; + for (let i = 0; i < data.length; ++i) line2.push(data[i]); + buffer.lines.set(0, line2); const str3 = buffer.translateBufferLineToString(0, true, 0, 3); assert.equal(str3, '😁a'); diff --git a/src/Buffer.ts b/src/Buffer.ts index 5c843808..42ab01b5 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -7,6 +7,7 @@ import { CircularList } from './common/CircularList'; import { LineData, CharData, ITerminal, IBuffer } from './Types'; import { EventEmitter } from './EventEmitter'; import { IMarker } from 'xterm'; +import { TerminalLine } from './TerminalLine'; export const DEFAULT_ATTR = (0 << 18) | (257 << 9) | (256 << 0); export const CHAR_DATA_ATTR_INDEX = 0; @@ -27,7 +28,7 @@ export const NULL_CELL_CODE = 32; * - scroll position */ export class Buffer implements IBuffer { - public lines: CircularList; + public lines: CircularList; public ydisp: number; public ybase: number; public y: number; @@ -97,7 +98,7 @@ export class Buffer implements IBuffer { this.ybase = 0; this.y = 0; this.x = 0; - this.lines = new CircularList(this._getCorrectBufferLength(this._terminal.rows)); + this.lines = new CircularList(this._getCorrectBufferLength(this._terminal.rows)); this.scrollTop = 0; this.scrollBottom = this._terminal.rows - 1; this.setupTabStops(); @@ -223,7 +224,7 @@ export class Buffer implements IBuffer { let endIndex = endCol; for (let i = 0; i < line.length; i++) { - const char = line[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 diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 42b1b095..08486cd0 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -381,18 +381,20 @@ export class InputHandler extends Disposable implements IInputHandler { // since they always follow a cell consuming char // therefore we can test for buffer.x to avoid overflow left if (!chWidth && buffer.x) { - if (bufferRow[buffer.x - 1]) { - if (!bufferRow[buffer.x - 1][CHAR_DATA_WIDTH_INDEX]) { + const chMinusOne = bufferRow.get(buffer.x - 1); + if (chMinusOne) { + if (!chMinusOne[CHAR_DATA_WIDTH_INDEX]) { // found empty cell after fullwidth, need to go 2 cells back // it is save to step 2 cells back here // since an empty cell is only set by fullwidth chars - if (bufferRow[buffer.x - 2]) { - bufferRow[buffer.x - 2][CHAR_DATA_CHAR_INDEX] += char; - bufferRow[buffer.x - 2][CHAR_DATA_CODE_INDEX] = code; + const chMinusTwo = bufferRow.get(buffer.x - 2); + if (chMinusTwo) { + chMinusTwo[CHAR_DATA_CHAR_INDEX] += char; + chMinusTwo[CHAR_DATA_CODE_INDEX] = code; } } else { - bufferRow[buffer.x - 1][CHAR_DATA_CHAR_INDEX] += char; - bufferRow[buffer.x - 1][CHAR_DATA_CODE_INDEX] = code; + chMinusOne[CHAR_DATA_CHAR_INDEX] += char; + chMinusOne[CHAR_DATA_CODE_INDEX] = code; } } continue; @@ -412,7 +414,7 @@ export class InputHandler extends Disposable implements IInputHandler { } else { // The line already exists (eg. the initial viewport), mark it as a // wrapped line - (buffer.lines.get(buffer.y)).isWrapped = true; + buffer.lines.get(buffer.y).isWrapped = true; } // row changed, get it again bufferRow = buffer.lines.get(buffer.y + buffer.ybase); @@ -435,10 +437,11 @@ export class InputHandler extends Disposable implements IInputHandler { // remove last cell // if it's width is 0, we have to adjust the second last cell as well const removed = bufferRow.pop(); + const chMinusTwo = bufferRow.get(buffer.x - 2); if (removed[CHAR_DATA_WIDTH_INDEX] === 0 - && bufferRow[this._terminal.cols - 2] - && bufferRow[this._terminal.cols - 2][CHAR_DATA_WIDTH_INDEX] === 2) { - bufferRow[this._terminal.cols - 2] = [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; + && chMinusTwo + && chMinusTwo[CHAR_DATA_WIDTH_INDEX] === 2) { + bufferRow.set(this._terminal.cols - 2, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); } // insert empty cell at cursor @@ -447,11 +450,11 @@ export class InputHandler extends Disposable implements IInputHandler { } // write current char to buffer and advance cursor - bufferRow[buffer.x++] = [curAttr, char, chWidth, code]; + bufferRow.set(buffer.x++, [curAttr, char, chWidth, code]); // fullwidth char - also set next cell to placeholder stub and advance cursor if (chWidth === 2) { - bufferRow[buffer.x++] = [curAttr, '', 0, undefined]; + bufferRow.set(buffer.x++, [curAttr, '', 0, undefined]); } } this._terminal.updateRange(buffer.y); @@ -929,7 +932,7 @@ export class InputHandler extends Disposable implements IInputHandler { const ch: CharData = [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm while (param-- && j < this._terminal.cols) { - buffer.lines.get(row)[j++] = ch; + buffer.lines.get(row).set(j++, ch); } } @@ -988,10 +991,10 @@ export class InputHandler extends Disposable implements IInputHandler { const buffer = this._terminal.buffer; const line = buffer.lines.get(buffer.ybase + buffer.y); - const ch = line[buffer.x - 1] || [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; + const ch = line.get(buffer.x - 1) || [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; while (param--) { - line[buffer.x++] = ch; + line.set(buffer.x++, ch); } } diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 7f4d5603..183571e8 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -9,6 +9,7 @@ import { ILinkMatcher, LineData, ITerminal } from './Types'; import { Linkifier } from './Linkifier'; import { MockBuffer, MockTerminal } from './utils/TestUtils.test'; import { CircularList } from './common/CircularList'; +import { TerminalLine } from './TerminalLine'; class TestLinkifier extends Linkifier { constructor(terminal: ITerminal) { @@ -42,14 +43,14 @@ describe('Linkifier', () => { terminal = new MockTerminal(); terminal.cols = 100; terminal.buffer = new MockBuffer(); - (terminal.buffer).setLines(new CircularList(20)); + (terminal.buffer).setLines(new CircularList(20)); terminal.buffer.ydisp = 0; linkifier = new TestLinkifier(terminal); mouseZoneManager = new TestMouseZoneManager(); }); - function stringToRow(text: string): LineData { - const result: LineData = []; + function stringToRow(text: string): TerminalLine { + const result = new TerminalLine(); for (let i = 0; i < text.length; i++) { result.push([0, text.charAt(i), 1, text.charCodeAt(i)]); } diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 9ab770d9..c82bf513 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -7,6 +7,7 @@ import { IMouseZoneManager } from './ui/Types'; import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, LineData } from './Types'; import { MouseZone } from './ui/MouseZoneManager'; import { EventEmitter } from './EventEmitter'; +import { TerminalLine } from './TerminalLine'; /** * The Linkifier applies links to rows shortly after they have been refreshed. @@ -169,7 +170,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { return; } // If the first row is wrapped, backtrack to find the origin row and linkify that - let line: LineData; + let line: TerminalLine; do { rowIndex--; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 20e1ca60..70e26fb4 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,6 +10,7 @@ import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { LineData, CharData, ITerminal, IBuffer } from './Types'; import { MockTerminal } from './utils/TestUtils.test'; +import { TerminalLine } from './TerminalLine'; class TestMockTerminal extends MockTerminal { emit(event: string, data: any): void {} @@ -52,16 +53,18 @@ describe('SelectionManager', () => { selectionManager = new TestSelectionManager(terminal, null); }); - function stringToRow(text: string): LineData { - const result: LineData = []; + function stringToRow(text: string): TerminalLine { + const result = new TerminalLine(); for (let i = 0; i < text.length; i++) { result.push([0, text.charAt(i), 1, text.charCodeAt(i)]); } return result; } - function stringArrayToRow(chars: string[]): LineData { - return chars.map(c => [0, c, 1, c.charCodeAt(0)]); + function stringArrayToRow(chars: string[]): TerminalLine { + const line = new TerminalLine(); + chars.map(c => line.push([0, c, 1, c.charCodeAt(0)])); + return line; } describe('_selectWordAt', () => { @@ -97,7 +100,8 @@ describe('SelectionManager', () => { }); it('should expand selection for wide characters', () => { // Wide characters use a special format - buffer.lines.set(0, [ + const line = new TerminalLine(); + const data: [number, string, number, number][] = [ [null, '中', 2, '中'.charCodeAt(0)], [null, '', 0, null], [null, '文', 2, '文'.charCodeAt(0)], @@ -113,7 +117,9 @@ describe('SelectionManager', () => { [null, 'f', 1, 'f'.charCodeAt(0)], [null, 'o', 1, 'o'.charCodeAt(0)], [null, 'o', 1, 'o'.charCodeAt(0)] - ]); + ]; + for (let i = 0; i < data.length; ++i) line.push(data[i]); + buffer.lines.set(0, line); // Ensure wide characters take up 2 columns selectionManager.selectWordAt([0, 0]); assert.equal(selectionManager.selectionText, '中文'); diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 7f7204fc..422dbc14 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -11,6 +11,7 @@ import { EventEmitter } from './EventEmitter'; import { SelectionModel } from './SelectionModel'; import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer'; import { AltClickHandler } from './handlers/AltClickHandler'; +import { TerminalLine } from './TerminalLine'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -204,7 +205,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager for (let i = start[1] + 1; i <= end[1] - 1; i++) { const bufferLine = this._buffer.lines.get(i); const lineText = this._buffer.translateBufferLineToString(i, true); - if ((bufferLine).isWrapped) { + if (bufferLine.isWrapped) { result[result.length - 1] += lineText; } else { result.push(lineText); @@ -215,7 +216,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager if (start[1] !== end[1]) { const bufferLine = this._buffer.lines.get(end[1]); const lineText = this._buffer.translateBufferLineToString(end[1], true, 0, end[0]); - if ((bufferLine).isWrapped) { + if (bufferLine.isWrapped) { result[result.length - 1] += lineText; } else { result.push(lineText); @@ -500,7 +501,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // If the mouse is over the second half of a wide character, adjust the // selection to cover the whole character - const char = line[this._model.selectionStart[0]]; + const char = line.get(this._model.selectionStart[0]); if (char[CHAR_DATA_WIDTH_INDEX] === 0) { this._model.selectionStart[0]++; } @@ -590,7 +591,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // selection. Note that selections at the very end of the line will never // have a character. if (this._model.selectionEnd[1] < this._buffer.lines.length) { - const char = this._buffer.lines.get(this._model.selectionEnd[1])[this._model.selectionEnd[0]]; + const char = this._buffer.lines.get(this._model.selectionEnd[1]).get(this._model.selectionEnd[0]); if (char && char[CHAR_DATA_WIDTH_INDEX] === 0) { this._model.selectionEnd[0]++; } @@ -661,10 +662,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * latter takes into account wide characters. * @param coords The coordinates to find the 2 index for. */ - private _convertViewportColToCharacterIndex(bufferLine: any, coords: [number, number]): number { + private _convertViewportColToCharacterIndex(bufferLine: TerminalLine, coords: [number, number]): number { let charIndex = coords[0]; for (let i = 0; coords[0] >= i; i++) { - const char = bufferLine[i]; + const char = bufferLine.get(i); if (char[CHAR_DATA_WIDTH_INDEX] === 0) { // Wide characters aren't included in the line string so decrement the // index so the index is back on the wide character. @@ -733,24 +734,24 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Consider the initial position, skip it and increment the wide char // variable - if (bufferLine[startCol][CHAR_DATA_WIDTH_INDEX] === 0) { + if (bufferLine.get(startCol)[CHAR_DATA_WIDTH_INDEX] === 0) { leftWideCharCount++; startCol--; } - if (bufferLine[endCol][CHAR_DATA_WIDTH_INDEX] === 2) { + if (bufferLine.get(endCol)[CHAR_DATA_WIDTH_INDEX] === 2) { rightWideCharCount++; endCol++; } // Adjust the end index for characters whose length are > 1 (emojis) - if (bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length > 1) { - rightLongCharOffset += bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length - 1; - endIndex += bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length - 1; + if (bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length > 1) { + rightLongCharOffset += bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length - 1; + endIndex += bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length - 1; } // Expand the string in both directions until a space is hit - while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine[startCol - 1])) { - const char = bufferLine[startCol - 1]; + while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.get(startCol - 1))) { + const char = bufferLine.get(startCol - 1); if (char[CHAR_DATA_WIDTH_INDEX] === 0) { // If the next character is a wide char, record it and skip the column leftWideCharCount++; @@ -764,8 +765,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager startIndex--; startCol--; } - while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine[endCol + 1])) { - const char = bufferLine[endCol + 1]; + while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.get(endCol + 1))) { + const char = bufferLine.get(endCol + 1); if (char[CHAR_DATA_WIDTH_INDEX] === 2) { // If the next character is a wide char, record it and skip the column rightWideCharCount++; @@ -808,9 +809,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Recurse upwards if the line is wrapped and the word wraps to the above line if (followWrappedLinesAbove) { - if (start === 0 && bufferLine[0][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + if (start === 0 && bufferLine.get(0)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { const previousBufferLine = this._buffer.lines.get(coords[1] - 1); - if (previousBufferLine && (bufferLine).isWrapped && previousBufferLine[this._terminal.cols - 1][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + if (previousBufferLine && (bufferLine).isWrapped && previousBufferLine.get(this._terminal.cols - 1)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false); if (previousLineWordPosition) { const offset = this._terminal.cols - previousLineWordPosition.start; @@ -823,9 +824,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager // Recurse downwards if the line is wrapped and the word wraps to the next line if (followWrappedLinesBelow) { - if (start + length === this._terminal.cols && bufferLine[this._terminal.cols - 1][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + if (start + length === this._terminal.cols && bufferLine.get(this._terminal.cols - 1)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { const nextBufferLine = this._buffer.lines.get(coords[1] + 1); - if (nextBufferLine && (nextBufferLine).isWrapped && nextBufferLine[0][CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { + if (nextBufferLine && (nextBufferLine).isWrapped && nextBufferLine.get(0)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) { const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true); if (nextLineWordPosition) { length += nextLineWordPosition.length; diff --git a/src/Terminal.integration.ts b/src/Terminal.integration.ts index ee38133b..47aeb44b 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)[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/Terminal.test.ts b/src/Terminal.test.ts index 0ea854e2..4344553e 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -337,62 +337,62 @@ describe('term.js addons', () => { describe('scroll() function', () => { describe('when scrollback > 0', () => { it('should create a new line and scroll', () => { - term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; + 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.y = INIT_ROWS - 1; // Move cursor to last line term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); - assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(INIT_ROWS)[0][CHAR_DATA_CHAR_INDEX], ' '); + 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], ' '); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + 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.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c'); + 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'); }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; - term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd'; - term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e'; + 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.y = 3; term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); - assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a', '\'a\' should be pushed to the scrollback'); - assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'c'); - assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(5)[0][CHAR_DATA_CHAR_INDEX], 'e'); + assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a', '\'a\' should be pushed to the scrollback'); + 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(5).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; - term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd'; - term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e'; + 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.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer'); - assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e'); + 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(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); }); }); @@ -403,65 +403,65 @@ describe('term.js addons', () => { }); it('should create a new line and shift everything up', () => { - term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + 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.y = INIT_ROWS - 1; // Move cursor to last line assert.equal(term.buffer.lines.length, INIT_ROWS); term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); // 'a' gets pushed out of buffer - assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], ' '); - assert.equal(term.buffer.lines.get(INIT_ROWS - 2)[0][CHAR_DATA_CHAR_INDEX], 'c'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1)[0][CHAR_DATA_CHAR_INDEX], ' '); + 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(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], ' '); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; + 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.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c'); + 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'); }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; - term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd'; - term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e'; + 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.y = 3; term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'b'); - assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c'); - assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e'); + 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(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX] = 'a'; - term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX] = 'b'; - term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX] = 'c'; - term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX] = 'd'; - term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX] = 'e'; + 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.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; term.scroll(); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0)[0][CHAR_DATA_CHAR_INDEX], 'a'); - assert.equal(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer'); - assert.equal(term.buffer.lines.get(2)[0][CHAR_DATA_CHAR_INDEX], 'd'); - assert.equal(term.buffer.lines.get(3)[0][CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4)[0][CHAR_DATA_CHAR_INDEX], 'e'); + 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(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e'); }); }); }); @@ -654,11 +654,11 @@ describe('term.js addons', () => { const high = String.fromCharCode(0xD800); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.write(high + String.fromCharCode(i)); - const tchar = term.buffer.lines.get(0)[0]; + const tchar = term.buffer.lines.get(0).get(0); 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)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' '); term.reset(); } }); @@ -667,9 +667,9 @@ describe('term.js addons', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.write(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0)[term.buffer.x - 1][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0)[term.buffer.x - 1][CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(' '); + 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(' '); term.reset(); } }); @@ -679,10 +679,10 @@ describe('term.js addons', () => { term.buffer.x = term.cols - 1; term.wraparoundMode = true; term.write('a' + high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(1)[0][CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + 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(' '); term.reset(); } }); @@ -693,9 +693,9 @@ describe('term.js addons', () => { term.wraparoundMode = false; term.write('a' + high + String.fromCharCode(i)); // auto wraparound mode should cut off the rest of the line - expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(term.buffer.lines.get(1)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + 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(' '); term.reset(); } }); @@ -704,11 +704,11 @@ describe('term.js addons', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.write(high); term.write(String.fromCharCode(i)); - const tchar = term.buffer.lines.get(0)[0]; + const tchar = term.buffer.lines.get(0).get(0); 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)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); + expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' '); term.reset(); } }); @@ -717,30 +717,30 @@ describe('term.js addons', () => { describe('unicode - combining characters', () => { it('café', () => { term.write('cafe\u0301'); - expect(term.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); - expect(term.buffer.lines.get(0)[3][CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(0)[3][CHAR_DATA_WIDTH_INDEX]).eql(1); + expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); + expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_CHAR_INDEX].length).eql(2); + expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_WIDTH_INDEX]).eql(1); }); it('café - end of line', () => { term.buffer.x = term.cols - 1 - 3; term.write('cafe\u0301'); - expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); - expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_CHAR_INDEX].length).eql(2); - expect(term.buffer.lines.get(0)[term.cols - 1][CHAR_DATA_WIDTH_INDEX]).eql(1); - expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX]).eql(' '); - expect(term.buffer.lines.get(0)[1][CHAR_DATA_CHAR_INDEX].length).eql(1); - expect(term.buffer.lines.get(0)[1][CHAR_DATA_WIDTH_INDEX]).eql(1); + 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_WIDTH_INDEX]).eql(1); }); it('multiple combined é', () => { term.wraparoundMode = true; term.write(Array(100).join('e\u0301')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0)[i]; + const tchar = term.buffer.lines.get(0).get(i); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); } - const tchar = term.buffer.lines.get(1)[0]; + const tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); @@ -749,12 +749,12 @@ describe('term.js addons', () => { term.wraparoundMode = true; term.write(Array(100).join('\uD800\uDC00\u0301')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0)[i]; + const tchar = term.buffer.lines.get(0).get(i); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); } - const tchar = term.buffer.lines.get(1)[0]; + const tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1); @@ -777,7 +777,7 @@ describe('term.js addons', () => { term.wraparoundMode = true; term.write(Array(50).join('¥')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0)[i]; + const tchar = term.buffer.lines.get(0).get(i); if (i % 2) { expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); @@ -788,7 +788,7 @@ describe('term.js addons', () => { expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); } } - const tchar = term.buffer.lines.get(1)[0]; + const tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); @@ -798,7 +798,7 @@ describe('term.js addons', () => { term.buffer.x = 1; term.write(Array(50).join('¥')); for (let i = 1; i < term.cols - 1; ++i) { - const tchar = term.buffer.lines.get(0)[i]; + const tchar = term.buffer.lines.get(0).get(i); if (!(i % 2)) { expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); @@ -809,11 +809,11 @@ describe('term.js addons', () => { expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); } } - let tchar = term.buffer.lines.get(0)[term.cols - 1]; + 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_WIDTH_INDEX]).eql(1); - tchar = term.buffer.lines.get(1)[0]; + tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); @@ -823,7 +823,7 @@ describe('term.js addons', () => { term.buffer.x = 1; term.write(Array(50).join('¥\u0301')); for (let i = 1; i < term.cols - 1; ++i) { - const tchar = term.buffer.lines.get(0)[i]; + const tchar = term.buffer.lines.get(0).get(i); if (!(i % 2)) { expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); @@ -834,11 +834,11 @@ describe('term.js addons', () => { expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); } } - let tchar = term.buffer.lines.get(0)[term.cols - 1]; + 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_WIDTH_INDEX]).eql(1); - tchar = term.buffer.lines.get(1)[0]; + tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); @@ -847,7 +847,7 @@ describe('term.js addons', () => { term.wraparoundMode = true; term.write(Array(50).join('¥\u0301')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0)[i]; + const tchar = term.buffer.lines.get(0).get(i); if (i % 2) { expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); @@ -858,7 +858,7 @@ describe('term.js addons', () => { expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); } } - const tchar = term.buffer.lines.get(1)[0]; + const tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); @@ -868,7 +868,7 @@ describe('term.js addons', () => { term.buffer.x = 1; term.write(Array(50).join('\ud843\ude6d\u0301')); for (let i = 1; i < term.cols - 1; ++i) { - const tchar = term.buffer.lines.get(0)[i]; + const tchar = term.buffer.lines.get(0).get(i); if (!(i % 2)) { expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); @@ -879,11 +879,11 @@ describe('term.js addons', () => { expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); } } - let tchar = term.buffer.lines.get(0)[term.cols - 1]; + 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_WIDTH_INDEX]).eql(1); - tchar = term.buffer.lines.get(1)[0]; + tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); @@ -892,7 +892,7 @@ describe('term.js addons', () => { term.wraparoundMode = true; term.write(Array(50).join('\ud843\ude6d\u0301')); for (let i = 0; i < term.cols; ++i) { - const tchar = term.buffer.lines.get(0)[i]; + const tchar = term.buffer.lines.get(0).get(i); if (i % 2) { expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(''); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0); @@ -903,7 +903,7 @@ describe('term.js addons', () => { expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); } } - const tchar = term.buffer.lines.get(1)[0]; + const tchar = term.buffer.lines.get(1).get(0); expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301'); expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3); expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2); @@ -918,10 +918,10 @@ describe('term.js addons', () => { term.insertMode = true; term.write('abcde'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(term.buffer.lines.get(0)[14][CHAR_DATA_CHAR_INDEX]).eql('e'); - expect(term.buffer.lines.get(0)[15][CHAR_DATA_CHAR_INDEX]).eql('0'); - expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql('4'); + expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a'); + expect(term.buffer.lines.get(0).get(14)[CHAR_DATA_CHAR_INDEX]).eql('e'); + expect(term.buffer.lines.get(0).get(15)[CHAR_DATA_CHAR_INDEX]).eql('0'); + expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql('4'); }); it('fullwidth - insert', () => { term.write(Array(9).join('0123456789').slice(-80)); @@ -930,11 +930,11 @@ describe('term.js addons', () => { term.insertMode = true; term.write('¥¥¥'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql(''); - expect(term.buffer.lines.get(0)[14][CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0)[15][CHAR_DATA_CHAR_INDEX]).eql(''); - expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql('3'); + expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(term.buffer.lines.get(0).get(14)[CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(term.buffer.lines.get(0).get(15)[CHAR_DATA_CHAR_INDEX]).eql(''); + expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql('3'); }); it('fullwidth - right border', () => { term.write(Array(41).join('¥')); @@ -943,14 +943,14 @@ describe('term.js addons', () => { term.insertMode = true; term.write('a'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0)[10][CHAR_DATA_CHAR_INDEX]).eql('a'); - expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql(' '); // fullwidth char got replaced + 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 term.write('b'); expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0)[11][CHAR_DATA_CHAR_INDEX]).eql('b'); - expect(term.buffer.lines.get(0)[12][CHAR_DATA_CHAR_INDEX]).eql('¥'); - expect(term.buffer.lines.get(0)[79][CHAR_DATA_CHAR_INDEX]).eql(''); // empty cell after fullwidth + expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('b'); + expect(term.buffer.lines.get(0).get(12)[CHAR_DATA_CHAR_INDEX]).eql('¥'); + expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(''); // empty cell after fullwidth }); }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index a310d543..5e3f8f50 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; +import { TerminalLine } from './TerminalLine'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -1719,7 +1720,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } const ch: CharData = [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm for (; x < this.cols; x++) { - line[x] = ch; + line.set(x, ch); } this.updateRange(y); } @@ -1737,7 +1738,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II const ch: CharData = [this.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // xterm x++; while (x--) { - line[x] = ch; + line.set(x, ch); } this.updateRange(y); } @@ -1777,21 +1778,21 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param cols The number of columns in the terminal, if this is not * set, the terminal's current column count would be used. */ - public blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData { + public blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): TerminalLine { const attr = cur ? this.eraseAttr() : DEFAULT_ATTR; const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // width defaults to 1 halfwidth character - const line: LineData = []; + const line = new TerminalLine(); // TODO: It is not ideal that this is a property on an array, a buffer line // class should be added that will hold this data and other useful functions. if (isWrapped) { - (line).isWrapped = isWrapped; + line.isWrapped = isWrapped; } cols = cols || this.cols; for (let i = 0; i < cols; i++) { - line[i] = ch; + line.set(i, ch); } return line; diff --git a/src/TerminalLine.ts b/src/TerminalLine.ts new file mode 100644 index 00000000..fcaa99df --- /dev/null +++ b/src/TerminalLine.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { CharData } from './Types'; + +export class TerminalLine { + private _data: CharData[]; + public isWrapped = false; + length: number; + constructor() { + this._data = []; + this.length = this._data.length; + + // for debugging purpose: + // throw Error when something tries to do number index access + // TODO: remove when done with transition + for (let i = 0; i < 100; ++i) { + Object.defineProperty(this, i, { + get: () => { + throw new Error('get per index access is disabled'); + }, + set: (value: any) => { + throw new Error('set per index access is disabled'); + } + }); + } + + } + get(index: number): CharData { + return this._data[index]; + } + set(index: number, data: CharData): void { + this._data[index] = data; + // TODO: unref old, ref new + } + pop(): CharData | undefined { + // TODO: unref here, change CharData to [typeof Attributes, ...] + const data = this._data.pop(); + this.length = this._data.length; + return data; + } + push(data: CharData): void { + this._data.push(data); + this.length = this._data.length; + // TODO: ref here + } + splice(start: number, deleteCount: number, ...items: CharData[]): CharData[] { + const removed = this._data.splice(start, deleteCount, ...items); + this.length = this._data.length; + // TODO: ref new, unref old + return removed; + } + /** to be called when a line gets removed */ + release(): void { + // TODO: unref here + } +} diff --git a/src/Types.ts b/src/Types.ts index ba1a7990..f7784b5f 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -7,6 +7,7 @@ import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, import { IColorSet, IRenderer } from './renderer/Types'; import { IMouseZoneManager } from './ui/Types'; import { ICharset } from './core/Types'; +import { TerminalLine } from './TerminalLine'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -74,7 +75,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { eraseRight(x: number, y: number): void; eraseLine(y: number): void; eraseLeft(x: number, y: number): void; - blankLine(cur?: boolean, isWrapped?: boolean): LineData; + blankLine(cur?: boolean, isWrapped?: boolean): TerminalLine; is(term: string): boolean; setgCharset(g: number, charset: ICharset): void; resize(x: number, y: number): void; @@ -233,7 +234,7 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce cancel(ev: Event, force?: boolean): boolean | void; log(text: string): void; showCursor(): void; - blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData; + blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): TerminalLine; } export interface IBufferAccessor { @@ -272,7 +273,7 @@ export interface ITerminalOptions extends IPublicTerminalOptions { } export interface IBuffer { - readonly lines: ICircularList; + readonly lines: ICircularList; ydisp: number; ybase: number; y: number; diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 7ac084db..48556ac5 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -5,6 +5,7 @@ import { ITerminal, ICircularList, LineData } from '../Types'; import { C0 } from '../common/data/EscapeSequences'; +import { TerminalLine } from '../TerminalLine'; const enum Direction { UP = 'A', @@ -18,7 +19,7 @@ export class AltClickHandler { private _startCol: number; private _endRow: number; private _endCol: number; - private _lines: ICircularList; + private _lines: ICircularList; constructor( private _mouseEvent: MouseEvent, diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index 85d94601..d427ef5e 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -1,11 +1,11 @@ import { assert } from 'chai'; -import { LineData, CharData } from '../Types'; import { MockTerminal, MockBuffer } from '../utils/TestUtils.test'; import { CircularList } from '../common/CircularList'; import { ICharacterJoinerRegistry } from './Types'; import { CharacterJoinerRegistry } from './CharacterJoinerRegistry'; +import { TerminalLine } from '../TerminalLine'; describe('CharacterJoinerRegistry', () => { let registry: ICharacterJoinerRegistry; @@ -14,22 +14,25 @@ describe('CharacterJoinerRegistry', () => { const terminal = new MockTerminal(); terminal.cols = 16; terminal.buffer = new MockBuffer(); - const lines = new CircularList(7); - lines.set(0, lineData('a -> b -> c -> d')); - lines.set(1, lineData('a -> b => c -> d')); - lines.set(2, [...lineData('a -> b -', 0xFFFFFFFF), ...lineData('> c -> d', 0)]); - lines.set(3, lineData('no joined ranges')); - lines.set(4, []); - lines.set(5, [...lineData('a', 0x11111111), ...lineData(' -> b -> c -> '), ...lineData('d', 0x22222222)]); - lines.set(6, [ - ...lineData('wi'), - [0, '¥', 2, '¥'.charCodeAt(0)], - [0, '', 0, null], - ...lineData('deemo'), - [0, '\xf0\x9f\x98\x81', 1, 128513], - [0, ' ', 1, ' '.charCodeAt(0)], - ...lineData('jiabc') - ]); + const lines = new CircularList(7); + lines.set(0, lineData([['a -> b -> c -> d']])); + lines.set(1, lineData([['a -> b => c -> d']])); + lines.set(2, lineData([['a -> b -', 0xFFFFFFFF], ['> c -> d', 0]])); + + lines.set(3, lineData([['no joined ranges']])); + lines.set(4, new TerminalLine()); + lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]])); + const line6 = lineData([['wi']]); + line6.push([0, '¥', 2, '¥'.charCodeAt(0)]); + line6.push([0, '', 0, null]); + let sub = lineData([['deemo']]); + for (let i = 0; i < sub.length; ++i) line6.push(sub.get(i)); + line6.push([0, '\xf0\x9f\x98\x81', 1, 128513]); + line6.push([0, ' ', 1, ' '.charCodeAt(0)]); + sub = lineData([['jiabc']]); + for (let i = 0; i < sub.length; ++i) line6.push(sub.get(i)); + lines.set(6, line6); + (terminal.buffer).setLines(lines); terminal.buffer.ydisp = 0; registry = new CharacterJoinerRegistry(terminal); @@ -257,8 +260,19 @@ describe('CharacterJoinerRegistry', () => { }); }); -function lineData(line: string, attr: number = 0): LineData { - return line.split('').map(char => [attr, char, 1, char.charCodeAt(0)]); +interface IPartialLineData { + [0]: string; + [1]?: number; +} + +function lineData(data: IPartialLineData[]): TerminalLine { + const tline = new TerminalLine(); + for (let i = 0; i < data.length; ++i) { + const line = data[i][0]; + const attr = data[i][1] || 0; + line.split('').map(char => tline.push([attr, char, 1, char.charCodeAt(0)])); + } + return tline; } function substringJoiner(substring: string): (sequence: string) => [number, number][] { diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 723b6f38..b8d1a3cd 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,6 +1,7 @@ import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; import { ITerminal, LineData } from '../Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; +import { TerminalLine } from '../TerminalLine'; export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { @@ -51,10 +52,10 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { let rangeStartColumn = 0; let currentStringIndex = 0; let rangeStartStringIndex = 0; - let rangeAttr = line[0][CHAR_DATA_ATTR_INDEX] >> 9; + let rangeAttr = line.get(0)[CHAR_DATA_ATTR_INDEX] >> 9; for (let x = 0; x < this._terminal.cols; x++) { - const charData = line[x]; + const charData = line.get(x); const chars = charData[CHAR_DATA_CHAR_INDEX]; const width = charData[CHAR_DATA_WIDTH_INDEX]; const attr = charData[CHAR_DATA_ATTR_INDEX] >> 9; @@ -115,7 +116,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { * @param startIndex Start position of the range to search in the string (inclusive) * @param endIndex End position of the range to search in the string (exclusive) */ - private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: LineData, startCol: number): [number, number][] { + private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: TerminalLine, startCol: number): [number, number][] { const text = line.substring(startIndex, endIndex); // At this point we already know that there is at least one joiner so // we can just pull its value and assign it directly rather than @@ -140,7 +141,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { * @param line Cell data for the relevant line in the terminal * @param startCol Offset within the line to start from */ - private _stringRangesToCellRanges(ranges: [number, number][], line: LineData, startCol: number): void { + private _stringRangesToCellRanges(ranges: [number, number][], line: TerminalLine, startCol: number): void { let currentRangeIndex = 0; let currentRangeStarted = false; let currentStringIndex = 0; @@ -152,7 +153,7 @@ export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { } for (let x = startCol; x < this._terminal.cols; x++) { - const charData = line[x]; + const charData = line.get(x); const width = charData[CHAR_DATA_WIDTH_INDEX]; const length = charData[CHAR_DATA_CHAR_INDEX].length; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 2480d8af..08a14739 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -127,7 +127,7 @@ export class CursorRenderLayer extends BaseRenderLayer { return; } - const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x]; + const charData = terminal.buffer.lines.get(cursorY).get(terminal.buffer.x); if (!charData) { return; } diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index d40aa28d..7f10e7c9 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -71,7 +71,7 @@ export class TextRenderLayer extends BaseRenderLayer { const line = terminal.buffer.lines.get(row); const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; for (let x = 0; x < terminal.cols; x++) { - const charData = line[x]; + const charData = line.get(x); let code: number = charData[CHAR_DATA_CODE_INDEX]; // Can either represent character(s) for a single cell or multiple cells @@ -124,7 +124,7 @@ export class TextRenderLayer extends BaseRenderLayer { // get removed, and `a` would not re-render because it thinks it's // already in the correct state. // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; - if (lastCharX < line.length - 1 && line[lastCharX + 1][CHAR_DATA_CODE_INDEX] === NULL_CELL_CODE) { + if (lastCharX < line.length - 1 && line.get(lastCharX + 1)[CHAR_DATA_CODE_INDEX] === NULL_CELL_CODE) { width = 2; // this._clearChar(x + 1, y); // The overlapping char's char data will force a clear and render when the diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index c90dd6e5..32284615 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -9,11 +9,12 @@ import { DomRendererRowFactory } from './DomRendererRowFactory'; import { LineData } from '../../Types'; import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer'; import { FLAGS } from '../Types'; +import { TerminalLine } from '../../TerminalLine'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; let rowFactory: DomRendererRowFactory; - let lineData: LineData; + let lineData: TerminalLine; beforeEach(() => { dom = new jsdom.JSDOM(''); @@ -31,9 +32,9 @@ describe('DomRendererRowFactory', () => { }); it('should set correct attributes for double width characters', () => { - lineData[0] = [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]; + lineData.set(0, [DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]); // There should be no element for the following "empty" cell - lineData[1] = [DEFAULT_ATTR, '', 0, undefined]; + lineData.set(1, [DEFAULT_ATTR, '', 0, undefined]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), '' @@ -49,8 +50,8 @@ describe('DomRendererRowFactory', () => { }); it('should not render cells that go beyond the terminal\'s columns', () => { - lineData[0] = [DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]; - lineData[1] = [DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]; + lineData.set(0, [DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]); + lineData.set(1, [DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 1); assert.equal(getFragmentHtml(fragment), 'a' @@ -59,7 +60,7 @@ describe('DomRendererRowFactory', () => { describe('attributes', () => { it('should add class for bold', () => { - lineData[0] = [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]; + lineData.set(0, [DEFAULT_ATTR | (FLAGS.BOLD << 18), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' + @@ -68,7 +69,7 @@ describe('DomRendererRowFactory', () => { }); it('should add class for italic', () => { - lineData[0] = [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]; + lineData.set(0, [DEFAULT_ATTR | (FLAGS.ITALIC << 18), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' + @@ -79,7 +80,7 @@ describe('DomRendererRowFactory', () => { it('should add classes for 256 foreground colors', () => { const defaultAttrNoFgColor = (0 << 9) | (256 << 0); for (let i = 0; i < 256; i++) { - lineData[0] = [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]; + lineData.set(0, [defaultAttrNoFgColor | (i << 9), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` + @@ -91,7 +92,7 @@ describe('DomRendererRowFactory', () => { it('should add classes for 256 background colors', () => { const defaultAttrNoBgColor = (257 << 9) | (0 << 0); for (let i = 0; i < 256; i++) { - lineData[0] = [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]; + lineData.set(0, [defaultAttrNoBgColor | (i << 0), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` + @@ -101,7 +102,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert colors', () => { - lineData[0] = [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]; + lineData.set(0, [(FLAGS.INVERSE << 18) | (2 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' + @@ -110,7 +111,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default fg color', () => { - lineData[0] = [(FLAGS.INVERSE << 18) | (257 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]; + lineData.set(0, [(FLAGS.INVERSE << 18) | (257 << 9) | (1 << 0), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' + @@ -119,7 +120,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default bg color', () => { - lineData[0] = [(FLAGS.INVERSE << 18) | (1 << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]; + lineData.set(0, [(FLAGS.INVERSE << 18) | (1 << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), 'a' + @@ -129,7 +130,7 @@ describe('DomRendererRowFactory', () => { it('should turn bold fg text bright', () => { for (let i = 0; i < 8; i++) { - lineData[0] = [(FLAGS.BOLD << 18) | (i << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]; + lineData.set(0, [(FLAGS.BOLD << 18) | (i << 9) | (256 << 0), 'a', 1, 'a'.charCodeAt(0)]); const fragment = rowFactory.createRow(lineData, false, 0, 5, 20); assert.equal(getFragmentHtml(fragment), `a` + @@ -146,8 +147,8 @@ describe('DomRendererRowFactory', () => { return element.innerHTML; } - function createEmptyLineData(cols: number): LineData { - const lineData: LineData = []; + function createEmptyLineData(cols: number): TerminalLine { + const lineData = new TerminalLine(); for (let i = 0; i < cols; i++) { lineData.push([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); } diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index eedb1d34..d2a43b06 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -6,6 +6,7 @@ import { LineData } from '../../Types'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer'; import { FLAGS } from '../Types'; +import { TerminalLine } from '../../TerminalLine'; export const BOLD_CLASS = 'xterm-bold'; export const ITALIC_CLASS = 'xterm-italic'; @@ -17,7 +18,7 @@ export class DomRendererRowFactory { ) { } - public createRow(lineData: LineData, isCursorRow: boolean, cursorX: number, cellWidth: number, cols: number): DocumentFragment { + public createRow(lineData: TerminalLine, isCursorRow: boolean, cursorX: number, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); let colCount = 0; @@ -27,7 +28,7 @@ export class DomRendererRowFactory { continue; } - const charData = lineData[x]; + const charData = lineData.get(x); const char: string = charData[CHAR_DATA_CHAR_INDEX]; const attr: number = charData[CHAR_DATA_ATTR_INDEX]; const width: number = charData[CHAR_DATA_WIDTH_INDEX]; diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 67ec9dda..81754397 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -8,6 +8,7 @@ import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITermi import { Buffer, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../Buffer'; import * as Browser from '../shared/utils/Browser'; import { ITheme, IDisposable, IMarker } from 'xterm'; +import { TerminalLine } from '../TerminalLine'; export class MockTerminal implements ITerminal { markers: IMarker[]; @@ -145,8 +146,8 @@ export class MockTerminal implements ITerminal { refresh(start: number, end: number): void { throw new Error('Method not implemented.'); } - blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData { - const line: LineData = []; + blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): TerminalLine { + const line = new TerminalLine(); cols = cols || this.cols; for (let i = 0; i < cols; i++) { line.push([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); @@ -228,7 +229,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { eraseLeft(x: number, y: number): void { throw new Error('Method not implemented.'); } - blankLine(cur?: boolean, isWrapped?: boolean): [number, string, number, number][] { + blankLine(cur?: boolean, isWrapped?: boolean): TerminalLine { throw new Error('Method not implemented.'); } prevStop(x?: number): number { @@ -295,7 +296,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { export class MockBuffer implements IBuffer { isCursorInViewport: boolean; - lines: ICircularList<[number, string, number, number][]>; + lines: ICircularList; ydisp: number; ybase: number; hasScrollback: boolean; @@ -318,7 +319,7 @@ export class MockBuffer implements IBuffer { prevStop(x?: number): number { throw new Error('Method not implemented.'); } - setLines(lines: ICircularList<[number, string, number, number][]>): void { + setLines(lines: ICircularList): void { this.lines = lines; } }