From 59fc4af9a0f1fa7c69cacf2b60f25e57fe0dc557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 30 Aug 2018 17:52:45 +0200 Subject: [PATCH] IBufferLine interface --- src/Buffer.test.ts | 20 +++++------ src/Buffer.ts | 12 +++---- src/InputHandler.test.ts | 12 +++---- src/InputHandler.ts | 10 +++--- src/Linkifier.test.ts | 10 +++--- src/Linkifier.ts | 5 ++- src/SelectionManager.test.ts | 14 ++++---- src/SelectionManager.ts | 5 ++- src/Terminal.test.ts | 8 ++--- src/Terminal.ts | 8 ++--- src/TerminalLine.test.ts | 35 +++++++++++-------- src/TerminalLine.ts | 23 ++++-------- src/Types.ts | 19 ++++++++-- src/handlers/AltClickHandler.ts | 5 ++- src/renderer/CharacterJoinerRegistry.test.ts | 11 +++--- src/renderer/CharacterJoinerRegistry.ts | 7 ++-- .../dom/DomRendererRowFactory.test.ts | 9 ++--- src/renderer/dom/DomRendererRowFactory.ts | 4 +-- src/utils/TestUtils.test.ts | 7 ++-- 19 files changed, 116 insertions(+), 108 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index e2db9636..ded814de 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -8,7 +8,7 @@ import { ITerminal } from './Types'; import { Buffer, DEFAULT_ATTR } from './Buffer'; import { CircularList } from './common/CircularList'; import { MockTerminal } from './utils/TestUtils.test'; -import { TerminalLine } from './TerminalLine'; +import { BufferLine } from './TerminalLine'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -37,7 +37,7 @@ describe('Buffer', () => { describe('fillViewportRows', () => { it('should fill the buffer with blank lines based on the size of the viewport', () => { - const blankLineChar = TerminalLine.blankLine(terminal.cols, DEFAULT_ATTR).get(0); + const blankLineChar = BufferLine.blankLine(terminal.cols, DEFAULT_ATTR).get(0); buffer.fillViewportRows(); assert.equal(buffer.lines.length, INIT_ROWS); for (let y = 0; y < INIT_ROWS; y++) { @@ -180,7 +180,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); // Create 10 extra blank lines for (let i = 0; i < 10; i++) { - buffer.lines.push(TerminalLine.blankLine(terminal.cols, DEFAULT_ATTR)); + buffer.lines.push(BufferLine.blankLine(terminal.cols, DEFAULT_ATTR)); } // Set cursor to the bottom of the buffer buffer.y = INIT_ROWS - 1; @@ -200,7 +200,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); // Create 10 extra blank lines for (let i = 0; i < 10; i++) { - buffer.lines.push(TerminalLine.blankLine(terminal.cols, DEFAULT_ATTR)); + buffer.lines.push(BufferLine.blankLine(terminal.cols, DEFAULT_ATTR)); } // Set cursor to the bottom of the buffer buffer.y = INIT_ROWS - 1; @@ -273,7 +273,7 @@ describe('Buffer', () => { describe ('translateBufferLineToString', () => { it('should handle selecting a section of ascii text', () => { - const line = new TerminalLine(); + const line = new BufferLine(); line.push([ null, 'a', 1, 'a'.charCodeAt(0)]); line.push([ null, 'b', 1, 'b'.charCodeAt(0)]); line.push([ null, 'c', 1, 'c'.charCodeAt(0)]); @@ -285,7 +285,7 @@ describe('Buffer', () => { }); it('should handle a cut-off double width character by including it', () => { - const line = new TerminalLine(); + const line = new BufferLine(); line.push([ null, 'θͺž', 2, 35486 ]); line.push([ null, '', 0, null]); line.push([ null, 'a', 1, 'a'.charCodeAt(0)]); @@ -296,7 +296,7 @@ describe('Buffer', () => { }); it('should handle a zero width character in the middle of the string by not including it', () => { - const line = new TerminalLine(); + const line = new BufferLine(); line.push([ null, 'θͺž', 2, 'θͺž'.charCodeAt(0) ]); line.push([ null, '', 0, null]); line.push([ null, 'a', 1, 'a'.charCodeAt(0)]); @@ -313,7 +313,7 @@ describe('Buffer', () => { }); it('should handle single width emojis', () => { - const line = new TerminalLine(); + const line = new BufferLine(); line.push([ null, '😁', 1, '😁'.charCodeAt(0) ]); line.push([ null, 'a', 1, 'a'.charCodeAt(0)]); buffer.lines.set(0, line); @@ -326,7 +326,7 @@ describe('Buffer', () => { }); it('should handle double width emojis', () => { - const line = new TerminalLine(); + const line = new BufferLine(); line.push([ null, '😁', 2, '😁'.charCodeAt(0) ]); line.push([ null, '', 0, null]); buffer.lines.set(0, line); @@ -337,7 +337,7 @@ describe('Buffer', () => { const str2 = buffer.translateBufferLineToString(0, true, 0, 2); assert.equal(str2, '😁'); - const line2 = new TerminalLine(); + const line2 = new BufferLine(); line2.push([ null, '😁', 2, '😁'.charCodeAt(0) ]); line2.push([ null, '', 0, null]); line2.push([ null, 'a', 1, 'a'.charCodeAt(0)]); diff --git a/src/Buffer.ts b/src/Buffer.ts index b71537dc..40cbf29e 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,10 +4,10 @@ */ import { CircularList } from './common/CircularList'; -import { CharData, ITerminal, IBuffer } from './Types'; +import { CharData, ITerminal, IBuffer, IBufferLine } from './Types'; import { EventEmitter } from './EventEmitter'; import { IMarker } from 'xterm'; -import { TerminalLine } from './TerminalLine'; +import { BufferLine } from './TerminalLine'; export const DEFAULT_ATTR = (0 << 18) | (257 << 9) | (256 << 0); export const CHAR_DATA_ATTR_INDEX = 0; @@ -28,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; @@ -85,7 +85,7 @@ export class Buffer implements IBuffer { if (this.lines.length === 0) { let i = this._terminal.rows; while (i--) { - this.lines.push(TerminalLine.blankLine(this._terminal.cols, DEFAULT_ATTR)); + this.lines.push(BufferLine.blankLine(this._terminal.cols, DEFAULT_ATTR)); } } } @@ -98,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(); @@ -147,7 +147,7 @@ export class Buffer implements IBuffer { } else { // Add a blank line if there is no buffer left at the top to scroll to, or if there // are blank lines after the cursor - this.lines.push(TerminalLine.blankLine(newCols, DEFAULT_ATTR)); + this.lines.push(BufferLine.blankLine(newCols, DEFAULT_ATTR)); } } } diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 22a80c18..4774c105 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -7,8 +7,8 @@ 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 { TerminalLine } from './TerminalLine'; import { Terminal } from './Terminal'; +import { IBufferLine } from './Types'; describe('InputHandler', () => { describe('save and restore cursor', () => { @@ -90,7 +90,7 @@ describe('InputHandler', () => { describe('regression tests', function(): void { type CharData = [number, string, number, number]; - function lineContent(line: TerminalLine): string { + 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; @@ -128,8 +128,8 @@ describe('InputHandler', () => { inputHandler.parse('1234567890'); inputHandler.parse(Array(term.cols - 9).join('a')); inputHandler.parse('1234567890'); - const line1: TerminalLine = term.buffer.lines.get(0); // line for old variant - const line2: TerminalLine = term.buffer.lines.get(1); // line for new variant + 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'); @@ -205,8 +205,8 @@ describe('InputHandler', () => { inputHandler.parse('1234567890'); inputHandler.parse(Array(term.cols - 9).join('a')); inputHandler.parse('1234567890'); - const line1: TerminalLine = term.buffer.lines.get(0); // line for old variant - const line2: TerminalLine = term.buffer.lines.get(1); // line for new variant + 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'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index b6883a0f..ca743575 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -13,7 +13,7 @@ import { wcwidth } from './CharWidth'; import { EscapeSequenceParser } from './EscapeSequenceParser'; import { ICharset } from './core/Types'; import { Disposable } from './common/Lifecycle'; -import { TerminalLine } from './TerminalLine'; +import { BufferLine } from './TerminalLine'; /** * Map collect to glevel. Used in `selectCharset`. @@ -832,7 +832,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test: echo -e '\e[44m\e[1L\e[0m' // blankLine(true) - xterm/linux behavior buffer.lines.splice(scrollBottomAbsolute - 1, 1); - buffer.lines.splice(row, 0, TerminalLine.blankLine(this._terminal.cols, this._terminal.eraseAttr())); + buffer.lines.splice(row, 0, BufferLine.blankLine(this._terminal.cols, this._terminal.eraseAttr())); } // this.maxRange(); @@ -862,7 +862,7 @@ export class InputHandler extends Disposable implements IInputHandler { // test: echo -e '\e[44m\e[1M\e[0m' // blankLine(true) - xterm/linux behavior buffer.lines.splice(row, 1); - buffer.lines.splice(j, 0, TerminalLine.blankLine(this._terminal.cols, this._terminal.eraseAttr())); + buffer.lines.splice(j, 0, BufferLine.blankLine(this._terminal.cols, this._terminal.eraseAttr())); } // this.maxRange(); @@ -894,7 +894,7 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1); - buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, TerminalLine.blankLine(this._terminal.cols, DEFAULT_ATTR)); + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, BufferLine.blankLine(this._terminal.cols, DEFAULT_ATTR)); } // this.maxRange(); this._terminal.updateRange(buffer.scrollTop); @@ -913,7 +913,7 @@ export class InputHandler extends Disposable implements IInputHandler { while (param--) { buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1); - buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, TerminalLine.blankLine(this._terminal.cols, DEFAULT_ATTR)); + buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, BufferLine.blankLine(this._terminal.cols, DEFAULT_ATTR)); } // this.maxRange(); this._terminal.updateRange(buffer.scrollTop); diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index 0a066284..0f84d41c 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -5,11 +5,11 @@ import { assert } from 'chai'; import { IMouseZoneManager, IMouseZone } from './ui/Types'; -import { ILinkMatcher, ITerminal } from './Types'; +import { ILinkMatcher, ITerminal, IBufferLine } from './Types'; import { Linkifier } from './Linkifier'; import { MockBuffer, MockTerminal } from './utils/TestUtils.test'; import { CircularList } from './common/CircularList'; -import { TerminalLine } from './TerminalLine'; +import { BufferLine } from './TerminalLine'; class TestLinkifier extends Linkifier { constructor(terminal: ITerminal) { @@ -43,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): TerminalLine { - const result = new TerminalLine(); + function stringToRow(text: string): IBufferLine { + const result = new BufferLine(); 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 859df661..8615daf8 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -4,10 +4,9 @@ */ import { IMouseZoneManager } from './ui/Types'; -import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal } from './Types'; +import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferLine } from './Types'; import { MouseZone } from './ui/MouseZoneManager'; import { EventEmitter } from './EventEmitter'; -import { TerminalLine } from './TerminalLine'; import { CHAR_DATA_ATTR_INDEX } from './Buffer'; /** @@ -171,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: TerminalLine; + let line: IBufferLine; do { rowIndex--; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index c7374ad5..880a42d3 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -8,9 +8,9 @@ import { CharMeasure } from './ui/CharMeasure'; import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { ITerminal, IBuffer } from './Types'; +import { ITerminal, IBuffer, IBufferLine } from './Types'; import { MockTerminal } from './utils/TestUtils.test'; -import { TerminalLine } from './TerminalLine'; +import { BufferLine } from './TerminalLine'; class TestMockTerminal extends MockTerminal { emit(event: string, data: any): void {} @@ -53,16 +53,16 @@ describe('SelectionManager', () => { selectionManager = new TestSelectionManager(terminal, null); }); - function stringToRow(text: string): TerminalLine { - const result = new TerminalLine(); + function stringToRow(text: string): IBufferLine { + const result = new BufferLine(); for (let i = 0; i < text.length; i++) { result.push([0, text.charAt(i), 1, text.charCodeAt(i)]); } return result; } - function stringArrayToRow(chars: string[]): TerminalLine { - const line = new TerminalLine(); + function stringArrayToRow(chars: string[]): IBufferLine { + const line = new BufferLine(); chars.map(c => line.push([0, c, 1, c.charCodeAt(0)])); return line; } @@ -100,7 +100,7 @@ describe('SelectionManager', () => { }); it('should expand selection for wide characters', () => { // Wide characters use a special format - const line = new TerminalLine(); + const line = new BufferLine(); const data: [number, string, number, number][] = [ [null, 'δΈ­', 2, 'δΈ­'.charCodeAt(0)], [null, '', 0, null], diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index fcd45f2a..2dd92c5b 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal, ISelectionManager, IBuffer, CharData, XtermListener } from './Types'; +import { ITerminal, ISelectionManager, IBuffer, CharData, XtermListener, IBufferLine } from './Types'; import { MouseHelper } from './utils/MouseHelper'; import * as Browser from './shared/utils/Browser'; import { CharMeasure } from './ui/CharMeasure'; @@ -11,7 +11,6 @@ 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 @@ -662,7 +661,7 @@ 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: TerminalLine, coords: [number, number]): number { + private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number { let charIndex = coords[0]; for (let i = 0; coords[0] >= i; i++) { const char = bufferLine.get(i); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 322e6d6c..4f765151 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -7,7 +7,7 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR } from './Buffer'; -import { TerminalLine } from './TerminalLine'; +import { BufferLine } from './TerminalLine'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -142,7 +142,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.length, term.rows); assert.deepEqual(term.buffer.lines.get(0), promptLine); for (let i = 1; i < term.rows; i++) { - assert.deepEqual(term.buffer.lines.get(i), TerminalLine.blankLine(term.cols, DEFAULT_ATTR)); + assert.deepEqual(term.buffer.lines.get(i), BufferLine.blankLine(term.cols, DEFAULT_ATTR)); } }); it('should clear a buffer larger than rows', () => { @@ -159,7 +159,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.length, term.rows); assert.deepEqual(term.buffer.lines.get(0), promptLine); for (let i = 1; i < term.rows; i++) { - assert.deepEqual(term.buffer.lines.get(i), TerminalLine.blankLine(term.cols, DEFAULT_ATTR)); + assert.deepEqual(term.buffer.lines.get(i), BufferLine.blankLine(term.cols, DEFAULT_ATTR)); } }); it('should not break the prompt when cleared twice', () => { @@ -172,7 +172,7 @@ describe('term.js addons', () => { assert.equal(term.buffer.lines.length, term.rows); assert.deepEqual(term.buffer.lines.get(0), promptLine); for (let i = 1; i < term.rows; i++) { - assert.deepEqual(term.buffer.lines.get(i), TerminalLine.blankLine(term.cols, DEFAULT_ATTR)); + assert.deepEqual(term.buffer.lines.get(i), BufferLine.blankLine(term.cols, DEFAULT_ATTR)); } }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index fadff43d..56d98afd 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,7 +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'; +import { BufferLine } from './TerminalLine'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -1171,7 +1171,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(isWrapped?: boolean): void { - const newLine = TerminalLine.blankLine(this.cols, DEFAULT_ATTR, isWrapped); + const newLine = BufferLine.blankLine(this.cols, DEFAULT_ATTR, isWrapped); const topRow = this.buffer.ybase + this.buffer.scrollTop; const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; @@ -1753,7 +1753,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.buffer.ybase = 0; this.buffer.y = 0; for (let i = 1; i < this.rows; i++) { - this.buffer.lines.push(TerminalLine.blankLine(this.cols, DEFAULT_ATTR)); + this.buffer.lines.push(BufferLine.blankLine(this.cols, DEFAULT_ATTR)); } this.refresh(0, this.rows - 1); this.emit('scroll', this.buffer.ydisp); @@ -1854,7 +1854,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // blankLine(true) is xterm/linux behavior const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop; this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1); - this.buffer.lines.set(this.buffer.y + this.buffer.ybase, TerminalLine.blankLine(this.cols, this.eraseAttr())); + this.buffer.lines.set(this.buffer.y + this.buffer.ybase, BufferLine.blankLine(this.cols, this.eraseAttr())); this.updateRange(this.buffer.scrollTop); this.updateRange(this.buffer.scrollBottom); } else { diff --git a/src/TerminalLine.test.ts b/src/TerminalLine.test.ts index 72cea26d..19a4d4c7 100644 --- a/src/TerminalLine.test.ts +++ b/src/TerminalLine.test.ts @@ -3,31 +3,38 @@ * @license MIT */ import * as chai from 'chai'; -import { TerminalLine } from './TerminalLine'; -import { CharData } from './Types'; +import { BufferLine } from './TerminalLine'; +import { CharData, IBufferLine } from './Types'; import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer'; -describe('TerminalLine', function(): void { + +class TestBufferLine extends BufferLine { + public toArray(): CharData[] { + return this._data; + } +} + +describe('BufferLine', function(): void { it('ctor', function(): void { - let line = new TerminalLine(); + let line: IBufferLine = new TestBufferLine(); chai.expect(line.length).equals(0); chai.expect(line.pop()).equals(undefined); chai.expect(line.isWrapped).equals(false); - line = new TerminalLine(10); + line = new TestBufferLine(10); chai.expect(line.length).equals(10); - chai.expect(line.pop()).eql(TerminalLine.defaultCell); + chai.expect(line.pop()).eql(TestBufferLine.defaultCell); chai.expect(line.isWrapped).equals(false); - line = new TerminalLine(10, null, true); + line = new TestBufferLine(10, null, true); chai.expect(line.length).equals(10); - chai.expect(line.pop()).eql(TerminalLine.defaultCell); + chai.expect(line.pop()).eql(TestBufferLine.defaultCell); chai.expect(line.isWrapped).equals(true); - line = new TerminalLine(10, [123, 'a', 456, 789], true); + line = new TestBufferLine(10, [123, 'a', 456, 789], true); chai.expect(line.length).equals(10); chai.expect(line.pop()).eql([123, 'a', 456, 789]); chai.expect(line.isWrapped).equals(true); }); it('splice', function(): void { - const line = new TerminalLine(); + const line = new TestBufferLine(); const data: CharData[] = [ [1, 'a', 0, 0], [2, 'b', 0, 0], @@ -41,7 +48,7 @@ describe('TerminalLine', function(): void { chai.expect(line.toArray()).eql(data); }); it('TerminalLine.blankLine', function(): void { - const line = TerminalLine.blankLine(5, 123); + const line = TestBufferLine.blankLine(5, 123); chai.expect(line.length).equals(5); chai.expect(line.isWrapped).equals(false); const ch = line.get(0); @@ -51,7 +58,7 @@ describe('TerminalLine', function(): void { chai.expect(ch[CHAR_DATA_CODE_INDEX]).equals(NULL_CELL_CODE); }); it('insertCells', function(): void { - const line = new TerminalLine(); + const line = new TestBufferLine(); const data: CharData[] = [ [1, 'a', 0, 0], [2, 'b', 0, 0], @@ -62,7 +69,7 @@ describe('TerminalLine', function(): void { chai.expect(line.toArray()).eql([[1, 'a', 0, 0], [4, 'd', 0, 0], [4, 'd', 0, 0]]); }); it('deleteCells', function(): void { - const line = new TerminalLine(); + const line = new TestBufferLine(); const data: CharData[] = [ [1, 'a', 0, 0], [2, 'b', 0, 0], @@ -75,7 +82,7 @@ describe('TerminalLine', function(): void { 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]]); }); it('replaceCells', function(): void { - const line = new TerminalLine(); + const line = new TestBufferLine(); const data: CharData[] = [ [1, 'a', 0, 0], [2, 'b', 0, 0], diff --git a/src/TerminalLine.ts b/src/TerminalLine.ts index 77f3962e..51e0148b 100644 --- a/src/TerminalLine.ts +++ b/src/TerminalLine.ts @@ -2,7 +2,7 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ -import { CharData } from './Types'; +import { CharData, IBufferLine } from './Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; /** @@ -20,13 +20,13 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; * - remove push/pop/splice * - implement typed array alternative once string is removed from CharData */ -export class TerminalLine { +export class BufferLine implements IBufferLine { static defaultCell: CharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - static blankLine(cols: number, attr: number, isWrapped?: boolean): TerminalLine { + static blankLine(cols: number, attr: number, isWrapped?: boolean): BufferLine { const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - return new TerminalLine(cols, ch, isWrapped); + return new BufferLine(cols, ch, isWrapped); } - private _data: CharData[]; + protected _data: CharData[]; public isWrapped = false; public length: number; @@ -35,7 +35,7 @@ export class TerminalLine { this.length = this._data.length; if (cols) { if (!ch) { - ch = TerminalLine.defaultCell; + ch = BufferLine.defaultCell; } for (let i = 0; i < cols; i++) { this.push(ch); // Note: the ctor ch is not cloned @@ -57,7 +57,6 @@ export class TerminalLine { // to be removed for typed array public pop(): CharData | undefined { - // TODO: unref here, change CharData to [typeof Attributes, ...] const data = this._data.pop(); this.length = this._data.length; return data; @@ -67,25 +66,15 @@ export class TerminalLine { public push(data: CharData): void { this._data.push(data); this.length = this._data.length; - // TODO: ref here } // to be removed for typed array public 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 */ - public release(): void { - // TODO: unref here - } - public toArray(): CharData[] { - return this._data; - } - /** insert n cells ch at pos, right cells are lost (stable length) */ public insertCells(pos: number, n: number, ch: CharData): void { while (n--) { diff --git a/src/Types.ts b/src/Types.ts index ecec9a95..c567fa25 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -7,7 +7,6 @@ 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; @@ -272,7 +271,7 @@ export interface ITerminalOptions extends IPublicTerminalOptions { } export interface IBuffer { - readonly lines: ICircularList; + readonly lines: ICircularList; ydisp: number; ybase: number; y: number; @@ -511,3 +510,19 @@ export interface IEscapeSequenceParser extends IDisposable { setErrorHandler(callback: (state: IParsingState) => IParsingState): void; clearErrorHandler(): void; } + +/** + * Interface for a line in the terminal buffer. + */ +export interface IBufferLine { + length: number; + isWrapped: boolean; + get(index: number): CharData; + set(index: number, value: CharData): void; + pop(): CharData | undefined; + push(data: CharData): void; + splice(start: number, deleteCount: number, ...items: CharData[]): CharData[]; + insertCells(pos: number, n: number, ch: CharData): void; + deleteCells(pos: number, n: number, fill: CharData): void; + replaceCells(start: number, end: number, fill: CharData): void; +} diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 2932dd5c..aa942f3e 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -3,9 +3,8 @@ * @license MIT */ -import { ITerminal, ICircularList } from '../Types'; +import { ITerminal, ICircularList, IBufferLine } from '../Types'; import { C0 } from '../common/data/EscapeSequences'; -import { TerminalLine } from '../TerminalLine'; const enum Direction { UP = 'A', @@ -19,7 +18,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 d7cd1ce4..b05b2f8d 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -5,7 +5,8 @@ import { CircularList } from '../common/CircularList'; import { ICharacterJoinerRegistry } from './Types'; import { CharacterJoinerRegistry } from './CharacterJoinerRegistry'; -import { TerminalLine } from '../TerminalLine'; +import { BufferLine } from '../TerminalLine'; +import { IBufferLine } from '../Types'; describe('CharacterJoinerRegistry', () => { let registry: ICharacterJoinerRegistry; @@ -14,13 +15,13 @@ describe('CharacterJoinerRegistry', () => { const terminal = new MockTerminal(); terminal.cols = 16; terminal.buffer = new MockBuffer(); - const lines = new CircularList(7); + 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(4, new BufferLine()); lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]])); const line6 = lineData([['wi']]); line6.push([0, 'οΏ₯', 2, 'οΏ₯'.charCodeAt(0)]); @@ -262,8 +263,8 @@ describe('CharacterJoinerRegistry', () => { type IPartialLineData = ([string] | [string, number]); -function lineData(data: IPartialLineData[]): TerminalLine { - const tline = new TerminalLine(); +function lineData(data: IPartialLineData[]): IBufferLine { + const tline = new BufferLine(); for (let i = 0; i < data.length; ++i) { const line = data[i][0]; const attr = (data[i][1] || 0); diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 2b50f5ac..dc9e95dd 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -1,7 +1,6 @@ import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer'; -import { ITerminal } from '../Types'; +import { ITerminal, IBufferLine } from '../Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; -import { TerminalLine } from '../TerminalLine'; export class CharacterJoinerRegistry implements ICharacterJoinerRegistry { @@ -116,7 +115,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: TerminalLine, startCol: number): [number, number][] { + private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: IBufferLine, 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 @@ -141,7 +140,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: TerminalLine, startCol: number): void { + private _stringRangesToCellRanges(ranges: [number, number][], line: IBufferLine, startCol: number): void { let currentRangeIndex = 0; let currentRangeStarted = false; let currentStringIndex = 0; diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index 9503a46c..ebce9028 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -8,12 +8,13 @@ import { assert } from 'chai'; import { DomRendererRowFactory } from './DomRendererRowFactory'; import { DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from '../../Buffer'; import { FLAGS } from '../Types'; -import { TerminalLine } from '../../TerminalLine'; +import { BufferLine } from '../../TerminalLine'; +import { IBufferLine } from '../../Types'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; let rowFactory: DomRendererRowFactory; - let lineData: TerminalLine; + let lineData: IBufferLine; beforeEach(() => { dom = new jsdom.JSDOM(''); @@ -146,8 +147,8 @@ describe('DomRendererRowFactory', () => { return element.innerHTML; } - function createEmptyLineData(cols: number): TerminalLine { - const lineData = new TerminalLine(); + function createEmptyLineData(cols: number): IBufferLine { + const lineData = new BufferLine(); 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 1c259549..351055ef 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -5,7 +5,7 @@ import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../../Buffer'; import { FLAGS } from '../Types'; -import { TerminalLine } from '../../TerminalLine'; +import { IBufferLine } from '../../Types'; export const BOLD_CLASS = 'xterm-bold'; export const ITALIC_CLASS = 'xterm-italic'; @@ -17,7 +17,7 @@ export class DomRendererRowFactory { ) { } - public createRow(lineData: TerminalLine, isCursorRow: boolean, cursorX: number, cellWidth: number, cols: number): DocumentFragment { + public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorX: number, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); let colCount = 0; diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index a279b48b..b9bb0348 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -4,11 +4,10 @@ */ import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener, CharacterJoinerHandler } from '../Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener, CharacterJoinerHandler, IBufferLine } from '../Types'; import { Buffer } 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[]; @@ -285,7 +284,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { export class MockBuffer implements IBuffer { isCursorInViewport: boolean; - lines: ICircularList; + lines: ICircularList; ydisp: number; ybase: number; hasScrollback: boolean; @@ -308,7 +307,7 @@ export class MockBuffer implements IBuffer { prevStop(x?: number): number { throw new Error('Method not implemented.'); } - setLines(lines: ICircularList): void { + setLines(lines: ICircularList): void { this.lines = lines; } }