From e2cea7578bb02616f1864b880e00b92209e5919e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 16:24:36 -0700 Subject: [PATCH 01/13] Adopt options service in buffer --- src/Buffer.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 6b89884e..dee112d9 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -9,6 +9,7 @@ import { IBufferLine, ICellData, IAttributeData } from 'common/Types'; import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from 'common/buffer/BufferReflow'; import { Marker } from 'common/buffer/Marker'; +import { IOptionsService } from 'common/services/Services'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 @@ -37,15 +38,10 @@ export class Buffer implements IBuffer { private _cols: number; private _rows: number; - /** - * Create a new Buffer. - * @param _terminal The terminal the Buffer will belong to. - * @param _hasScrollback Whether the buffer should respect the scrollback of - * the terminal. - */ constructor( private _terminal: ITerminal, - private _hasScrollback: boolean + private _hasScrollback: boolean, + private _optionsService: IOptionsService ) { this._cols = this._terminal.cols; this._rows = this._terminal.rows; @@ -98,7 +94,7 @@ export class Buffer implements IBuffer { return rows; } - const correctBufferLength = rows + this._terminal.options.scrollback; + const correctBufferLength = rows + this._optionsService.options.scrollback; return correctBufferLength > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : correctBufferLength; } @@ -237,7 +233,7 @@ export class Buffer implements IBuffer { } private get _isReflowEnabled(): boolean { - return this._hasScrollback && !this._terminal.options.windowsMode; + return this._hasScrollback && !this._optionsService.options.windowsMode; } private _reflow(newCols: number, newRows: number): void { @@ -533,7 +529,7 @@ export class Buffer implements IBuffer { i = 0; } - for (; i < this._cols; i += this._terminal.options.tabStopWidth) { + for (; i < this._cols; i += this._optionsService.options.tabStopWidth) { this.tabs[i] = true; } } From cd61f956129c128919ede7d9824483662bcc8f4d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 16:46:03 -0700 Subject: [PATCH 02/13] Adopt options service in buffer --- src/Buffer.test.ts | 32 +++++++++++++-------------- src/BufferSet.test.ts | 5 ++--- src/BufferSet.ts | 10 ++++++--- src/SelectionManager.test.ts | 5 ++--- src/SelectionModel.test.ts | 5 ++--- src/Terminal.test.ts | 4 ++-- src/Terminal.ts | 2 +- src/TestUtils.test.ts | 24 ++++++++++++++++---- src/common/services/OptionsService.ts | 2 +- tslint.json | 2 +- 10 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index 101912bb..d0d09805 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -7,28 +7,30 @@ import { assert, expect } from 'chai'; import { ITerminal } from './Types'; import { Buffer } from './Buffer'; import { CircularList } from 'common/CircularList'; -import { MockTerminal, TestTerminal } from './TestUtils.test'; +import { MockTerminal, TestTerminal, MockOptionsService } from './TestUtils.test'; import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; +const INIT_SCROLLBACK = 1000; describe('Buffer', () => { let terminal: ITerminal; + let optionsService: MockOptionsService; let buffer: Buffer; beforeEach(() => { terminal = new MockTerminal(); (terminal as any).cols = INIT_COLS; (terminal as any).rows = INIT_ROWS; - terminal.options.scrollback = 1000; - buffer = new Buffer(terminal, true); + optionsService = new MockOptionsService({ scrollback: INIT_SCROLLBACK }); + buffer = new Buffer(terminal, true, optionsService); }); describe('constructor', () => { it('should create a CircularList with max length equal to rows + scrollback, for its lines', () => { assert.instanceOf(buffer.lines, CircularList); - assert.equal(buffer.lines.maxLength, terminal.rows + terminal.options.scrollback); + assert.equal(buffer.lines.maxLength, terminal.rows + INIT_SCROLLBACK); }); it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => { assert.equal(buffer.scrollBottom, terminal.rows - 1); @@ -150,8 +152,7 @@ describe('Buffer', () => { describe('no scrollback', () => { it('should trim from the top of the buffer when the cursor reaches the bottom', () => { - terminal.options.scrollback = 0; - buffer = new Buffer(terminal, true); + buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); assert.equal(buffer.lines.maxLength, INIT_ROWS); buffer.y = INIT_ROWS - 1; buffer.fillViewportRows(); @@ -295,7 +296,7 @@ describe('Buffer', () => { }); it('should discard parts of wrapped lines that go out of the scrollback', () => { buffer.fillViewportRows(); - terminal.options.scrollback = 1; + optionsService.options.scrollback = 1; buffer.resize(10, 5); const lastLine = buffer.lines.get(3); for (let i = 0; i < 10; i++) { @@ -462,7 +463,7 @@ describe('Buffer', () => { }); it('should dispose markers whose rows are trimmed during a reflow', () => { buffer.fillViewportRows(); - terminal.options.scrollback = 1; + optionsService.options.scrollback = 1; buffer.resize(10, 11); for (let i = 0; i < 10; i++) { const code = 'a'.charCodeAt(0) + i; @@ -788,7 +789,7 @@ describe('Buffer', () => { // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported describe('ybase !== 0', () => { beforeEach(() => { - terminal.options.scrollback = 10; + optionsService.options.scrollback = 10; // Add 10 empty rows to start for (let i = 0; i < 10; i++) { buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); @@ -986,7 +987,7 @@ describe('Buffer', () => { // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported describe('ybase !== 0', () => { beforeEach(() => { - terminal.options.scrollback = 10; + optionsService.options.scrollback = 10; // Add 10 empty rows to start for (let i = 0; i < 10; i++) { buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); @@ -1053,9 +1054,8 @@ describe('Buffer', () => { describe('buffer marked to have no scrollback', () => { it('should always have a scrollback of 0', () => { - assert.equal(terminal.options.scrollback, 1000); // Test size on initialization - buffer = new Buffer(terminal, false); + buffer = new Buffer(terminal, false, new MockOptionsService({ scrollback: 1000 })); buffer.fillViewportRows(); assert.equal(buffer.lines.maxLength, INIT_ROWS); // Test size on buffer increase @@ -1069,8 +1069,7 @@ describe('Buffer', () => { describe('addMarker', () => { it('should adjust a marker line when the buffer is trimmed', () => { - terminal.options.scrollback = 0; - buffer = new Buffer(terminal, true); + buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); @@ -1078,8 +1077,7 @@ describe('Buffer', () => { assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { - terminal.options.scrollback = 0; - buffer = new Buffer(terminal, true); + buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); buffer.fillViewportRows(); assert.equal(buffer.markers.length, 0); const marker = buffer.addMarker(0); @@ -1366,7 +1364,7 @@ describe('Buffer', () => { const input = '\thttps://google.de'; terminal.writeSync(input); const s = terminal.buffer.iterator(true).next().content; - assert.equal(s, Array(terminal.options.tabStopWidth + 1).join(' ') + 'https://google.de'); + assert.equal(s, Array(optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de'); }); }); describe('BufferStringIterator', function(): void { diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index cdc220b3..fb70e954 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { BufferSet } from './BufferSet'; import { Buffer } from './Buffer'; -import { MockTerminal } from './TestUtils.test'; +import { MockTerminal, MockOptionsService } from './TestUtils.test'; describe('BufferSet', () => { let terminal: ITerminal; @@ -17,8 +17,7 @@ describe('BufferSet', () => { terminal = new MockTerminal(); (terminal as any).cols = 80; (terminal as any).rows = 24; - terminal.options.scrollback = 1000; - bufferSet = new BufferSet(terminal); + bufferSet = new BufferSet(terminal, new MockOptionsService({ scrollback: 1000 })); }); describe('constructor', () => { diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 1d4ae2c6..f5df2073 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -7,6 +7,7 @@ import { ITerminal, IBufferSet, IBuffer } from './Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from './Buffer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { IOptionsService } from 'common/services/Services'; /** * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and @@ -25,13 +26,16 @@ export class BufferSet implements IBufferSet { * Create a new BufferSet for the given terminal. * @param _terminal - The terminal the BufferSet will belong to */ - constructor(private _terminal: ITerminal) { - this._normal = new Buffer(this._terminal, true); + constructor( + private _terminal: ITerminal, + readonly optionsService: IOptionsService + ) { + this._normal = new Buffer(this._terminal, true, optionsService); this._normal.fillViewportRows(); // The alt buffer should never have scrollback. // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer - this._alt = new Buffer(this._terminal, false); + this._alt = new Buffer(this._terminal, false, optionsService); this._activeBuffer = this._normal; this.setupTabStops(); diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 90d70d79..970a8e9b 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -9,7 +9,7 @@ import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer } from './Types'; import { IBufferLine } from 'common/Types'; -import { MockTerminal, MockCharSizeService } from './TestUtils.test'; +import { MockTerminal, MockCharSizeService, MockOptionsService } from './TestUtils.test'; import { BufferLine, CellData } from 'common/buffer/BufferLine'; class TestMockTerminal extends MockTerminal { @@ -46,8 +46,7 @@ describe('SelectionManager', () => { terminal = new TestMockTerminal(); (terminal as any).cols = 80; (terminal as any).rows = 2; - terminal.options.scrollback = 100; - terminal.buffers = new BufferSet(terminal); + terminal.buffers = new BufferSet(terminal, new MockOptionsService({ scrollback: 100 })); terminal.buffer = terminal.buffers.active; buffer = terminal.buffer; selectionManager = new TestSelectionManager(terminal); diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index c2b261a9..05444aac 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { MockTerminal } from './TestUtils.test'; +import { MockTerminal, MockOptionsService } from './TestUtils.test'; class TestSelectionModel extends SelectionModel { constructor( @@ -25,8 +25,7 @@ describe('SelectionManager', () => { terminal = new MockTerminal(); (terminal as any).cols = 80; (terminal as any).rows = 2; - terminal.options.scrollback = 10; - terminal.buffers = new BufferSet(terminal); + terminal.buffers = new BufferSet(terminal, new MockOptionsService({ scrollback: 10 })); terminal.buffer = terminal.buffers.active; model = new TestSelectionModel(terminal); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index db474269..a3cff41f 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -379,10 +379,10 @@ describe('Terminal', () => { describe('scrollLines', () => { let startYDisp: number; beforeEach(() => { - for (let i = 0; i < term.rows * 2; i++) { + for (let i = 0; i < INIT_ROWS * 2; i++) { term.writeln('test'); } - startYDisp = term.rows + 1; + startYDisp = INIT_ROWS + 1; }); it('should scroll a single line', () => { assert.equal(term.buffer.ydisp, startYDisp); diff --git a/src/Terminal.ts b/src/Terminal.ts index a5348568..29a0478f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -313,7 +313,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.soundManager = this.soundManager || new SoundManager(this); // Create the terminal's buffers and set the current buffer - this.buffers = new BufferSet(this); + this.buffers = new BufferSet(this, this.optionsService); if (this.selectionManager) { this.selectionManager.clearSelection(); this.selectionManager.initBuffersListeners(); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index e8fdeeba..5ac908e4 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions } from './renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions as IInternalTerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; import { Buffer } from './Buffer'; import * as Browser from 'common/Platform'; @@ -12,8 +12,10 @@ import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; import { IColorManager, IColorSet } from 'browser/Types'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IPartialTerminalOptions, ITerminalOptions } from 'common/services/Services'; import { ICharSizeService } from 'browser/services/Services'; +import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; +import { clone } from 'common/Clone'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -122,7 +124,7 @@ export class MockTerminal implements ITerminal { renderer: IRenderer; linkifier: ILinkifier; isFocused: boolean; - options: ITerminalOptions = {}; + options: IInternalTerminalOptions = {}; element: HTMLElement; screenElement: HTMLElement; rowContainer: HTMLElement; @@ -183,7 +185,7 @@ export class MockTerminal implements ITerminal { export class MockInputHandlingTerminal implements IInputHandlingTerminal { element: HTMLElement; - options: ITerminalOptions = {}; + options: IInternalTerminalOptions = {}; cols: number; rows: number; charset: { [key: string]: string; }; @@ -435,3 +437,17 @@ export class MockCharSizeService implements ICharSizeService { constructor(public width: number, public height: number) {} measure(): void {} } + +export class MockOptionsService implements IOptionsService { + options: ITerminalOptions = clone(DEFAULT_OPTIONS); + onOptionChange: IEvent; + constructor(testOptions: IPartialTerminalOptions) { + Object.keys(testOptions).forEach(key => this.options[key] = (testOptions)[key]); + } + setOption(key: string, value: T): void { + throw new Error('Method not implemented.'); + } + getOption(key: string): T { + throw new Error('Method not implemented.'); + } +} diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 0fce405d..63ad04a7 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -15,7 +15,7 @@ import { clone } from 'common/Clone'; export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; // TODO: Freeze? -const DEFAULT_OPTIONS: ITerminalOptions = { +export const DEFAULT_OPTIONS: ITerminalOptions = { cols: 80, rows: 24, cursorBlink: false, diff --git a/tslint.json b/tslint.json index d2daef09..f6f1f46c 100644 --- a/tslint.json +++ b/tslint.json @@ -95,7 +95,7 @@ {"type": "default", "format": "camelCase", "leadingUnderscore": "forbid"}, {"type": "type", "format": "PascalCase"}, {"type": "class", "format": "PascalCase"}, - {"type": "property", "modifiers": ["const"], "format": "UPPER_CASE"}, + {"type": "property", "modifiers": ["const"], "format": ["camelCase", "UPPER_CASE"]}, {"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "allow"}, // TODO: Change allow to require when there aren't many PRs out // {"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "require"}, From a6540c8d064c50795d520f6d658469f1afb4dcdf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 16:48:12 -0700 Subject: [PATCH 03/13] Freeze DEFAULT_OPTIONS to prevent accidental edits --- src/common/services/OptionsService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 63ad04a7..d518aae6 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -15,7 +15,7 @@ import { clone } from 'common/Clone'; export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; // TODO: Freeze? -export const DEFAULT_OPTIONS: ITerminalOptions = { +export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ cols: 80, rows: 24, cursorBlink: false, @@ -47,7 +47,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = { debug: false, cancelEvents: false, useFlowControl: false -}; +}); /** * The set of options that only have an effect when set in the Terminal constructor. From 0dfc1afc2192f6fb5cad928827776e6ec161894d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 16:59:28 -0700 Subject: [PATCH 04/13] Create BufferService --- src/Terminal.ts | 21 ++++++++++---------- src/browser/services/CharSizeService.ts | 2 +- src/common/services/BufferService.ts | 26 +++++++++++++++++++++++++ src/common/services/Services.d.ts | 9 +++++++++ 4 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 src/common/services/BufferService.ts diff --git a/src/Terminal.ts b/src/Terminal.ts index 29a0478f..402a4a2b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -51,10 +51,11 @@ import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderCoordinator } from './renderer/RenderCoordinator'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IBufferService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; +import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -75,9 +76,6 @@ const WRITE_BUFFER_PAUSE_THRESHOLD = 5; const WRITE_TIMEOUT_MS = 12; const WRITE_BUFFER_LENGTH_THRESHOLD = 50; -const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars -const MINIMUM_ROWS = 1; - export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; @@ -108,6 +106,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _customKeyEventHandler: CustomKeyEventHandler; // common services + private _bufferService: IBufferService; public optionsService: IOptionsService; // browser services @@ -188,8 +187,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // bufferline to clone/copy from for new blank lines private _blankLine: IBufferLine = null; - public cols: number; - public rows: number; + public get cols(): number { return this._bufferService.cols; } + public get rows(): number { return this._bufferService.rows; } private _onCursorMove = new EventEmitter2(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } @@ -226,7 +225,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II options: ITerminalOptions = {} ) { super(); + + // Initialize common services this.optionsService = new OptionsService(options); + this._bufferService = new BufferService(this.optionsService); + this._setupOptionsListeners(); // this.options = clone(options); @@ -263,9 +266,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _setup(): void { this._parent = document ? document.body : null; - this.cols = Math.max(this.options.cols, MINIMUM_COLS); - this.rows = Math.max(this.options.rows, MINIMUM_ROWS); - this.cursorState = 0; this.cursorHidden = false; this._customKeyEventHandler = null; @@ -1745,8 +1745,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.buffers.resize(x, y); - this.cols = x; - this.rows = y; + this._bufferService.resize(x, y); this.buffers.setupTabStops(this.cols); if (this._charSizeService) { diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 60ce685e..9312e383 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -72,7 +72,7 @@ class DomMeasureStrategy implements IMeasureStrategy { // Note that this triggers a synchronous layout const geometry = this._measureElement.getBoundingClientRect(); -console.log('measure', geometry); + // If values are 0 then the element is likely currently display:none, in which case we should // retain the previous value. if (geometry.width !== 0 && geometry.height !== 0) { diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts new file mode 100644 index 00000000..542166c1 --- /dev/null +++ b/src/common/services/BufferService.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IBufferService, IOptionsService } from './Services'; + +export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars +export const MINIMUM_ROWS = 1; + +export class BufferService implements IBufferService { + public cols: number; + public rows: number; + + constructor( + optionsService: IOptionsService + ) { + this.cols = Math.max(optionsService.options.cols, MINIMUM_COLS); + this.rows = Math.max(optionsService.options.rows, MINIMUM_ROWS); + } + + public resize(cols: number, rows: number): void { + this.cols = cols; + this.rows = rows; + } +} diff --git a/src/common/services/Services.d.ts b/src/common/services/Services.d.ts index 651e8636..8ebc0308 100644 --- a/src/common/services/Services.d.ts +++ b/src/common/services/Services.d.ts @@ -5,6 +5,15 @@ import { IEvent } from 'common/EventEmitter2'; +export interface IBufferService { + readonly cols: number; + readonly rows: number; + + // TODO: Move resize event here + + resize(cols: number, rows: number): void; +} + export interface IOptionsService { readonly options: ITerminalOptions; From c44524b55363345133fd377a38a181674e591ad6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 17:06:43 -0700 Subject: [PATCH 05/13] Use MockBufferService in tests --- src/Buffer.test.ts | 23 ++++++++++------------- src/Buffer.ts | 14 +++++++------- src/BufferSet.test.ts | 12 +++++------- src/BufferSet.ts | 12 ++++++------ src/SelectionManager.test.ts | 9 +++++---- src/SelectionModel.test.ts | 9 +++++---- src/Terminal.ts | 2 +- src/TestUtils.test.ts | 14 +++++++++++++- 8 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index d0d09805..be9afe93 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -4,10 +4,9 @@ */ import { assert, expect } from 'chai'; -import { ITerminal } from './Types'; import { Buffer } from './Buffer'; import { CircularList } from 'common/CircularList'; -import { MockTerminal, TestTerminal, MockOptionsService } from './TestUtils.test'; +import { TestTerminal, MockOptionsService, MockBufferService } from './TestUtils.test'; import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; const INIT_COLS = 80; @@ -15,25 +14,23 @@ const INIT_ROWS = 24; const INIT_SCROLLBACK = 1000; describe('Buffer', () => { - let terminal: ITerminal; let optionsService: MockOptionsService; + let bufferService: MockBufferService; let buffer: Buffer; beforeEach(() => { - terminal = new MockTerminal(); - (terminal as any).cols = INIT_COLS; - (terminal as any).rows = INIT_ROWS; optionsService = new MockOptionsService({ scrollback: INIT_SCROLLBACK }); - buffer = new Buffer(terminal, true, optionsService); + bufferService = new MockBufferService(INIT_COLS, INIT_ROWS); + buffer = new Buffer(true, optionsService, bufferService); }); describe('constructor', () => { it('should create a CircularList with max length equal to rows + scrollback, for its lines', () => { assert.instanceOf(buffer.lines, CircularList); - assert.equal(buffer.lines.maxLength, terminal.rows + INIT_SCROLLBACK); + assert.equal(buffer.lines.maxLength, bufferService.rows + INIT_SCROLLBACK); }); it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => { - assert.equal(buffer.scrollBottom, terminal.rows - 1); + assert.equal(buffer.scrollBottom, bufferService.rows - 1); }); }); @@ -152,7 +149,7 @@ describe('Buffer', () => { describe('no scrollback', () => { it('should trim from the top of the buffer when the cursor reaches the bottom', () => { - buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); + buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); assert.equal(buffer.lines.maxLength, INIT_ROWS); buffer.y = INIT_ROWS - 1; buffer.fillViewportRows(); @@ -1055,7 +1052,7 @@ describe('Buffer', () => { describe('buffer marked to have no scrollback', () => { it('should always have a scrollback of 0', () => { // Test size on initialization - buffer = new Buffer(terminal, false, new MockOptionsService({ scrollback: 1000 })); + buffer = new Buffer(false, new MockOptionsService({ scrollback: 1000 }), bufferService); buffer.fillViewportRows(); assert.equal(buffer.lines.maxLength, INIT_ROWS); // Test size on buffer increase @@ -1069,7 +1066,7 @@ describe('Buffer', () => { describe('addMarker', () => { it('should adjust a marker line when the buffer is trimmed', () => { - buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); + buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); @@ -1077,7 +1074,7 @@ describe('Buffer', () => { assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { - buffer = new Buffer(terminal, true, new MockOptionsService({ scrollback: 0 })); + buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); buffer.fillViewportRows(); assert.equal(buffer.markers.length, 0); const marker = buffer.addMarker(0); diff --git a/src/Buffer.ts b/src/Buffer.ts index dee112d9..c359ffff 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,12 +4,12 @@ */ import { CircularList, IInsertEvent } from 'common/CircularList'; -import { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; +import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; import { IBufferLine, ICellData, IAttributeData } from 'common/Types'; import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from 'common/buffer/BufferReflow'; import { Marker } from 'common/buffer/Marker'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IBufferService } from 'common/services/Services'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 @@ -39,12 +39,12 @@ export class Buffer implements IBuffer { private _rows: number; constructor( - private _terminal: ITerminal, private _hasScrollback: boolean, - private _optionsService: IOptionsService + private _optionsService: IOptionsService, + private _bufferService: IBufferService ) { - this._cols = this._terminal.cols; - this._rows = this._terminal.rows; + this._cols = this._bufferService.cols; + this._rows = this._bufferService.rows; this.clear(); } @@ -71,7 +71,7 @@ export class Buffer implements IBuffer { } public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine { - return new BufferLine(this._terminal.cols, this.getNullCell(attr), isWrapped); + return new BufferLine(this._bufferService.cols, this.getNullCell(attr), isWrapped); } public get hasScrollback(): boolean { diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index fb70e954..e8e978e5 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -4,20 +4,18 @@ */ import { assert } from 'chai'; -import { ITerminal } from './Types'; import { BufferSet } from './BufferSet'; import { Buffer } from './Buffer'; -import { MockTerminal, MockOptionsService } from './TestUtils.test'; +import { MockOptionsService, MockBufferService } from './TestUtils.test'; describe('BufferSet', () => { - let terminal: ITerminal; let bufferSet: BufferSet; beforeEach(() => { - terminal = new MockTerminal(); - (terminal as any).cols = 80; - (terminal as any).rows = 24; - bufferSet = new BufferSet(terminal, new MockOptionsService({ scrollback: 1000 })); + bufferSet = new BufferSet( + new MockOptionsService({ scrollback: 1000 }), + new MockBufferService(80, 24) + ); }); describe('constructor', () => { diff --git a/src/BufferSet.ts b/src/BufferSet.ts index f5df2073..4b61c39a 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { ITerminal, IBufferSet, IBuffer } from './Types'; +import { IBufferSet, IBuffer } from './Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from './Buffer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IBufferService } from 'common/services/Services'; /** * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and @@ -27,15 +27,15 @@ export class BufferSet implements IBufferSet { * @param _terminal - The terminal the BufferSet will belong to */ constructor( - private _terminal: ITerminal, - readonly optionsService: IOptionsService + readonly optionsService: IOptionsService, + readonly bufferService: IBufferService ) { - this._normal = new Buffer(this._terminal, true, optionsService); + this._normal = new Buffer(true, optionsService, bufferService); this._normal.fillViewportRows(); // The alt buffer should never have scrollback. // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer - this._alt = new Buffer(this._terminal, false, optionsService); + this._alt = new Buffer(false, optionsService, bufferService); this._activeBuffer = this._normal; this.setupTabStops(); diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 970a8e9b..f2f5cb9a 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -9,7 +9,7 @@ import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer } from './Types'; import { IBufferLine } from 'common/Types'; -import { MockTerminal, MockCharSizeService, MockOptionsService } from './TestUtils.test'; +import { MockTerminal, MockCharSizeService, MockOptionsService, MockBufferService } from './TestUtils.test'; import { BufferLine, CellData } from 'common/buffer/BufferLine'; class TestMockTerminal extends MockTerminal { @@ -44,9 +44,10 @@ describe('SelectionManager', () => { beforeEach(() => { terminal = new TestMockTerminal(); - (terminal as any).cols = 80; - (terminal as any).rows = 2; - terminal.buffers = new BufferSet(terminal, new MockOptionsService({ scrollback: 100 })); + terminal.buffers = new BufferSet( + new MockOptionsService({ scrollback: 100 }), + new MockBufferService(80, 2) + ); terminal.buffer = terminal.buffers.active; buffer = terminal.buffer; selectionManager = new TestSelectionManager(terminal); diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 05444aac..1b011e25 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { MockTerminal, MockOptionsService } from './TestUtils.test'; +import { MockTerminal, MockOptionsService, MockBufferService } from './TestUtils.test'; class TestSelectionModel extends SelectionModel { constructor( @@ -23,9 +23,10 @@ describe('SelectionManager', () => { beforeEach(() => { terminal = new MockTerminal(); - (terminal as any).cols = 80; - (terminal as any).rows = 2; - terminal.buffers = new BufferSet(terminal, new MockOptionsService({ scrollback: 10 })); + terminal.buffers = new BufferSet( + new MockOptionsService({ scrollback: 10 }), + new MockBufferService(80, 2) + ); terminal.buffer = terminal.buffers.active; model = new TestSelectionModel(terminal); diff --git a/src/Terminal.ts b/src/Terminal.ts index 402a4a2b..38efd3b4 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -313,7 +313,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.soundManager = this.soundManager || new SoundManager(this); // Create the terminal's buffers and set the current buffer - this.buffers = new BufferSet(this, this.optionsService); + this.buffers = new BufferSet(this.optionsService, this._bufferService); if (this.selectionManager) { this.selectionManager.clearSelection(); this.selectionManager.initBuffersListeners(); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 5ac908e4..0ffdcf92 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -12,7 +12,7 @@ import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; import { IColorManager, IColorSet } from 'browser/Types'; -import { IOptionsService, IPartialTerminalOptions, ITerminalOptions } from 'common/services/Services'; +import { IOptionsService, IPartialTerminalOptions, ITerminalOptions, IBufferService } from 'common/services/Services'; import { ICharSizeService } from 'browser/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; @@ -451,3 +451,15 @@ export class MockOptionsService implements IOptionsService { throw new Error('Method not implemented.'); } } + +export class MockBufferService implements IBufferService { + constructor( + public cols: number, + public rows: number + ) {} + resize(cols: number, rows: number): void { + this.cols = cols; + this.rows = rows; + } + +} From 291fc7368d83c4e63702bc4fba9dc0407e524878 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 17:12:18 -0700 Subject: [PATCH 06/13] Fix remaining tests --- src/SelectionManager.test.ts | 2 +- src/SelectionManager.ts | 6 ++++-- src/SelectionModel.test.ts | 11 +++++++---- src/SelectionModel.ts | 10 ++++++---- src/Terminal.ts | 2 +- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index f2f5cb9a..97d48eac 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -20,7 +20,7 @@ class TestSelectionManager extends SelectionManager { constructor( terminal: ITerminal ) { - super(terminal, new MockCharSizeService(10, 10)); + super(terminal, new MockCharSizeService(10, 10), new MockBufferService(20, 20)); } public get model(): SelectionModel { return this._model; } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 498b4abe..9aa1f99f 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -13,6 +13,7 @@ import { CellData } from 'common/buffer/BufferLine'; import { IDisposable } from 'xterm'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { ICharSizeService } from 'browser/services/Services'; +import { IBufferService } from 'common/services/Services'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -117,12 +118,13 @@ export class SelectionManager implements ISelectionManager { constructor( private _terminal: ITerminal, - private _charSizeService: ICharSizeService + private _charSizeService: ICharSizeService, + bufferService: IBufferService ) { this._initListeners(); this.enable(); - this._model = new SelectionModel(_terminal); + this._model = new SelectionModel(_terminal, bufferService); this._activeSelectionMode = SelectionMode.NORMAL; } diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 1b011e25..28420a78 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -8,12 +8,14 @@ import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { MockTerminal, MockOptionsService, MockBufferService } from './TestUtils.test'; +import { IBufferService } from 'common/services/Services'; class TestSelectionModel extends SelectionModel { constructor( - terminal: ITerminal + terminal: ITerminal, + bufferService: IBufferService ) { - super(terminal); + super(terminal, bufferService); } } @@ -23,13 +25,14 @@ describe('SelectionManager', () => { beforeEach(() => { terminal = new MockTerminal(); + const bufferService = new MockBufferService(80, 2); terminal.buffers = new BufferSet( new MockOptionsService({ scrollback: 10 }), - new MockBufferService(80, 2) + bufferService ); terminal.buffer = terminal.buffers.active; - model = new TestSelectionModel(terminal); + model = new TestSelectionModel(terminal, bufferService); }); describe('clearSelection', () => { diff --git a/src/SelectionModel.ts b/src/SelectionModel.ts index f87667f2..44cd4cac 100644 --- a/src/SelectionModel.ts +++ b/src/SelectionModel.ts @@ -4,6 +4,7 @@ */ import { ITerminal } from './Types'; +import { IBufferService } from 'common/services/Services'; /** * Represents a selection within the buffer. This model only cares about column @@ -33,7 +34,8 @@ export class SelectionModel { public selectionEnd: [number, number]; constructor( - private _terminal: ITerminal + private _terminal: ITerminal, + private _bufferService: IBufferService ) { this.clearSelection(); } @@ -69,7 +71,7 @@ export class SelectionModel { */ public get finalSelectionEnd(): [number, number] { if (this.isSelectAllActive) { - return [this._terminal.cols, this._terminal.buffer.ybase + this._terminal.rows - 1]; + return [this._bufferService.cols, this._terminal.buffer.ybase + this._bufferService.rows - 1]; } if (!this.selectionStart) { @@ -79,8 +81,8 @@ export class SelectionModel { // Use the selection start + length if the end doesn't exist or they're reversed if (!this.selectionEnd || this.areSelectionValuesReversed()) { const startPlusLength = this.selectionStart[0] + this.selectionStartLength; - if (startPlusLength > this._terminal.cols) { - return [startPlusLength % this._terminal.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._terminal.cols)]; + if (startPlusLength > this._bufferService.cols) { + return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)]; } return [startPlusLength, this.selectionStart[1]]; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 38efd3b4..47672c38 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -643,7 +643,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.addDisposableListener('focus', () => this._renderCoordinator.onFocus())); this.register(this._renderCoordinator.onDimensionsChange(() => this.viewport.syncScrollArea())); - this.selectionManager = new SelectionManager(this, this._charSizeService); + this.selectionManager = new SelectionManager(this, this._charSizeService, this._bufferService); this.register(this.selectionManager.onSelectionChange(() => this._onSelectionChange.fire())); this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this.selectionManager.onMouseDown(e))); this.register(this.selectionManager.onRedrawRequest(e => this._renderCoordinator.onSelectionChanged(e.start, e.end, e.columnSelectMode))); From 5c93f2971e47325dd8e4d299ad920991b59cbb17 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 17:17:17 -0700 Subject: [PATCH 07/13] Move buffer types to common --- src/AccessibilityManager.ts | 3 ++- src/Buffer.ts | 2 +- src/BufferSet.ts | 3 ++- src/Linkifier.ts | 3 ++- src/SelectionManager.test.ts | 3 ++- src/SelectionManager.ts | 3 ++- src/TestUtils.test.ts | 3 ++- src/Types.ts | 41 ++------------------------------- src/common/buffer/Types.ts | 44 ++++++++++++++++++++++++++++++++++++ src/public/Terminal.ts | 3 ++- 10 files changed, 61 insertions(+), 47 deletions(-) create mode 100644 src/common/buffer/Types.ts diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 4ff359d0..6d47a75f 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -4,7 +4,8 @@ */ import * as Strings from './Strings'; -import { ITerminal, IBuffer } from './Types'; +import { ITerminal } from './Types'; +import { IBuffer } from 'common/buffer/Types'; import { isMac } from 'common/Platform'; import { RenderDebouncer } from 'browser/RenderDebouncer'; import { addDisposableDomListener } from 'browser/Lifecycle'; diff --git a/src/Buffer.ts b/src/Buffer.ts index c359ffff..8d4b3686 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,7 +4,7 @@ */ import { CircularList, IInsertEvent } from 'common/CircularList'; -import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; +import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData } from 'common/Types'; import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from 'common/buffer/BufferReflow'; diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 4b61c39a..47e9d6fa 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { IBufferSet, IBuffer } from './Types'; +import { IBufferSet } from './Types'; +import { IBuffer } from 'common/buffer/Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from './Buffer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; diff --git a/src/Linkifier.ts b/src/Linkifier.ts index a66c1388..50794a58 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult, IMouseZoneManager } from './Types'; +import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IMouseZoneManager } from './Types'; +import { IBufferStringIteratorResult } from 'common/buffer/Types'; import { MouseZone } from './MouseZoneManager'; import { getStringCellWidth } from 'common/CharWidth'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 97d48eac..40a75a5a 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -7,7 +7,8 @@ import { assert } from 'chai'; import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { ITerminal, IBuffer } from './Types'; +import { ITerminal } from './Types'; +import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import { MockTerminal, MockCharSizeService, MockOptionsService, MockBufferService } from './TestUtils.test'; import { BufferLine, CellData } from 'common/buffer/BufferLine'; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 9aa1f99f..fe36a348 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { ITerminal, ISelectionManager, IBuffer, ISelectionRedrawRequestEvent } from './Types'; +import { ITerminal, ISelectionManager, ISelectionRedrawRequestEvent } from './Types'; +import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import { MouseHelper } from './MouseHelper'; import * as Browser from 'common/Platform'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 0ffdcf92..47dd4089 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,8 @@ */ import { IRenderer, IRenderDimensions } from './renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions as IInternalTerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions as IInternalTerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler } from './Types'; +import { IBuffer, IBufferStringIterator } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; import { Buffer } from './Buffer'; import * as Browser from 'common/Platform'; diff --git a/src/Types.ts b/src/Types.ts index cb6f4178..40328cf4 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -4,10 +4,11 @@ */ import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; -import { ICharset, IAttributeData, ICellData, IBufferLine, CharData, ICircularList } from 'common/Types'; +import { ICharset, IAttributeData, CharData } from 'common/Types'; import { IEvent } from 'common/EventEmitter2'; import { IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; +import { IBuffer } from 'common/buffer/Types'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -18,9 +19,6 @@ export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: bo export type CharacterJoinerHandler = (text: string) => [number, number][]; -// BufferIndex denotes a position in the buffer: [rowIndex, colIndex] -export type BufferIndex = [number, number]; - /** * This interface encapsulates everything needed from the Terminal by the * InputHandler. This cleanly separates the large amount of methods needed by @@ -298,41 +296,6 @@ export interface ITerminalOptions extends IPublicTerminalOptions { useFlowControl?: boolean; } -export interface IBufferStringIteratorResult { - range: {first: number, last: number}; - content: string; -} - -export interface IBufferStringIterator { - hasNext(): boolean; - next(): IBufferStringIteratorResult; -} - -export interface IBuffer { - readonly lines: ICircularList; - ydisp: number; - ybase: number; - y: number; - x: number; - tabs: any; - scrollBottom: number; - scrollTop: number; - hasScrollback: boolean; - savedY: number; - savedX: number; - savedCurAttrData: IAttributeData; - isCursorInViewport: boolean; - translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; - getWrappedRangeForLine(y: number): { first: number, last: number }; - nextStop(x?: number): number; - prevStop(x?: number): number; - getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine; - stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[]; - iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator; - getNullCell(attr?: IAttributeData): ICellData; - getWhitespaceCell(attr?: IAttributeData): ICellData; -} - export interface IBufferSet { alt: IBuffer; normal: IBuffer; diff --git a/src/common/buffer/Types.ts b/src/common/buffer/Types.ts new file mode 100644 index 00000000..9a483735 --- /dev/null +++ b/src/common/buffer/Types.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IAttributeData, ICircularList, IBufferLine, ICellData } from 'common/Types'; + +// BufferIndex denotes a position in the buffer: [rowIndex, colIndex] +export type BufferIndex = [number, number]; + +export interface IBufferStringIteratorResult { + range: {first: number, last: number}; + content: string; +} + +export interface IBufferStringIterator { + hasNext(): boolean; + next(): IBufferStringIteratorResult; +} + +export interface IBuffer { + readonly lines: ICircularList; + ydisp: number; + ybase: number; + y: number; + x: number; + tabs: any; + scrollBottom: number; + scrollTop: number; + hasScrollback: boolean; + savedY: number; + savedX: number; + savedCurAttrData: IAttributeData; + isCursorInViewport: boolean; + translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; + getWrappedRangeForLine(y: number): { first: number, last: number }; + nextStop(x?: number): number; + prevStop(x?: number): number; + getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine; + stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[]; + iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator; + getNullCell(attr?: IAttributeData): ICellData; + getWhitespaceCell(attr?: IAttributeData): ICellData; +} diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index f2524381..5c7b4b99 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -4,8 +4,9 @@ */ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm'; -import { ITerminal, IBuffer } from '../Types'; +import { ITerminal } from '../Types'; import { IBufferLine } from 'common/Types'; +import { IBuffer } from 'common/buffer/Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; import { IEvent } from 'common/EventEmitter2'; From 50378403cf8d806d9616b8fc2ee0ded6c67f3c31 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 17:43:40 -0700 Subject: [PATCH 08/13] Move Buffer to common --- src/Buffer.test.ts | 2 +- src/BufferSet.test.ts | 2 +- src/BufferSet.ts | 2 +- src/SelectionManager.test.ts | 54 +++++++++++++++++-------------- src/Terminal.ts | 2 +- src/TestUtils.test.ts | 5 ++- src/{ => common/buffer}/Buffer.ts | 26 +++++++-------- 7 files changed, 49 insertions(+), 44 deletions(-) rename src/{ => common/buffer}/Buffer.ts (98%) diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index be9afe93..042c42d5 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -4,7 +4,7 @@ */ import { assert, expect } from 'chai'; -import { Buffer } from './Buffer'; +import { Buffer } from './common/buffer/Buffer'; import { CircularList } from 'common/CircularList'; import { TestTerminal, MockOptionsService, MockBufferService } from './TestUtils.test'; import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index e8e978e5..44c23d79 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -5,7 +5,7 @@ import { assert } from 'chai'; import { BufferSet } from './BufferSet'; -import { Buffer } from './Buffer'; +import { Buffer } from './common/buffer/Buffer'; import { MockOptionsService, MockBufferService } from './TestUtils.test'; describe('BufferSet', () => { diff --git a/src/BufferSet.ts b/src/BufferSet.ts index 47e9d6fa..fc07b9a7 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -6,7 +6,7 @@ import { IBufferSet } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IAttributeData } from 'common/Types'; -import { Buffer } from './Buffer'; +import { Buffer } from './common/buffer/Buffer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { IOptionsService, IBufferService } from 'common/services/Services'; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 40a75a5a..57397faa 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -12,6 +12,7 @@ import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; import { MockTerminal, MockCharSizeService, MockOptionsService, MockBufferService } from './TestUtils.test'; import { BufferLine, CellData } from 'common/buffer/BufferLine'; +import { IBufferService } from 'common/services/Services'; class TestMockTerminal extends MockTerminal { emit(event: string, data: any): void {} @@ -19,9 +20,10 @@ class TestMockTerminal extends MockTerminal { class TestSelectionManager extends SelectionManager { constructor( - terminal: ITerminal + terminal: ITerminal, + bufferService: IBufferService ) { - super(terminal, new MockCharSizeService(10, 10), new MockBufferService(20, 20)); + super(terminal, new MockCharSizeService(10, 10), bufferService); } public get model(): SelectionModel { return this._model; } @@ -41,17 +43,21 @@ class TestSelectionManager extends SelectionManager { describe('SelectionManager', () => { let terminal: ITerminal; let buffer: IBuffer; + let bufferService: IBufferService; let selectionManager: TestSelectionManager; beforeEach(() => { terminal = new TestMockTerminal(); + bufferService = new MockBufferService(20, 20); terminal.buffers = new BufferSet( new MockOptionsService({ scrollback: 100 }), - new MockBufferService(80, 2) + bufferService ); + terminal.cols = 20; + terminal.rows = 20; terminal.buffer = terminal.buffers.active; buffer = terminal.buffer; - selectionManager = new TestSelectionManager(terminal); + selectionManager = new TestSelectionManager(terminal, bufferService); }); function stringToRow(text: string): IBufferLine { @@ -191,36 +197,36 @@ describe('SelectionManager', () => { assert.equal(selectionManager.selectionText, 'ij"'); }); it('should expand upwards or downards for wrapped lines', () => { - buffer.lines.set(0, stringToRow(' foo')); - buffer.lines.set(1, stringToRow('bar ')); + buffer.lines.set(0, stringToRow(' foo')); + buffer.lines.set(1, stringToRow('bar ')); buffer.lines.get(1).isWrapped = true; selectionManager.selectWordAt([1, 1]); assert.equal(selectionManager.selectionText, 'foobar'); selectionManager.model.clearSelection(); - selectionManager.selectWordAt([78, 0]); + selectionManager.selectWordAt([18, 0]); assert.equal(selectionManager.selectionText, 'foobar'); }); it('should expand both upwards and downwards for word wrapped over many lines', () => { - const expectedText = 'fooaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccbar'; - buffer.lines.set(0, stringToRow(' foo')); - buffer.lines.set(1, stringToRow('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')); - buffer.lines.set(2, stringToRow('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')); - buffer.lines.set(3, stringToRow('cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc')); - buffer.lines.set(4, stringToRow('bar ')); + const expectedText = 'fooaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbccccccccccccccccccccbar'; + buffer.lines.set(0, stringToRow(' foo')); + buffer.lines.set(1, stringToRow('aaaaaaaaaaaaaaaaaaaa')); + buffer.lines.set(2, stringToRow('bbbbbbbbbbbbbbbbbbbb')); + buffer.lines.set(3, stringToRow('cccccccccccccccccccc')); + buffer.lines.set(4, stringToRow('bar ')); buffer.lines.get(1).isWrapped = true; buffer.lines.get(2).isWrapped = true; buffer.lines.get(3).isWrapped = true; buffer.lines.get(4).isWrapped = true; - selectionManager.selectWordAt([78, 0]); + selectionManager.selectWordAt([18, 0]); assert.equal(selectionManager.selectionText, expectedText); selectionManager.model.clearSelection(); - selectionManager.selectWordAt([40, 1]); + selectionManager.selectWordAt([10, 1]); assert.equal(selectionManager.selectionText, expectedText); selectionManager.model.clearSelection(); - selectionManager.selectWordAt([40, 2]); + selectionManager.selectWordAt([10, 2]); assert.equal(selectionManager.selectionText, expectedText); selectionManager.model.clearSelection(); - selectionManager.selectWordAt([40, 3]); + selectionManager.selectWordAt([10, 3]); assert.equal(selectionManager.selectionText, expectedText); selectionManager.model.clearSelection(); selectionManager.selectWordAt([1, 4]); @@ -341,7 +347,7 @@ describe('SelectionManager', () => { selectionManager.selectLineAt(0); assert.equal(selectionManager.selectionText, 'foo bar', 'The selected text is correct'); assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]); - assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 0], 'The actual selection spans the entire column'); + assert.deepEqual(selectionManager.model.finalSelectionEnd, [bufferService.cols, 0], 'The actual selection spans the entire column'); }); it('should select the entire wrapped line', () => { buffer.lines.set(0, stringToRow('foo')); @@ -351,7 +357,7 @@ describe('SelectionManager', () => { selectionManager.selectLineAt(0); assert.equal(selectionManager.selectionText, 'foobar', 'The selected text is correct'); assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]); - assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 1], 'The actual selection spans the entire column'); + assert.deepEqual(selectionManager.model.finalSelectionEnd, [bufferService.cols, 1], 'The actual selection spans the entire column'); }); }); @@ -364,7 +370,7 @@ describe('SelectionManager', () => { buffer.lines.set(3, stringToRow('4')); buffer.lines.set(4, stringToRow('5')); selectionManager.selectAll(); - terminal.buffer.ybase = buffer.lines.length - terminal.rows; + terminal.buffer.ybase = buffer.lines.length - bufferService.rows; assert.equal(selectionManager.selectionText, '1\n2\n3\n4\n5'); }); }); @@ -377,7 +383,7 @@ describe('SelectionManager', () => { buffer.lines.set(2, stringToRow('3')); selectionManager.selectLines(1, 1); assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 1]); - assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 1]); + assert.deepEqual(selectionManager.model.finalSelectionEnd, [bufferService.cols, 1]); }); it('should select multiple lines', () => { buffer.lines.length = 5; @@ -388,7 +394,7 @@ describe('SelectionManager', () => { buffer.lines.set(4, stringToRow('5')); selectionManager.selectLines(1, 3); assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 1]); - assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 3]); + assert.deepEqual(selectionManager.model.finalSelectionEnd, [bufferService.cols, 3]); }); it('should select the to the start when requesting a negative row', () => { buffer.lines.length = 2; @@ -396,7 +402,7 @@ describe('SelectionManager', () => { buffer.lines.set(1, stringToRow('2')); selectionManager.selectLines(-1, 0); assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 0]); - assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 0]); + assert.deepEqual(selectionManager.model.finalSelectionEnd, [bufferService.cols, 0]); }); it('should select the to the end when requesting beyond the final row', () => { buffer.lines.length = 2; @@ -404,7 +410,7 @@ describe('SelectionManager', () => { buffer.lines.set(1, stringToRow('2')); selectionManager.selectLines(1, 2); assert.deepEqual(selectionManager.model.finalSelectionStart, [0, 1]); - assert.deepEqual(selectionManager.model.finalSelectionEnd, [terminal.cols, 1]); + assert.deepEqual(selectionManager.model.finalSelectionEnd, [bufferService.cols, 1]); }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index 47672c38..a15c9229 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -24,7 +24,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IMouseZoneManager } from './Types'; import { IRenderer } from './renderer/Types'; import { BufferSet } from './BufferSet'; -import { Buffer } from './Buffer'; +import { Buffer } from './common/buffer/Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from 'common/EventEmitter'; import { Viewport } from './Viewport'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 47dd4089..f9f4f037 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IRenderer, IRenderDimensions } from './renderer/Types'; import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions as IInternalTerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler } from './Types'; import { IBuffer, IBufferStringIterator } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; -import { Buffer } from './Buffer'; +import { Buffer } from './common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; @@ -33,8 +33,7 @@ export class MockTerminal implements ITerminal { onTitleChange: IEvent; onScroll: IEvent; onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>; - onRender: IEvent<{ start: number - ; end: number; }>; + onRender: IEvent<{ start: number; end: number; }>; onResize: IEvent<{ cols: number; rows: number; }>; markers: IMarker[]; optionsService: IOptionsService; diff --git a/src/Buffer.ts b/src/common/buffer/Buffer.ts similarity index 98% rename from src/Buffer.ts rename to src/common/buffer/Buffer.ts index 8d4b3686..4269f79c 100644 --- a/src/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -21,16 +21,16 @@ export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 * - scroll position */ export class Buffer implements IBuffer { - public lines: CircularList; - public ydisp: number; - public ybase: number; - public y: number; - public x: number; - public scrollBottom: number; - public scrollTop: number; + public lines!: CircularList; + public ydisp: number = 0; + public ybase: number = 0; + public y: number = 0; + public x: number = 0; + public scrollBottom!: number; + public scrollTop!: number; public tabs: any; - public savedY: number; - public savedX: number; + public savedY: number = 0; + public savedX: number = 0; public savedCurAttrData = DEFAULT_ATTR_DATA.clone(); public markers: Marker[] = []; private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); @@ -150,7 +150,7 @@ export class Buffer implements IBuffer { // Deal with columns increasing (reducing needs to happen after reflow) if (this._cols < newCols) { for (let i = 0; i < this.lines.length; i++) { - this.lines.get(i).resize(newCols, nullCell); + this.lines.get(i)!.resize(newCols, nullCell); } } @@ -223,7 +223,7 @@ export class Buffer implements IBuffer { // Trim the end of the line off if cols shrunk if (this._cols > newCols) { for (let i = 0; i < this.lines.length; i++) { - this.lines.get(i).resize(newCols, nullCell); + this.lines.get(i)!.resize(newCols, nullCell); } } } @@ -505,11 +505,11 @@ export class Buffer implements IBuffer { let first = y; let last = y; // Scan upwards for wrapped lines - while (first > 0 && this.lines.get(first).isWrapped) { + while (first > 0 && this.lines.get(first)!.isWrapped) { first--; } // Scan downwards for wrapped lines - while (last + 1 < this.lines.length && this.lines.get(last + 1).isWrapped) { + while (last + 1 < this.lines.length && this.lines.get(last + 1)!.isWrapped) { last++; } return { first, last }; From 658cd987d9abb95797270de61909cf84d44dab13 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 17:56:55 -0700 Subject: [PATCH 09/13] Move Buffer.test and test utils to common --- src/Buffer.test.ts | 1398 ------------------------------ src/BufferSet.test.ts | 2 +- src/SelectionManager.test.ts | 3 +- src/SelectionModel.test.ts | 3 +- src/TestUtils.test.ts | 36 +- src/common/TestUtils.test.ts | 34 + src/common/buffer/Buffer.test.ts | 1398 ++++++++++++++++++++++++++++++ 7 files changed, 1441 insertions(+), 1433 deletions(-) delete mode 100644 src/Buffer.test.ts create mode 100644 src/common/TestUtils.test.ts create mode 100644 src/common/buffer/Buffer.test.ts diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts deleted file mode 100644 index 042c42d5..00000000 --- a/src/Buffer.test.ts +++ /dev/null @@ -1,1398 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert, expect } from 'chai'; -import { Buffer } from './common/buffer/Buffer'; -import { CircularList } from 'common/CircularList'; -import { TestTerminal, MockOptionsService, MockBufferService } from './TestUtils.test'; -import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; - -const INIT_COLS = 80; -const INIT_ROWS = 24; -const INIT_SCROLLBACK = 1000; - -describe('Buffer', () => { - let optionsService: MockOptionsService; - let bufferService: MockBufferService; - let buffer: Buffer; - - beforeEach(() => { - optionsService = new MockOptionsService({ scrollback: INIT_SCROLLBACK }); - bufferService = new MockBufferService(INIT_COLS, INIT_ROWS); - buffer = new Buffer(true, optionsService, bufferService); - }); - - describe('constructor', () => { - it('should create a CircularList with max length equal to rows + scrollback, for its lines', () => { - assert.instanceOf(buffer.lines, CircularList); - assert.equal(buffer.lines.maxLength, bufferService.rows + INIT_SCROLLBACK); - }); - it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => { - assert.equal(buffer.scrollBottom, bufferService.rows - 1); - }); - }); - - describe('fillViewportRows', () => { - it('should fill the buffer with blank lines based on the size of the viewport', () => { - const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR_DATA).loadCell(0, new CellData()).getAsCharData(); - 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).loadCell(x, new CellData()).getAsCharData(), blankLineChar); - } - } - }); - }); - - describe('getWrappedRangeForLine', () => { - describe('non-wrapped', () => { - it('should return a single row for the first row', () => { - buffer.fillViewportRows(); - assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 0 }); - }); - it('should return a single row for a middle row', () => { - buffer.fillViewportRows(); - assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 12 }); - }); - it('should return a single row for the last row', () => { - buffer.fillViewportRows(); - assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 23, last: 23 }); - }); - }); - describe('wrapped', () => { - it('should return a range for the first row', () => { - buffer.fillViewportRows(); - buffer.lines.get(1).isWrapped = true; - assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 1 }); - }); - it('should return a range for a middle row wrapping upwards', () => { - buffer.fillViewportRows(); - buffer.lines.get(12).isWrapped = true; - assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 11, last: 12 }); - }); - it('should return a range for a middle row wrapping downwards', () => { - buffer.fillViewportRows(); - buffer.lines.get(13).isWrapped = true; - assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 13 }); - }); - it('should return a range for a middle row wrapping both ways', () => { - buffer.fillViewportRows(); - buffer.lines.get(11).isWrapped = true; - buffer.lines.get(12).isWrapped = true; - buffer.lines.get(13).isWrapped = true; - buffer.lines.get(14).isWrapped = true; - assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 10, last: 14 }); - }); - it('should return a range for the last row', () => { - buffer.fillViewportRows(); - buffer.lines.get(23).isWrapped = true; - assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 22, last: 23 }); - }); - it('should return a range for a row that wraps upward to first row', () => { - buffer.fillViewportRows(); - buffer.lines.get(1).isWrapped = true; - assert.deepEqual(buffer.getWrappedRangeForLine(1), { first: 0, last: 1 }); - }); - it('should return a range for a row that wraps downward to last row', () => { - buffer.fillViewportRows(); - buffer.lines.get(buffer.lines.length - 1).isWrapped = true; - assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 2), { first: 22, last: 23 }); - }); - }); - }); - - describe('resize', () => { - describe('column size is reduced', () => { - it('should trim the data in the buffer', () => { - buffer.fillViewportRows(); - buffer.resize(INIT_COLS / 2, INIT_ROWS); - assert.equal(buffer.lines.length, INIT_ROWS); - for (let i = 0; i < INIT_ROWS; i++) { - assert.equal(buffer.lines.get(i).length, INIT_COLS / 2); - } - }); - }); - - describe('column size is increased', () => { - it('should add pad columns', () => { - buffer.fillViewportRows(); - buffer.resize(INIT_COLS + 10, INIT_ROWS); - assert.equal(buffer.lines.length, INIT_ROWS); - for (let i = 0; i < INIT_ROWS; i++) { - assert.equal(buffer.lines.get(i).length, INIT_COLS + 10); - } - }); - }); - - describe('row size reduced', () => { - it('should trim blank lines from the end', () => { - buffer.fillViewportRows(); - buffer.resize(INIT_COLS, INIT_ROWS - 10); - assert.equal(buffer.lines.length, INIT_ROWS - 10); - }); - - it('should move the viewport down when it\'s at the end', () => { - buffer.fillViewportRows(); - // Set cursor y to have 5 blank lines below it - buffer.y = INIT_ROWS - 5 - 1; - buffer.resize(INIT_COLS, INIT_ROWS - 10); - // Trim 5 rows - assert.equal(buffer.lines.length, INIT_ROWS - 5); - // Shift the viewport down 5 rows - assert.equal(buffer.ydisp, 5); - assert.equal(buffer.ybase, 5); - }); - - describe('no scrollback', () => { - it('should trim from the top of the buffer when the cursor reaches the bottom', () => { - buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); - assert.equal(buffer.lines.maxLength, INIT_ROWS); - buffer.y = INIT_ROWS - 1; - buffer.fillViewportRows(); - let chData = buffer.lines.get(5).loadCell(0, new CellData()).getAsCharData(); - chData[1] = 'a'; - buffer.lines.get(5).setCell(0, CellData.fromCharData(chData)); - chData = buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getAsCharData(); - chData[1] = 'b'; - buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData(chData)); - buffer.resize(INIT_COLS, INIT_ROWS - 5); - assert.equal(buffer.lines.get(0).loadCell(0, new CellData()).getAsCharData()[1], 'a'); - assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5).loadCell(0, new CellData()).getAsCharData()[1], 'b'); - }); - }); - }); - - describe('row size increased', () => { - describe('empty buffer', () => { - it('should add blank lines to end', () => { - buffer.fillViewportRows(); - assert.equal(buffer.ydisp, 0); - buffer.resize(INIT_COLS, INIT_ROWS + 10); - assert.equal(buffer.ydisp, 0); - assert.equal(buffer.lines.length, INIT_ROWS + 10); - }); - }); - - describe('filled buffer', () => { - it('should show more of the buffer above', () => { - buffer.fillViewportRows(); - // Create 10 extra blank lines - for (let i = 0; i < 10; i++) { - buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA)); - } - // Set cursor to the bottom of the buffer - buffer.y = INIT_ROWS - 1; - // Scroll down 10 lines - buffer.ybase = 10; - buffer.ydisp = 10; - assert.equal(buffer.lines.length, INIT_ROWS + 10); - buffer.resize(INIT_COLS, INIT_ROWS + 5); - // Should be should 5 more lines - assert.equal(buffer.ydisp, 5); - assert.equal(buffer.ybase, 5); - // Should not trim the buffer - assert.equal(buffer.lines.length, INIT_ROWS + 10); - }); - - it('should show more of the buffer below when the viewport is at the top of the buffer', () => { - buffer.fillViewportRows(); - // Create 10 extra blank lines - for (let i = 0; i < 10; i++) { - buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA)); - } - // Set cursor to the bottom of the buffer - buffer.y = INIT_ROWS - 1; - // Scroll down 10 lines - buffer.ybase = 10; - buffer.ydisp = 0; - assert.equal(buffer.lines.length, INIT_ROWS + 10); - buffer.resize(INIT_COLS, INIT_ROWS + 5); - // The viewport should remain at the top - assert.equal(buffer.ydisp, 0); - // The buffer ybase should move up 5 lines - assert.equal(buffer.ybase, 5); - // Should not trim the buffer - assert.equal(buffer.lines.length, INIT_ROWS + 10); - }); - }); - }); - - describe('row and column increased', () => { - it('should resize properly', () => { - buffer.fillViewportRows(); - buffer.resize(INIT_COLS + 5, INIT_ROWS + 5); - assert.equal(buffer.lines.length, INIT_ROWS + 5); - for (let i = 0; i < INIT_ROWS + 5; i++) { - assert.equal(buffer.lines.get(i).length, INIT_COLS + 5); - } - }); - }); - - describe('reflow', () => { - it('should not wrap empty lines', () => { - buffer.fillViewportRows(); - assert.equal(buffer.lines.length, INIT_ROWS); - buffer.resize(INIT_COLS - 5, INIT_ROWS); - assert.equal(buffer.lines.length, INIT_ROWS); - }); - it('should shrink row length', () => { - buffer.fillViewportRows(); - buffer.resize(5, 10); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).length, 5); - assert.equal(buffer.lines.get(1).length, 5); - assert.equal(buffer.lines.get(2).length, 5); - assert.equal(buffer.lines.get(3).length, 5); - assert.equal(buffer.lines.get(4).length, 5); - assert.equal(buffer.lines.get(5).length, 5); - assert.equal(buffer.lines.get(6).length, 5); - assert.equal(buffer.lines.get(7).length, 5); - assert.equal(buffer.lines.get(8).length, 5); - assert.equal(buffer.lines.get(9).length, 5); - }); - it('should wrap and unwrap lines', () => { - buffer.fillViewportRows(); - buffer.resize(5, 10); - const firstLine = buffer.lines.get(0); - for (let i = 0; i < 5; i++) { - const code = 'a'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - firstLine.set(i, [null, char, 1, code]); - } - buffer.y = 1; - assert.equal(buffer.lines.get(0).length, 5); - assert.equal(buffer.lines.get(0).translateToString(), 'abcde'); - buffer.resize(1, 10); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(), 'a'); - assert.equal(buffer.lines.get(1).translateToString(), 'b'); - assert.equal(buffer.lines.get(2).translateToString(), 'c'); - assert.equal(buffer.lines.get(3).translateToString(), 'd'); - assert.equal(buffer.lines.get(4).translateToString(), 'e'); - assert.equal(buffer.lines.get(5).translateToString(), ' '); - assert.equal(buffer.lines.get(6).translateToString(), ' '); - assert.equal(buffer.lines.get(7).translateToString(), ' '); - assert.equal(buffer.lines.get(8).translateToString(), ' '); - assert.equal(buffer.lines.get(9).translateToString(), ' '); - buffer.resize(5, 10); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(), 'abcde'); - assert.equal(buffer.lines.get(1).translateToString(), ' '); - assert.equal(buffer.lines.get(2).translateToString(), ' '); - assert.equal(buffer.lines.get(3).translateToString(), ' '); - assert.equal(buffer.lines.get(4).translateToString(), ' '); - assert.equal(buffer.lines.get(5).translateToString(), ' '); - assert.equal(buffer.lines.get(6).translateToString(), ' '); - assert.equal(buffer.lines.get(7).translateToString(), ' '); - assert.equal(buffer.lines.get(8).translateToString(), ' '); - assert.equal(buffer.lines.get(9).translateToString(), ' '); - }); - it('should discard parts of wrapped lines that go out of the scrollback', () => { - buffer.fillViewportRows(); - optionsService.options.scrollback = 1; - buffer.resize(10, 5); - const lastLine = buffer.lines.get(3); - for (let i = 0; i < 10; i++) { - const code = 'a'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - lastLine.set(i, [null, char, 1, code]); - } - assert.equal(buffer.lines.length, 5); - buffer.y = 4; - buffer.resize(2, 5); - assert.equal(buffer.y, 4); - assert.equal(buffer.ybase, 1); - assert.equal(buffer.lines.length, 6); - assert.equal(buffer.lines.get(0).translateToString(), 'ab'); - assert.equal(buffer.lines.get(1).translateToString(), 'cd'); - assert.equal(buffer.lines.get(2).translateToString(), 'ef'); - assert.equal(buffer.lines.get(3).translateToString(), 'gh'); - assert.equal(buffer.lines.get(4).translateToString(), 'ij'); - assert.equal(buffer.lines.get(5).translateToString(), ' '); - buffer.resize(1, 5); - assert.equal(buffer.y, 4); - assert.equal(buffer.ybase, 1); - assert.equal(buffer.lines.length, 6); - assert.equal(buffer.lines.get(0).translateToString(), 'f'); - assert.equal(buffer.lines.get(1).translateToString(), 'g'); - assert.equal(buffer.lines.get(2).translateToString(), 'h'); - assert.equal(buffer.lines.get(3).translateToString(), 'i'); - assert.equal(buffer.lines.get(4).translateToString(), 'j'); - assert.equal(buffer.lines.get(5).translateToString(), ' '); - buffer.resize(10, 5); - assert.equal(buffer.y, 1); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 5); - assert.equal(buffer.lines.get(0).translateToString(), 'fghij '); - assert.equal(buffer.lines.get(1).translateToString(), ' '); - assert.equal(buffer.lines.get(2).translateToString(), ' '); - assert.equal(buffer.lines.get(3).translateToString(), ' '); - assert.equal(buffer.lines.get(4).translateToString(), ' '); - }); - it('should remove the correct amount of rows when reflowing larger', () => { - // This is a regression test to ensure that successive wrapped lines that are getting - // 3+ lines removed on a reflow actually remove the right lines - buffer.fillViewportRows(); - buffer.resize(10, 10); - buffer.y = 2; - const firstLine = buffer.lines.get(0); - const secondLine = buffer.lines.get(1); - for (let i = 0; i < 10; i++) { - const code = 'a'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - firstLine.set(i, [null, char, 1, code]); - } - for (let i = 0; i < 10; i++) { - const code = '0'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - secondLine.set(i, [null, char, 1, code]); - } - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij'); - assert.equal(buffer.lines.get(1).translateToString(), '0123456789'); - for (let i = 2; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - buffer.resize(2, 10); - assert.equal(buffer.ybase, 1); - assert.equal(buffer.lines.length, 11); - assert.equal(buffer.lines.get(0).translateToString(), 'ab'); - assert.equal(buffer.lines.get(1).translateToString(), 'cd'); - assert.equal(buffer.lines.get(2).translateToString(), 'ef'); - assert.equal(buffer.lines.get(3).translateToString(), 'gh'); - assert.equal(buffer.lines.get(4).translateToString(), 'ij'); - assert.equal(buffer.lines.get(5).translateToString(), '01'); - assert.equal(buffer.lines.get(6).translateToString(), '23'); - assert.equal(buffer.lines.get(7).translateToString(), '45'); - assert.equal(buffer.lines.get(8).translateToString(), '67'); - assert.equal(buffer.lines.get(9).translateToString(), '89'); - assert.equal(buffer.lines.get(10).translateToString(), ' '); - buffer.resize(10, 10); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij'); - assert.equal(buffer.lines.get(1).translateToString(), '0123456789'); - for (let i = 2; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - }); - it('should transfer combined char data over to reflowed lines', () => { - buffer.fillViewportRows(); - buffer.resize(4, 3); - buffer.y = 2; - const firstLine = buffer.lines.get(0); - firstLine.set(0, [ null, 'a', 1, 'a'.charCodeAt(0) ]); - firstLine.set(1, [ null, 'b', 1, 'b'.charCodeAt(0) ]); - firstLine.set(2, [ null, 'c', 1, 'c'.charCodeAt(0) ]); - firstLine.set(3, [ null, '😁', 1, '😁'.charCodeAt(0) ]); - assert.equal(buffer.lines.length, 3); - assert.equal(buffer.lines.get(0).translateToString(), 'abc😁'); - assert.equal(buffer.lines.get(1).translateToString(), ' '); - buffer.resize(2, 3); - assert.equal(buffer.lines.get(0).translateToString(), 'ab'); - assert.equal(buffer.lines.get(1).translateToString(), 'c😁'); - }); - it('should adjust markers when reflowing', () => { - buffer.fillViewportRows(); - buffer.resize(10, 16); - for (let i = 0; i < 10; i++) { - const code = 'a'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - buffer.lines.get(0).set(i, [null, char, 1, code]); - } - for (let i = 0; i < 10; i++) { - const code = '0'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - buffer.lines.get(1).set(i, [null, char, 1, code]); - } - for (let i = 0; i < 10; i++) { - const code = 'k'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - buffer.lines.get(2).set(i, [null, char, 1, code]); - } - buffer.y = 3; - // Buffer: - // abcdefghij - // 0123456789 - // abcdefghij - const firstMarker = buffer.addMarker(0); - const secondMarker = buffer.addMarker(1); - const thirdMarker = buffer.addMarker(2); - assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij'); - assert.equal(buffer.lines.get(1).translateToString(), '0123456789'); - assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst'); - assert.equal(firstMarker.line, 0); - assert.equal(secondMarker.line, 1); - assert.equal(thirdMarker.line, 2); - buffer.resize(2, 16); - assert.equal(buffer.lines.get(0).translateToString(), 'ab'); - assert.equal(buffer.lines.get(1).translateToString(), 'cd'); - assert.equal(buffer.lines.get(2).translateToString(), 'ef'); - assert.equal(buffer.lines.get(3).translateToString(), 'gh'); - assert.equal(buffer.lines.get(4).translateToString(), 'ij'); - assert.equal(buffer.lines.get(5).translateToString(), '01'); - assert.equal(buffer.lines.get(6).translateToString(), '23'); - assert.equal(buffer.lines.get(7).translateToString(), '45'); - assert.equal(buffer.lines.get(8).translateToString(), '67'); - assert.equal(buffer.lines.get(9).translateToString(), '89'); - assert.equal(buffer.lines.get(10).translateToString(), 'kl'); - assert.equal(buffer.lines.get(11).translateToString(), 'mn'); - assert.equal(buffer.lines.get(12).translateToString(), 'op'); - assert.equal(buffer.lines.get(13).translateToString(), 'qr'); - assert.equal(buffer.lines.get(14).translateToString(), 'st'); - assert.equal(firstMarker.line, 0, 'first marker should remain unchanged'); - assert.equal(secondMarker.line, 5, 'second marker should be shifted since the first line wrapped'); - assert.equal(thirdMarker.line, 10, 'third marker should be shifted since the first and second lines wrapped'); - buffer.resize(10, 16); - assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij'); - assert.equal(buffer.lines.get(1).translateToString(), '0123456789'); - assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst'); - assert.equal(firstMarker.line, 0, 'first marker should remain unchanged'); - assert.equal(secondMarker.line, 1, 'second marker should be restored to it\'s original line'); - assert.equal(thirdMarker.line, 2, 'third marker should be restored to it\'s original line'); - assert.equal(firstMarker.isDisposed, false); - assert.equal(secondMarker.isDisposed, false); - assert.equal(thirdMarker.isDisposed, false); - }); - it('should dispose markers whose rows are trimmed during a reflow', () => { - buffer.fillViewportRows(); - optionsService.options.scrollback = 1; - buffer.resize(10, 11); - for (let i = 0; i < 10; i++) { - const code = 'a'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - buffer.lines.get(0).set(i, [null, char, 1, code]); - } - for (let i = 0; i < 10; i++) { - const code = '0'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - buffer.lines.get(1).set(i, [null, char, 1, code]); - } - for (let i = 0; i < 10; i++) { - const code = 'k'.charCodeAt(0) + i; - const char = String.fromCharCode(code); - buffer.lines.get(2).set(i, [null, char, 1, code]); - } - buffer.y = 10; - // Buffer: - // abcdefghij - // 0123456789 - // abcdefghij - const firstMarker = buffer.addMarker(0); - const secondMarker = buffer.addMarker(1); - const thirdMarker = buffer.addMarker(2); - buffer.y = 3; - assert.equal(buffer.lines.get(0).translateToString(), 'abcdefghij'); - assert.equal(buffer.lines.get(1).translateToString(), '0123456789'); - assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst'); - assert.equal(firstMarker.line, 0); - assert.equal(secondMarker.line, 1); - assert.equal(thirdMarker.line, 2); - buffer.resize(2, 11); - assert.equal(buffer.lines.get(0).translateToString(), 'ij'); - assert.equal(buffer.lines.get(1).translateToString(), '01'); - assert.equal(buffer.lines.get(2).translateToString(), '23'); - assert.equal(buffer.lines.get(3).translateToString(), '45'); - assert.equal(buffer.lines.get(4).translateToString(), '67'); - assert.equal(buffer.lines.get(5).translateToString(), '89'); - assert.equal(buffer.lines.get(6).translateToString(), 'kl'); - assert.equal(buffer.lines.get(7).translateToString(), 'mn'); - assert.equal(buffer.lines.get(8).translateToString(), 'op'); - assert.equal(buffer.lines.get(9).translateToString(), 'qr'); - assert.equal(buffer.lines.get(10).translateToString(), 'st'); - assert.equal(secondMarker.line, 1, 'second marker should remain the same as it was shifted 4 and trimmed 4'); - assert.equal(thirdMarker.line, 6, 'third marker should be shifted since the first and second lines wrapped'); - assert.equal(firstMarker.isDisposed, true, 'first marker was trimmed'); - assert.equal(secondMarker.isDisposed, false); - assert.equal(thirdMarker.isDisposed, false); - buffer.resize(10, 11); - assert.equal(buffer.lines.get(0).translateToString(), 'ij '); - assert.equal(buffer.lines.get(1).translateToString(), '0123456789'); - assert.equal(buffer.lines.get(2).translateToString(), 'klmnopqrst'); - assert.equal(secondMarker.line, 1, 'second marker should be restored'); - assert.equal(thirdMarker.line, 2, 'third marker should be restored'); - }); - it('should correctly reflow wrapped lines that end in null space (via tab char)', () => { - buffer.fillViewportRows(); - buffer.resize(4, 10); - buffer.y = 2; - buffer.lines.get(0).set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); - buffer.lines.get(0).set(1, [null, 'b', 1, 'b'.charCodeAt(0)]); - buffer.lines.get(1).set(0, [null, 'c', 1, 'c'.charCodeAt(0)]); - buffer.lines.get(1).set(1, [null, 'd', 1, 'd'.charCodeAt(0)]); - buffer.lines.get(1).isWrapped = true; - // Buffer: - // "ab " (wrapped) - // "cd" - buffer.resize(5, 10); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(true), 'ab c'); - assert.equal(buffer.lines.get(1).translateToString(false), 'd '); - buffer.resize(6, 10); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(true), 'ab cd'); - assert.equal(buffer.lines.get(1).translateToString(false), ' '); - }); - it('should wrap wide characters correctly when reflowing larger', () => { - buffer.fillViewportRows(); - buffer.resize(12, 10); - buffer.y = 2; - for (let i = 0; i < 12; i += 4) { - buffer.lines.get(0).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]); - buffer.lines.get(1).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]); - } - for (let i = 2; i < 12; i += 4) { - buffer.lines.get(0).set(i, [null, '语', 2, '语'.charCodeAt(0)]); - buffer.lines.get(1).set(i, [null, '语', 2, '语'.charCodeAt(0)]); - } - for (let i = 1; i < 12; i += 2) { - buffer.lines.get(0).set(i, [null, '', 0, undefined]); - buffer.lines.get(1).set(i, [null, '', 0, undefined]); - } - buffer.lines.get(1).isWrapped = true; - // Buffer: - // 汉语汉语汉语 (wrapped) - // 汉语汉语汉语 - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉语'); - assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语汉语'); - buffer.resize(13, 10); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉语'); - assert.equal(buffer.lines.get(0).translateToString(false), '汉语汉语汉语 '); - assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语汉语'); - assert.equal(buffer.lines.get(1).translateToString(false), '汉语汉语汉语 '); - buffer.resize(14, 10); - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉语汉'); - assert.equal(buffer.lines.get(0).translateToString(false), '汉语汉语汉语汉'); - assert.equal(buffer.lines.get(1).translateToString(true), '语汉语汉语'); - assert.equal(buffer.lines.get(1).translateToString(false), '语汉语汉语 '); - }); - it('should correctly reflow wrapped lines that end in null space (via tab char)', () => { - buffer.fillViewportRows(); - buffer.resize(4, 10); - buffer.y = 2; - buffer.lines.get(0).set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); - buffer.lines.get(0).set(1, [null, 'b', 1, 'b'.charCodeAt(0)]); - buffer.lines.get(1).set(0, [null, 'c', 1, 'c'.charCodeAt(0)]); - buffer.lines.get(1).set(1, [null, 'd', 1, 'd'.charCodeAt(0)]); - buffer.lines.get(1).isWrapped = true; - // Buffer: - // "ab " (wrapped) - // "cd" - buffer.resize(3, 10); - assert.equal(buffer.y, 2); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(false), 'ab '); - assert.equal(buffer.lines.get(1).translateToString(false), ' cd'); - buffer.resize(2, 10); - assert.equal(buffer.y, 3); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(false), 'ab'); - assert.equal(buffer.lines.get(1).translateToString(false), ' '); - assert.equal(buffer.lines.get(2).translateToString(false), 'cd'); - }); - it('should wrap wide characters correctly when reflowing smaller', () => { - buffer.fillViewportRows(); - buffer.resize(12, 10); - buffer.y = 2; - for (let i = 0; i < 12; i += 4) { - buffer.lines.get(0).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]); - buffer.lines.get(1).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]); - } - for (let i = 2; i < 12; i += 4) { - buffer.lines.get(0).set(i, [null, '语', 2, '语'.charCodeAt(0)]); - buffer.lines.get(1).set(i, [null, '语', 2, '语'.charCodeAt(0)]); - } - for (let i = 1; i < 12; i += 2) { - buffer.lines.get(0).set(i, [null, '', 0, undefined]); - buffer.lines.get(1).set(i, [null, '', 0, undefined]); - } - buffer.lines.get(1).isWrapped = true; - // Buffer: - // 汉语汉语汉语 (wrapped) - // 汉语汉语汉语 - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉语'); - assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语汉语'); - buffer.resize(11, 10); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉'); - assert.equal(buffer.lines.get(1).translateToString(true), '语汉语汉语'); - assert.equal(buffer.lines.get(2).translateToString(true), '汉语'); - buffer.resize(10, 10); - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉'); - assert.equal(buffer.lines.get(1).translateToString(true), '语汉语汉语'); - assert.equal(buffer.lines.get(2).translateToString(true), '汉语'); - buffer.resize(9, 10); - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语'); - assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语'); - assert.equal(buffer.lines.get(2).translateToString(true), '汉语汉语'); - buffer.resize(8, 10); - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语'); - assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语'); - assert.equal(buffer.lines.get(2).translateToString(true), '汉语汉语'); - buffer.resize(7, 10); - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉'); - assert.equal(buffer.lines.get(1).translateToString(true), '语汉语'); - assert.equal(buffer.lines.get(2).translateToString(true), '汉语汉'); - assert.equal(buffer.lines.get(3).translateToString(true), '语汉语'); - buffer.resize(6, 10); - assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉'); - assert.equal(buffer.lines.get(1).translateToString(true), '语汉语'); - assert.equal(buffer.lines.get(2).translateToString(true), '汉语汉'); - assert.equal(buffer.lines.get(3).translateToString(true), '语汉语'); - }); - - describe('reflowLarger cases', () => { - beforeEach(() => { - // Setup buffer state: - // 'ab' - // 'cd' (wrapped) - // 'ef' - // 'gh' (wrapped) - // 'ij' - // 'kl' (wrapped) - // ' ' - // ' ' - // ' ' - // ' ' - buffer.fillViewportRows(); - buffer.resize(2, 10); - buffer.lines.get(0).set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); - buffer.lines.get(0).set(1, [null, 'b', 1, 'b'.charCodeAt(0)]); - buffer.lines.get(1).set(0, [null, 'c', 1, 'c'.charCodeAt(0)]); - buffer.lines.get(1).set(1, [null, 'd', 1, 'd'.charCodeAt(0)]); - buffer.lines.get(1).isWrapped = true; - buffer.lines.get(2).set(0, [null, 'e', 1, 'e'.charCodeAt(0)]); - buffer.lines.get(2).set(1, [null, 'f', 1, 'f'.charCodeAt(0)]); - buffer.lines.get(3).set(0, [null, 'g', 1, 'g'.charCodeAt(0)]); - buffer.lines.get(3).set(1, [null, 'h', 1, 'h'.charCodeAt(0)]); - buffer.lines.get(3).isWrapped = true; - buffer.lines.get(4).set(0, [null, 'i', 1, 'i'.charCodeAt(0)]); - buffer.lines.get(4).set(1, [null, 'j', 1, 'j'.charCodeAt(0)]); - buffer.lines.get(5).set(0, [null, 'k', 1, 'k'.charCodeAt(0)]); - buffer.lines.get(5).set(1, [null, 'l', 1, 'l'.charCodeAt(0)]); - buffer.lines.get(5).isWrapped = true; - }); - describe('viewport not yet filled', () => { - it('should move the cursor up and add empty lines', () => { - buffer.y = 6; - buffer.resize(4, 10); - assert.equal(buffer.y, 3); - assert.equal(buffer.ydisp, 0); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(), 'abcd'); - assert.equal(buffer.lines.get(1).translateToString(), 'efgh'); - assert.equal(buffer.lines.get(2).translateToString(), 'ijkl'); - for (let i = 3; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines: number[] = []; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - describe('viewport filled, scrollback remaining', () => { - beforeEach(() => { - buffer.y = 9; - }); - describe('ybase === 0', () => { - it('should move the cursor up and add empty lines', () => { - buffer.resize(4, 10); - assert.equal(buffer.y, 6); - assert.equal(buffer.ydisp, 0); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(), 'abcd'); - assert.equal(buffer.lines.get(1).translateToString(), 'efgh'); - assert.equal(buffer.lines.get(2).translateToString(), 'ijkl'); - for (let i = 3; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines: number[] = []; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - describe('ybase !== 0', () => { - beforeEach(() => { - // Add 10 empty rows to start - for (let i = 0; i < 10; i++) { - buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); - } - buffer.ybase = 10; - }); - describe('&& ydisp === ybase', () => { - it('should adjust the viewport and keep ydisp = ybase', () => { - buffer.ydisp = 10; - buffer.resize(4, 10); - assert.equal(buffer.y, 9); - assert.equal(buffer.ydisp, 7); - assert.equal(buffer.ybase, 7); - assert.equal(buffer.lines.length, 17); - for (let i = 0; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - assert.equal(buffer.lines.get(10).translateToString(), 'abcd'); - assert.equal(buffer.lines.get(11).translateToString(), 'efgh'); - assert.equal(buffer.lines.get(12).translateToString(), 'ijkl'); - for (let i = 13; i < 17; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines: number[] = []; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - describe('&& ydisp !== ybase', () => { - it('should keep ydisp at the same value', () => { - buffer.ydisp = 5; - buffer.resize(4, 10); - assert.equal(buffer.y, 9); - assert.equal(buffer.ydisp, 5); - assert.equal(buffer.ybase, 7); - assert.equal(buffer.lines.length, 17); - for (let i = 0; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - assert.equal(buffer.lines.get(10).translateToString(), 'abcd'); - assert.equal(buffer.lines.get(11).translateToString(), 'efgh'); - assert.equal(buffer.lines.get(12).translateToString(), 'ijkl'); - for (let i = 13; i < 17; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines: number[] = []; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - }); - }); - describe('viewport filled, no scrollback remaining', () => { - // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported - describe('ybase !== 0', () => { - beforeEach(() => { - optionsService.options.scrollback = 10; - // Add 10 empty rows to start - for (let i = 0; i < 10; i++) { - buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); - } - buffer.y = 9; - buffer.ybase = 10; - }); - describe('&& ydisp === ybase', () => { - it('should trim lines and keep ydisp = ybase', () => { - buffer.ydisp = 10; - buffer.resize(4, 10); - assert.equal(buffer.y, 9); - assert.equal(buffer.ydisp, 7); - assert.equal(buffer.ybase, 7); - assert.equal(buffer.lines.length, 17); - for (let i = 0; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - assert.equal(buffer.lines.get(10).translateToString(), 'abcd'); - assert.equal(buffer.lines.get(11).translateToString(), 'efgh'); - assert.equal(buffer.lines.get(12).translateToString(), 'ijkl'); - for (let i = 13; i < 17; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines: number[] = []; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - describe('&& ydisp !== ybase', () => { - it('should trim lines and not change ydisp', () => { - buffer.ydisp = 5; - buffer.resize(4, 10); - assert.equal(buffer.y, 9); - assert.equal(buffer.ydisp, 5); - assert.equal(buffer.ybase, 7); - assert.equal(buffer.lines.length, 17); - for (let i = 0; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - assert.equal(buffer.lines.get(10).translateToString(), 'abcd'); - assert.equal(buffer.lines.get(11).translateToString(), 'efgh'); - assert.equal(buffer.lines.get(12).translateToString(), 'ijkl'); - for (let i = 13; i < 17; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines: number[] = []; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - }); - }); - }); - describe('reflowSmaller cases', () => { - beforeEach(() => { - // Setup buffer state: - // 'abcd' - // 'efgh' (wrapped) - // 'ijkl' - // ' ' - // ' ' - // ' ' - // ' ' - // ' ' - // ' ' - // ' ' - buffer.fillViewportRows(); - buffer.resize(4, 10); - buffer.lines.get(0).set(0, [null, 'a', 1, 'a'.charCodeAt(0)]); - buffer.lines.get(0).set(1, [null, 'b', 1, 'b'.charCodeAt(0)]); - buffer.lines.get(0).set(2, [null, 'c', 1, 'c'.charCodeAt(0)]); - buffer.lines.get(0).set(3, [null, 'd', 1, 'd'.charCodeAt(0)]); - buffer.lines.get(1).set(0, [null, 'e', 1, 'e'.charCodeAt(0)]); - buffer.lines.get(1).set(1, [null, 'f', 1, 'f'.charCodeAt(0)]); - buffer.lines.get(1).set(2, [null, 'g', 1, 'g'.charCodeAt(0)]); - buffer.lines.get(1).set(3, [null, 'h', 1, 'h'.charCodeAt(0)]); - buffer.lines.get(2).set(0, [null, 'i', 1, 'i'.charCodeAt(0)]); - buffer.lines.get(2).set(1, [null, 'j', 1, 'j'.charCodeAt(0)]); - buffer.lines.get(2).set(2, [null, 'k', 1, 'k'.charCodeAt(0)]); - buffer.lines.get(2).set(3, [null, 'l', 1, 'l'.charCodeAt(0)]); - }); - describe('viewport not yet filled', () => { - it('should move the cursor down', () => { - buffer.y = 3; - buffer.resize(2, 10); - assert.equal(buffer.y, 6); - assert.equal(buffer.ydisp, 0); - assert.equal(buffer.ybase, 0); - assert.equal(buffer.lines.length, 10); - assert.equal(buffer.lines.get(0).translateToString(), 'ab'); - assert.equal(buffer.lines.get(1).translateToString(), 'cd'); - assert.equal(buffer.lines.get(2).translateToString(), 'ef'); - assert.equal(buffer.lines.get(3).translateToString(), 'gh'); - assert.equal(buffer.lines.get(4).translateToString(), 'ij'); - assert.equal(buffer.lines.get(5).translateToString(), 'kl'); - for (let i = 6; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines = [1, 3, 5]; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - describe('viewport filled, scrollback remaining', () => { - beforeEach(() => { - buffer.y = 9; - }); - describe('ybase === 0', () => { - it('should trim the top', () => { - buffer.resize(2, 10); - assert.equal(buffer.y, 9); - assert.equal(buffer.ydisp, 3); - assert.equal(buffer.ybase, 3); - assert.equal(buffer.lines.length, 13); - assert.equal(buffer.lines.get(0).translateToString(), 'ab'); - assert.equal(buffer.lines.get(1).translateToString(), 'cd'); - assert.equal(buffer.lines.get(2).translateToString(), 'ef'); - assert.equal(buffer.lines.get(3).translateToString(), 'gh'); - assert.equal(buffer.lines.get(4).translateToString(), 'ij'); - assert.equal(buffer.lines.get(5).translateToString(), 'kl'); - for (let i = 6; i < 13; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines = [1, 3, 5]; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - describe('ybase !== 0', () => { - beforeEach(() => { - // Add 10 empty rows to start - for (let i = 0; i < 10; i++) { - buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); - } - buffer.ybase = 10; - }); - describe('&& ydisp === ybase', () => { - it('should adjust the viewport and keep ydisp = ybase', () => { - buffer.ydisp = 10; - buffer.resize(2, 10); - assert.equal(buffer.ydisp, 13); - assert.equal(buffer.ybase, 13); - assert.equal(buffer.lines.length, 23); - for (let i = 0; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - assert.equal(buffer.lines.get(10).translateToString(), 'ab'); - assert.equal(buffer.lines.get(11).translateToString(), 'cd'); - assert.equal(buffer.lines.get(12).translateToString(), 'ef'); - assert.equal(buffer.lines.get(13).translateToString(), 'gh'); - assert.equal(buffer.lines.get(14).translateToString(), 'ij'); - assert.equal(buffer.lines.get(15).translateToString(), 'kl'); - for (let i = 16; i < 23; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines = [11, 13, 15]; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - describe('&& ydisp !== ybase', () => { - it('should keep ydisp at the same value', () => { - buffer.ydisp = 5; - buffer.resize(2, 10); - assert.equal(buffer.ydisp, 5); - assert.equal(buffer.ybase, 13); - assert.equal(buffer.lines.length, 23); - for (let i = 0; i < 10; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - assert.equal(buffer.lines.get(10).translateToString(), 'ab'); - assert.equal(buffer.lines.get(11).translateToString(), 'cd'); - assert.equal(buffer.lines.get(12).translateToString(), 'ef'); - assert.equal(buffer.lines.get(13).translateToString(), 'gh'); - assert.equal(buffer.lines.get(14).translateToString(), 'ij'); - assert.equal(buffer.lines.get(15).translateToString(), 'kl'); - for (let i = 16; i < 23; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines = [11, 13, 15]; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - }); - }); - describe('viewport filled, no scrollback remaining', () => { - // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported - describe('ybase !== 0', () => { - beforeEach(() => { - optionsService.options.scrollback = 10; - // Add 10 empty rows to start - for (let i = 0; i < 10; i++) { - buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); - } - buffer.ybase = 10; - }); - describe('&& ydisp === ybase', () => { - it('should trim lines and keep ydisp = ybase', () => { - buffer.ydisp = 10; - buffer.y = 13; - buffer.resize(2, 10); - assert.equal(buffer.ydisp, 10); - assert.equal(buffer.ybase, 10); - assert.equal(buffer.lines.length, 20); - for (let i = 0; i < 7; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - assert.equal(buffer.lines.get(7).translateToString(), 'ab'); - assert.equal(buffer.lines.get(8).translateToString(), 'cd'); - assert.equal(buffer.lines.get(9).translateToString(), 'ef'); - assert.equal(buffer.lines.get(10).translateToString(), 'gh'); - assert.equal(buffer.lines.get(11).translateToString(), 'ij'); - assert.equal(buffer.lines.get(12).translateToString(), 'kl'); - for (let i = 13; i < 20; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines = [8, 10, 12]; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - describe('&& ydisp !== ybase', () => { - it('should trim lines and not change ydisp', () => { - buffer.ydisp = 5; - buffer.y = 13; - buffer.resize(2, 10); - assert.equal(buffer.ydisp, 5); - assert.equal(buffer.ybase, 10); - assert.equal(buffer.lines.length, 20); - for (let i = 0; i < 7; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - assert.equal(buffer.lines.get(7).translateToString(), 'ab'); - assert.equal(buffer.lines.get(8).translateToString(), 'cd'); - assert.equal(buffer.lines.get(9).translateToString(), 'ef'); - assert.equal(buffer.lines.get(10).translateToString(), 'gh'); - assert.equal(buffer.lines.get(11).translateToString(), 'ij'); - assert.equal(buffer.lines.get(12).translateToString(), 'kl'); - for (let i = 13; i < 20; i++) { - assert.equal(buffer.lines.get(i).translateToString(), ' '); - } - const wrappedLines = [8, 10, 12]; - for (let i = 0; i < buffer.lines.length; i++) { - assert.equal(buffer.lines.get(i).isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); - } - }); - }); - }); - }); - }); - }); - }); - - describe('buffer marked to have no scrollback', () => { - it('should always have a scrollback of 0', () => { - // Test size on initialization - buffer = new Buffer(false, new MockOptionsService({ scrollback: 1000 }), bufferService); - buffer.fillViewportRows(); - assert.equal(buffer.lines.maxLength, INIT_ROWS); - // Test size on buffer increase - buffer.resize(INIT_COLS, INIT_ROWS * 2); - assert.equal(buffer.lines.maxLength, INIT_ROWS * 2); - // Test size on buffer decrease - buffer.resize(INIT_COLS, INIT_ROWS / 2); - assert.equal(buffer.lines.maxLength, INIT_ROWS / 2); - }); - }); - - describe('addMarker', () => { - it('should adjust a marker line when the buffer is trimmed', () => { - buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); - buffer.fillViewportRows(); - const marker = buffer.addMarker(buffer.lines.length - 1); - assert.equal(marker.line, buffer.lines.length - 1); - buffer.lines.onTrimEmitter.fire(1); - assert.equal(marker.line, buffer.lines.length - 2); - }); - it('should dispose of a marker if it is trimmed off the buffer', () => { - buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); - buffer.fillViewportRows(); - assert.equal(buffer.markers.length, 0); - const marker = buffer.addMarker(0); - assert.equal(marker.isDisposed, false); - assert.equal(buffer.markers.length, 1); - buffer.lines.onTrimEmitter.fire(1); - assert.equal(marker.isDisposed, true); - assert.equal(buffer.markers.length, 0); - }); - }); - - describe ('translateBufferLineToString', () => { - it('should handle selecting a section of ascii text', () => { - const line = new BufferLine(4); - line.setCell(0, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(1, CellData.fromCharData([ null, 'b', 1, 'b'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([ null, 'c', 1, 'c'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([ null, 'd', 1, 'd'.charCodeAt(0)])); - 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', () => { - const line = new BufferLine(3); - line.setCell(0, CellData.fromCharData([ null, '語', 2, 35486 ])); - line.setCell(1, CellData.fromCharData([ null, '', 0, null])); - line.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); - 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', () => { - const line = new BufferLine(3); - line.setCell(0, CellData.fromCharData([ null, '語', 2, '語'.charCodeAt(0) ])); - line.setCell(1, CellData.fromCharData([ null, '', 0, null])); - line.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); - buffer.lines.set(0, line); - - const str0 = buffer.translateBufferLineToString(0, true, 0, 1); - assert.equal(str0, '語'); - - const str1 = buffer.translateBufferLineToString(0, true, 0, 2); - assert.equal(str1, '語'); - - const str2 = buffer.translateBufferLineToString(0, true, 0, 3); - assert.equal(str2, '語a'); - }); - - it('should handle single width emojis', () => { - const line = new BufferLine(2); - line.setCell(0, CellData.fromCharData([ null, '😁', 1, '😁'.charCodeAt(0) ])); - line.setCell(1, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); - buffer.lines.set(0, line); - - const str1 = buffer.translateBufferLineToString(0, true, 0, 1); - assert.equal(str1, '😁'); - - const str2 = buffer.translateBufferLineToString(0, true, 0, 2); - assert.equal(str2, '😁a'); - }); - - it('should handle double width emojis', () => { - const line = new BufferLine(2); - line.setCell(0, CellData.fromCharData([ null, '😁', 2, '😁'.charCodeAt(0) ])); - line.setCell(1, CellData.fromCharData([ null, '', 0, null])); - buffer.lines.set(0, line); - - const str1 = buffer.translateBufferLineToString(0, true, 0, 1); - assert.equal(str1, '😁'); - - const str2 = buffer.translateBufferLineToString(0, true, 0, 2); - assert.equal(str2, '😁'); - - const line2 = new BufferLine(3); - line2.setCell(0, CellData.fromCharData([ null, '😁', 2, '😁'.charCodeAt(0) ])); - line2.setCell(1, CellData.fromCharData([ null, '', 0, null])); - line2.setCell(2, CellData.fromCharData([ null, 'a', 1, 'a'.charCodeAt(0)])); - buffer.lines.set(0, line2); - - const str3 = buffer.translateBufferLineToString(0, true, 0, 3); - assert.equal(str3, '😁a'); - }); - }); - describe('stringIndexToBufferIndex', () => { - let terminal: TestTerminal; - - beforeEach(() => { - terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5}); - }); - - it('multiline ascii', () => { - const input = 'This is ASCII text spanning multiple lines.'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); - } - }); - - it('combining e\u0301 in a sentence', () => { - const input = 'Sitting in the cafe\u0301 drinking coffee.'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < 19; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); - } - // string index 18 & 19 point to combining char e\u0301 ---> same buffer Index - assert.deepEqual( - terminal.buffer.stringIndexToBufferIndex(0, 18), - terminal.buffer.stringIndexToBufferIndex(0, 19)); - // after the combining char every string index has an offset of -1 - for (let i = 19; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex); - } - }); - - it('multiline combining e\u0301', () => { - const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - // every buffer cell index contains 2 string indices - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex); - } - }); - - it('surrogate char in a sentence', () => { - const input = 'The 𝄞 is a clef widely used in modern notation.'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < 5; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); - } - // string index 4 & 5 point to surrogate char 𝄞 ---> same buffer Index - assert.deepEqual( - terminal.buffer.stringIndexToBufferIndex(0, 4), - terminal.buffer.stringIndexToBufferIndex(0, 5)); - // after the combining char every string index has an offset of -1 - for (let i = 5; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex); - } - }); - - it('multiline surrogate char', () => { - const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - // every buffer cell index contains 2 string indices - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex); - } - }); - - it('surrogate char with combining', () => { - // eye of Ra with acute accent - string length of 3 - const input = '𓂀\u0301 - the eye hiroglyph with an acute accent.'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - // index 0..2 should map to 0 - assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 1)); - assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 2)); - for (let i = 2; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i - 2) / terminal.cols) | 0, (i - 2) % terminal.cols], bufferIndex); - } - }); - - it('multiline surrogate with combining', () => { - const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - // every buffer cell index contains 3 string indices - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(((i / 3) | 0) / terminal.cols) | 0, ((i / 3) | 0) % terminal.cols], bufferIndex); - } - }); - - it('fullwidth chars', () => { - const input = 'These 123 are some fat numbers.'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < 6; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); - } - // string index 6, 7, 8 take 2 cells - assert.deepEqual([0, 8], terminal.buffer.stringIndexToBufferIndex(0, 7)); - assert.deepEqual([1, 0], terminal.buffer.stringIndexToBufferIndex(0, 8)); - // rest of the string has offset of +3 - for (let i = 9; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i + 3) / terminal.cols) | 0, (i + 3) % terminal.cols], bufferIndex); - } - }); - - it('multiline fullwidth chars', () => { - const input = '12345678901234567890'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 9; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i << 1) / terminal.cols) | 0, (i << 1) % terminal.cols], bufferIndex); - } - }); - - it('fullwidth combining with emoji - match emoji cell', () => { - const input = 'Lots of ¥\u0301 make me 😃.'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - const stringIndex = s.match(/😃/).index; - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex); - assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars(), '😃'); - }); - - it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => { - const input = 'a12345678901234567890'; - // the 'a' at the beginning moves all fullwidth chars one to the right - // now the end of the line contains a dangling empty cell since - // the next fullwidth char has to wrap early - // the dangling last cell is wrongly added in the string - // --> fixable after resolving #1685 - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 10; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); - const j = (i - 0) << 1; - assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex); - } - }); - - it('test fully wrapped buffer up to last char', () => { - const input = Array(6).join('1234567890'); - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); - assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars()); - } - }); - - it('test fully wrapped buffer up to last char with full width odd', () => { - const input = 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301' - + 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); - assert.equal( - (!(i % 3)) - ? input[i] - : (i % 3 === 1) - ? input.substr(i, 2) - : input.substr(i - 1, 2), - terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars()); - } - }); - - it('should handle \t in lines correctly', () => { - const input = '\thttps://google.de'; - terminal.writeSync(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(s, Array(optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de'); - }); - }); - describe('BufferStringIterator', function(): void { - it('iterator does not overflow buffer limits', function(): void { - const terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5}); - const data = [ - 'aaaaaaaaaa', - 'aaaaaaaaa\n', - 'aaaaaaaaaa', - 'aaaaaaaaa\n', - 'aaaaaaaaaa', - 'aaaaaaaaaa', - 'aaaaaaaaaa', - 'aaaaaaaaa\n', - 'aaaaaaaaaa', - 'aaaaaaaaaa' - ]; - terminal.writeSync(data.join('')); - // brute force test with insane values - expect(() => { - for (let overscan = 0; overscan < 20; ++overscan) { - for (let start = -10; start < 20; ++start) { - for (let end = -10; end < 20; ++end) { - const it = terminal.buffer.iterator(false, start, end, overscan, overscan); - while (it.hasNext()) { - it.next(); - } - } - } - } - }).to.not.throw(); - }); - }); -}); diff --git a/src/BufferSet.test.ts b/src/BufferSet.test.ts index 44c23d79..9dab0f7a 100644 --- a/src/BufferSet.test.ts +++ b/src/BufferSet.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { BufferSet } from './BufferSet'; import { Buffer } from './common/buffer/Buffer'; -import { MockOptionsService, MockBufferService } from './TestUtils.test'; +import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; describe('BufferSet', () => { let bufferSet: BufferSet; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 57397faa..f81cdcbe 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,7 +10,8 @@ import { BufferSet } from './BufferSet'; import { ITerminal } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; -import { MockTerminal, MockCharSizeService, MockOptionsService, MockBufferService } from './TestUtils.test'; +import { MockTerminal, MockCharSizeService } from './TestUtils.test'; +import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; import { BufferLine, CellData } from 'common/buffer/BufferLine'; import { IBufferService } from 'common/services/Services'; diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 28420a78..479e20d3 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -7,7 +7,8 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; -import { MockTerminal, MockOptionsService, MockBufferService } from './TestUtils.test'; +import { MockTerminal } from './TestUtils.test'; +import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; class TestSelectionModel extends SelectionModel { diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index f9f4f037..fa48b4b0 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions } from './renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions as IInternalTerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler } from './Types'; import { IBuffer, IBufferStringIterator } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; import { Buffer } from './common/buffer/Buffer'; @@ -13,10 +13,8 @@ import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; import { IColorManager, IColorSet } from 'browser/Types'; -import { IOptionsService, IPartialTerminalOptions, ITerminalOptions, IBufferService } from 'common/services/Services'; +import { IOptionsService } from 'common/services/Services'; import { ICharSizeService } from 'browser/services/Services'; -import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; -import { clone } from 'common/Clone'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -124,7 +122,7 @@ export class MockTerminal implements ITerminal { renderer: IRenderer; linkifier: ILinkifier; isFocused: boolean; - options: IInternalTerminalOptions = {}; + options: ITerminalOptions = {}; element: HTMLElement; screenElement: HTMLElement; rowContainer: HTMLElement; @@ -185,7 +183,7 @@ export class MockTerminal implements ITerminal { export class MockInputHandlingTerminal implements IInputHandlingTerminal { element: HTMLElement; - options: IInternalTerminalOptions = {}; + options: ITerminalOptions = {}; cols: number; rows: number; charset: { [key: string]: string; }; @@ -437,29 +435,3 @@ export class MockCharSizeService implements ICharSizeService { constructor(public width: number, public height: number) {} measure(): void {} } - -export class MockOptionsService implements IOptionsService { - options: ITerminalOptions = clone(DEFAULT_OPTIONS); - onOptionChange: IEvent; - constructor(testOptions: IPartialTerminalOptions) { - Object.keys(testOptions).forEach(key => this.options[key] = (testOptions)[key]); - } - setOption(key: string, value: T): void { - throw new Error('Method not implemented.'); - } - getOption(key: string): T { - throw new Error('Method not implemented.'); - } -} - -export class MockBufferService implements IBufferService { - constructor( - public cols: number, - public rows: number - ) {} - resize(cols: number, rows: number): void { - this.cols = cols; - this.rows = rows; - } - -} diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts new file mode 100644 index 00000000..0a0cb6a4 --- /dev/null +++ b/src/common/TestUtils.test.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IBufferService, IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services'; +import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; +import { clone } from 'common/Clone'; +import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; + +export class MockBufferService implements IBufferService { + constructor( + public cols: number, + public rows: number + ) {} + resize(cols: number, rows: number): void { + this.cols = cols; + this.rows = rows; + } +} + +export class MockOptionsService implements IOptionsService { + options: ITerminalOptions = clone(DEFAULT_OPTIONS); + onOptionChange: IEvent = new EventEmitter2().event; + constructor(testOptions: IPartialTerminalOptions) { + Object.keys(testOptions).forEach(key => this.options[key] = (testOptions)[key]); + } + setOption(key: string, value: T): void { + throw new Error('Method not implemented.'); + } + getOption(key: string): T { + throw new Error('Method not implemented.'); + } +} diff --git a/src/common/buffer/Buffer.test.ts b/src/common/buffer/Buffer.test.ts new file mode 100644 index 00000000..01516b3c --- /dev/null +++ b/src/common/buffer/Buffer.test.ts @@ -0,0 +1,1398 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { Buffer } from 'common/buffer/Buffer'; +import { CircularList } from 'common/CircularList'; +import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; +import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; + +const INIT_COLS = 80; +const INIT_ROWS = 24; +const INIT_SCROLLBACK = 1000; + +describe('Buffer', () => { + let optionsService: MockOptionsService; + let bufferService: MockBufferService; + let buffer: Buffer; + + beforeEach(() => { + optionsService = new MockOptionsService({ scrollback: INIT_SCROLLBACK }); + bufferService = new MockBufferService(INIT_COLS, INIT_ROWS); + buffer = new Buffer(true, optionsService, bufferService); + }); + + describe('constructor', () => { + it('should create a CircularList with max length equal to rows + scrollback, for its lines', () => { + assert.instanceOf(buffer.lines, CircularList); + assert.equal(buffer.lines.maxLength, bufferService.rows + INIT_SCROLLBACK); + }); + it('should set the Buffer\'s scrollBottom value equal to the terminal\'s rows -1', () => { + assert.equal(buffer.scrollBottom, bufferService.rows - 1); + }); + }); + + describe('fillViewportRows', () => { + it('should fill the buffer with blank lines based on the size of the viewport', () => { + const blankLineChar = buffer.getBlankLine(DEFAULT_ATTR_DATA).loadCell(0, new CellData()).getAsCharData(); + 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)!.loadCell(x, new CellData()).getAsCharData(), blankLineChar); + } + } + }); + }); + + describe('getWrappedRangeForLine', () => { + describe('non-wrapped', () => { + it('should return a single row for the first row', () => { + buffer.fillViewportRows(); + assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 0 }); + }); + it('should return a single row for a middle row', () => { + buffer.fillViewportRows(); + assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 12 }); + }); + it('should return a single row for the last row', () => { + buffer.fillViewportRows(); + assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 23, last: 23 }); + }); + }); + describe('wrapped', () => { + it('should return a range for the first row', () => { + buffer.fillViewportRows(); + buffer.lines.get(1)!.isWrapped = true; + assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 1 }); + }); + it('should return a range for a middle row wrapping upwards', () => { + buffer.fillViewportRows(); + buffer.lines.get(12)!.isWrapped = true; + assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 11, last: 12 }); + }); + it('should return a range for a middle row wrapping downwards', () => { + buffer.fillViewportRows(); + buffer.lines.get(13)!.isWrapped = true; + assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 13 }); + }); + it('should return a range for a middle row wrapping both ways', () => { + buffer.fillViewportRows(); + buffer.lines.get(11)!.isWrapped = true; + buffer.lines.get(12)!.isWrapped = true; + buffer.lines.get(13)!.isWrapped = true; + buffer.lines.get(14)!.isWrapped = true; + assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 10, last: 14 }); + }); + it('should return a range for the last row', () => { + buffer.fillViewportRows(); + buffer.lines.get(23)!.isWrapped = true; + assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 22, last: 23 }); + }); + it('should return a range for a row that wraps upward to first row', () => { + buffer.fillViewportRows(); + buffer.lines.get(1)!.isWrapped = true; + assert.deepEqual(buffer.getWrappedRangeForLine(1), { first: 0, last: 1 }); + }); + it('should return a range for a row that wraps downward to last row', () => { + buffer.fillViewportRows(); + buffer.lines.get(buffer.lines.length - 1)!.isWrapped = true; + assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 2), { first: 22, last: 23 }); + }); + }); + }); + + describe('resize', () => { + describe('column size is reduced', () => { + it('should trim the data in the buffer', () => { + buffer.fillViewportRows(); + buffer.resize(INIT_COLS / 2, INIT_ROWS); + assert.equal(buffer.lines.length, INIT_ROWS); + for (let i = 0; i < INIT_ROWS; i++) { + assert.equal(buffer.lines.get(i)!.length, INIT_COLS / 2); + } + }); + }); + + describe('column size is increased', () => { + it('should add pad columns', () => { + buffer.fillViewportRows(); + buffer.resize(INIT_COLS + 10, INIT_ROWS); + assert.equal(buffer.lines.length, INIT_ROWS); + for (let i = 0; i < INIT_ROWS; i++) { + assert.equal(buffer.lines.get(i)!.length, INIT_COLS + 10); + } + }); + }); + + describe('row size reduced', () => { + it('should trim blank lines from the end', () => { + buffer.fillViewportRows(); + buffer.resize(INIT_COLS, INIT_ROWS - 10); + assert.equal(buffer.lines.length, INIT_ROWS - 10); + }); + + it('should move the viewport down when it\'s at the end', () => { + buffer.fillViewportRows(); + // Set cursor y to have 5 blank lines below it + buffer.y = INIT_ROWS - 5 - 1; + buffer.resize(INIT_COLS, INIT_ROWS - 10); + // Trim 5 rows + assert.equal(buffer.lines.length, INIT_ROWS - 5); + // Shift the viewport down 5 rows + assert.equal(buffer.ydisp, 5); + assert.equal(buffer.ybase, 5); + }); + + describe('no scrollback', () => { + it('should trim from the top of the buffer when the cursor reaches the bottom', () => { + buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); + assert.equal(buffer.lines.maxLength, INIT_ROWS); + buffer.y = INIT_ROWS - 1; + buffer.fillViewportRows(); + let chData = buffer.lines.get(5)!.loadCell(0, new CellData()).getAsCharData(); + chData[1] = 'a'; + buffer.lines.get(5)!.setCell(0, CellData.fromCharData(chData)); + chData = buffer.lines.get(INIT_ROWS - 1)!.loadCell(0, new CellData()).getAsCharData(); + chData[1] = 'b'; + buffer.lines.get(INIT_ROWS - 1)!.setCell(0, CellData.fromCharData(chData)); + buffer.resize(INIT_COLS, INIT_ROWS - 5); + assert.equal(buffer.lines.get(0)!.loadCell(0, new CellData()).getAsCharData()[1], 'a'); + assert.equal(buffer.lines.get(INIT_ROWS - 1 - 5)!.loadCell(0, new CellData()).getAsCharData()[1], 'b'); + }); + }); + }); + + describe('row size increased', () => { + describe('empty buffer', () => { + it('should add blank lines to end', () => { + buffer.fillViewportRows(); + assert.equal(buffer.ydisp, 0); + buffer.resize(INIT_COLS, INIT_ROWS + 10); + assert.equal(buffer.ydisp, 0); + assert.equal(buffer.lines.length, INIT_ROWS + 10); + }); + }); + + describe('filled buffer', () => { + it('should show more of the buffer above', () => { + buffer.fillViewportRows(); + // Create 10 extra blank lines + for (let i = 0; i < 10; i++) { + buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA)); + } + // Set cursor to the bottom of the buffer + buffer.y = INIT_ROWS - 1; + // Scroll down 10 lines + buffer.ybase = 10; + buffer.ydisp = 10; + assert.equal(buffer.lines.length, INIT_ROWS + 10); + buffer.resize(INIT_COLS, INIT_ROWS + 5); + // Should be should 5 more lines + assert.equal(buffer.ydisp, 5); + assert.equal(buffer.ybase, 5); + // Should not trim the buffer + assert.equal(buffer.lines.length, INIT_ROWS + 10); + }); + + it('should show more of the buffer below when the viewport is at the top of the buffer', () => { + buffer.fillViewportRows(); + // Create 10 extra blank lines + for (let i = 0; i < 10; i++) { + buffer.lines.push(buffer.getBlankLine(DEFAULT_ATTR_DATA)); + } + // Set cursor to the bottom of the buffer + buffer.y = INIT_ROWS - 1; + // Scroll down 10 lines + buffer.ybase = 10; + buffer.ydisp = 0; + assert.equal(buffer.lines.length, INIT_ROWS + 10); + buffer.resize(INIT_COLS, INIT_ROWS + 5); + // The viewport should remain at the top + assert.equal(buffer.ydisp, 0); + // The buffer ybase should move up 5 lines + assert.equal(buffer.ybase, 5); + // Should not trim the buffer + assert.equal(buffer.lines.length, INIT_ROWS + 10); + }); + }); + }); + + describe('row and column increased', () => { + it('should resize properly', () => { + buffer.fillViewportRows(); + buffer.resize(INIT_COLS + 5, INIT_ROWS + 5); + assert.equal(buffer.lines.length, INIT_ROWS + 5); + for (let i = 0; i < INIT_ROWS + 5; i++) { + assert.equal(buffer.lines.get(i)!.length, INIT_COLS + 5); + } + }); + }); + + describe('reflow', () => { + it('should not wrap empty lines', () => { + buffer.fillViewportRows(); + assert.equal(buffer.lines.length, INIT_ROWS); + buffer.resize(INIT_COLS - 5, INIT_ROWS); + assert.equal(buffer.lines.length, INIT_ROWS); + }); + it('should shrink row length', () => { + buffer.fillViewportRows(); + buffer.resize(5, 10); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.length, 5); + assert.equal(buffer.lines.get(1)!.length, 5); + assert.equal(buffer.lines.get(2)!.length, 5); + assert.equal(buffer.lines.get(3)!.length, 5); + assert.equal(buffer.lines.get(4)!.length, 5); + assert.equal(buffer.lines.get(5)!.length, 5); + assert.equal(buffer.lines.get(6)!.length, 5); + assert.equal(buffer.lines.get(7)!.length, 5); + assert.equal(buffer.lines.get(8)!.length, 5); + assert.equal(buffer.lines.get(9)!.length, 5); + }); + it('should wrap and unwrap lines', () => { + buffer.fillViewportRows(); + buffer.resize(5, 10); + const firstLine = buffer.lines.get(0)!; + for (let i = 0; i < 5; i++) { + const code = 'a'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + firstLine.set(i, [0, char, 1, code]); + } + buffer.y = 1; + assert.equal(buffer.lines.get(0)!.length, 5); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcde'); + buffer.resize(1, 10); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(), 'a'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'b'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'c'); + assert.equal(buffer.lines.get(3)!.translateToString(), 'd'); + assert.equal(buffer.lines.get(4)!.translateToString(), 'e'); + assert.equal(buffer.lines.get(5)!.translateToString(), ' '); + assert.equal(buffer.lines.get(6)!.translateToString(), ' '); + assert.equal(buffer.lines.get(7)!.translateToString(), ' '); + assert.equal(buffer.lines.get(8)!.translateToString(), ' '); + assert.equal(buffer.lines.get(9)!.translateToString(), ' '); + buffer.resize(5, 10); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcde'); + assert.equal(buffer.lines.get(1)!.translateToString(), ' '); + assert.equal(buffer.lines.get(2)!.translateToString(), ' '); + assert.equal(buffer.lines.get(3)!.translateToString(), ' '); + assert.equal(buffer.lines.get(4)!.translateToString(), ' '); + assert.equal(buffer.lines.get(5)!.translateToString(), ' '); + assert.equal(buffer.lines.get(6)!.translateToString(), ' '); + assert.equal(buffer.lines.get(7)!.translateToString(), ' '); + assert.equal(buffer.lines.get(8)!.translateToString(), ' '); + assert.equal(buffer.lines.get(9)!.translateToString(), ' '); + }); + it('should discard parts of wrapped lines that go out of the scrollback', () => { + buffer.fillViewportRows(); + optionsService.options.scrollback = 1; + buffer.resize(10, 5); + const lastLine = buffer.lines.get(3)!; + for (let i = 0; i < 10; i++) { + const code = 'a'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + lastLine.set(i, [0, char, 1, code]); + } + assert.equal(buffer.lines.length, 5); + buffer.y = 4; + buffer.resize(2, 5); + assert.equal(buffer.y, 4); + assert.equal(buffer.ybase, 1); + assert.equal(buffer.lines.length, 6); + assert.equal(buffer.lines.get(0)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(3)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(4)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(5)!.translateToString(), ' '); + buffer.resize(1, 5); + assert.equal(buffer.y, 4); + assert.equal(buffer.ybase, 1); + assert.equal(buffer.lines.length, 6); + assert.equal(buffer.lines.get(0)!.translateToString(), 'f'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'g'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'h'); + assert.equal(buffer.lines.get(3)!.translateToString(), 'i'); + assert.equal(buffer.lines.get(4)!.translateToString(), 'j'); + assert.equal(buffer.lines.get(5)!.translateToString(), ' '); + buffer.resize(10, 5); + assert.equal(buffer.y, 1); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 5); + assert.equal(buffer.lines.get(0)!.translateToString(), 'fghij '); + assert.equal(buffer.lines.get(1)!.translateToString(), ' '); + assert.equal(buffer.lines.get(2)!.translateToString(), ' '); + assert.equal(buffer.lines.get(3)!.translateToString(), ' '); + assert.equal(buffer.lines.get(4)!.translateToString(), ' '); + }); + it('should remove the correct amount of rows when reflowing larger', () => { + // This is a regression test to ensure that successive wrapped lines that are getting + // 3+ lines removed on a reflow actually remove the right lines + buffer.fillViewportRows(); + buffer.resize(10, 10); + buffer.y = 2; + const firstLine = buffer.lines.get(0)!; + const secondLine = buffer.lines.get(1)!; + for (let i = 0; i < 10; i++) { + const code = 'a'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + firstLine.set(i, [0, char, 1, code]); + } + for (let i = 0; i < 10; i++) { + const code = '0'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + secondLine.set(i, [0, char, 1, code]); + } + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij'); + assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789'); + for (let i = 2; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + buffer.resize(2, 10); + assert.equal(buffer.ybase, 1); + assert.equal(buffer.lines.length, 11); + assert.equal(buffer.lines.get(0)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(3)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(4)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(5)!.translateToString(), '01'); + assert.equal(buffer.lines.get(6)!.translateToString(), '23'); + assert.equal(buffer.lines.get(7)!.translateToString(), '45'); + assert.equal(buffer.lines.get(8)!.translateToString(), '67'); + assert.equal(buffer.lines.get(9)!.translateToString(), '89'); + assert.equal(buffer.lines.get(10)!.translateToString(), ' '); + buffer.resize(10, 10); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij'); + assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789'); + for (let i = 2; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + }); + it('should transfer combined char data over to reflowed lines', () => { + buffer.fillViewportRows(); + buffer.resize(4, 3); + buffer.y = 2; + const firstLine = buffer.lines.get(0)!; + firstLine.set(0, [ 0, 'a', 1, 'a'.charCodeAt(0) ]); + firstLine.set(1, [ 0, 'b', 1, 'b'.charCodeAt(0) ]); + firstLine.set(2, [ 0, 'c', 1, 'c'.charCodeAt(0) ]); + firstLine.set(3, [ 0, '😁', 1, '😁'.charCodeAt(0) ]); + assert.equal(buffer.lines.length, 3); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abc😁'); + assert.equal(buffer.lines.get(1)!.translateToString(), ' '); + buffer.resize(2, 3); + assert.equal(buffer.lines.get(0)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'c😁'); + }); + it('should adjust markers when reflowing', () => { + buffer.fillViewportRows(); + buffer.resize(10, 16); + for (let i = 0; i < 10; i++) { + const code = 'a'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + buffer.lines.get(0)!.set(i, [0, char, 1, code]); + } + for (let i = 0; i < 10; i++) { + const code = '0'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + buffer.lines.get(1)!.set(i, [0, char, 1, code]); + } + for (let i = 0; i < 10; i++) { + const code = 'k'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + buffer.lines.get(2)!.set(i, [0, char, 1, code]); + } + buffer.y = 3; + // Buffer: + // abcdefghij + // 0123456789 + // abcdefghij + const firstMarker = buffer.addMarker(0); + const secondMarker = buffer.addMarker(1); + const thirdMarker = buffer.addMarker(2); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij'); + assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'klmnopqrst'); + assert.equal(firstMarker.line, 0); + assert.equal(secondMarker.line, 1); + assert.equal(thirdMarker.line, 2); + buffer.resize(2, 16); + assert.equal(buffer.lines.get(0)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(3)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(4)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(5)!.translateToString(), '01'); + assert.equal(buffer.lines.get(6)!.translateToString(), '23'); + assert.equal(buffer.lines.get(7)!.translateToString(), '45'); + assert.equal(buffer.lines.get(8)!.translateToString(), '67'); + assert.equal(buffer.lines.get(9)!.translateToString(), '89'); + assert.equal(buffer.lines.get(10)!.translateToString(), 'kl'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'mn'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'op'); + assert.equal(buffer.lines.get(13)!.translateToString(), 'qr'); + assert.equal(buffer.lines.get(14)!.translateToString(), 'st'); + assert.equal(firstMarker.line, 0, 'first marker should remain unchanged'); + assert.equal(secondMarker.line, 5, 'second marker should be shifted since the first line wrapped'); + assert.equal(thirdMarker.line, 10, 'third marker should be shifted since the first and second lines wrapped'); + buffer.resize(10, 16); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij'); + assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'klmnopqrst'); + assert.equal(firstMarker.line, 0, 'first marker should remain unchanged'); + assert.equal(secondMarker.line, 1, 'second marker should be restored to it\'s original line'); + assert.equal(thirdMarker.line, 2, 'third marker should be restored to it\'s original line'); + assert.equal(firstMarker.isDisposed, false); + assert.equal(secondMarker.isDisposed, false); + assert.equal(thirdMarker.isDisposed, false); + }); + it('should dispose markers whose rows are trimmed during a reflow', () => { + buffer.fillViewportRows(); + optionsService.options.scrollback = 1; + buffer.resize(10, 11); + for (let i = 0; i < 10; i++) { + const code = 'a'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + buffer.lines.get(0)!.set(i, [0, char, 1, code]); + } + for (let i = 0; i < 10; i++) { + const code = '0'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + buffer.lines.get(1)!.set(i, [0, char, 1, code]); + } + for (let i = 0; i < 10; i++) { + const code = 'k'.charCodeAt(0) + i; + const char = String.fromCharCode(code); + buffer.lines.get(2)!.set(i, [0, char, 1, code]); + } + buffer.y = 10; + // Buffer: + // abcdefghij + // 0123456789 + // abcdefghij + const firstMarker = buffer.addMarker(0); + const secondMarker = buffer.addMarker(1); + const thirdMarker = buffer.addMarker(2); + buffer.y = 3; + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcdefghij'); + assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'klmnopqrst'); + assert.equal(firstMarker.line, 0); + assert.equal(secondMarker.line, 1); + assert.equal(thirdMarker.line, 2); + buffer.resize(2, 11); + assert.equal(buffer.lines.get(0)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(1)!.translateToString(), '01'); + assert.equal(buffer.lines.get(2)!.translateToString(), '23'); + assert.equal(buffer.lines.get(3)!.translateToString(), '45'); + assert.equal(buffer.lines.get(4)!.translateToString(), '67'); + assert.equal(buffer.lines.get(5)!.translateToString(), '89'); + assert.equal(buffer.lines.get(6)!.translateToString(), 'kl'); + assert.equal(buffer.lines.get(7)!.translateToString(), 'mn'); + assert.equal(buffer.lines.get(8)!.translateToString(), 'op'); + assert.equal(buffer.lines.get(9)!.translateToString(), 'qr'); + assert.equal(buffer.lines.get(10)!.translateToString(), 'st'); + assert.equal(secondMarker.line, 1, 'second marker should remain the same as it was shifted 4 and trimmed 4'); + assert.equal(thirdMarker.line, 6, 'third marker should be shifted since the first and second lines wrapped'); + assert.equal(firstMarker.isDisposed, true, 'first marker was trimmed'); + assert.equal(secondMarker.isDisposed, false); + assert.equal(thirdMarker.isDisposed, false); + buffer.resize(10, 11); + assert.equal(buffer.lines.get(0)!.translateToString(), 'ij '); + assert.equal(buffer.lines.get(1)!.translateToString(), '0123456789'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'klmnopqrst'); + assert.equal(secondMarker.line, 1, 'second marker should be restored'); + assert.equal(thirdMarker.line, 2, 'third marker should be restored'); + }); + it('should correctly reflow wrapped lines that end in 0 space (via tab char)', () => { + buffer.fillViewportRows(); + buffer.resize(4, 10); + buffer.y = 2; + buffer.lines.get(0)!.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]); + buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]); + buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]); + buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]); + buffer.lines.get(1)!.isWrapped = true; + // Buffer: + // "ab " (wrapped) + // "cd" + buffer.resize(5, 10); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), 'ab c'); + assert.equal(buffer.lines.get(1)!.translateToString(false), 'd '); + buffer.resize(6, 10); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), 'ab cd'); + assert.equal(buffer.lines.get(1)!.translateToString(false), ' '); + }); + it('should wrap wide characters correctly when reflowing larger', () => { + buffer.fillViewportRows(); + buffer.resize(12, 10); + buffer.y = 2; + for (let i = 0; i < 12; i += 4) { + buffer.lines.get(0)!.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]); + buffer.lines.get(1)!.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]); + } + for (let i = 2; i < 12; i += 4) { + buffer.lines.get(0)!.set(i, [0, '语', 2, '语'.charCodeAt(0)]); + buffer.lines.get(1)!.set(i, [0, '语', 2, '语'.charCodeAt(0)]); + } + for (let i = 1; i < 12; i += 2) { + buffer.lines.get(0)!.set(i, [0, '', 0, 0]); + buffer.lines.get(1)!.set(i, [0, '', 0, 0]); + } + buffer.lines.get(1)!.isWrapped = true; + // Buffer: + // 汉语汉语汉语 (wrapped) + // 汉语汉语汉语 + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉语'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语汉语'); + buffer.resize(13, 10); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉语'); + assert.equal(buffer.lines.get(0)!.translateToString(false), '汉语汉语汉语 '); + assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语汉语'); + assert.equal(buffer.lines.get(1)!.translateToString(false), '汉语汉语汉语 '); + buffer.resize(14, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉语汉'); + assert.equal(buffer.lines.get(0)!.translateToString(false), '汉语汉语汉语汉'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语汉语'); + assert.equal(buffer.lines.get(1)!.translateToString(false), '语汉语汉语 '); + }); + it('should correctly reflow wrapped lines that end in 0 space (via tab char)', () => { + buffer.fillViewportRows(); + buffer.resize(4, 10); + buffer.y = 2; + buffer.lines.get(0)!.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]); + buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]); + buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]); + buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]); + buffer.lines.get(1)!.isWrapped = true; + // Buffer: + // "ab " (wrapped) + // "cd" + buffer.resize(3, 10); + assert.equal(buffer.y, 2); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(false), 'ab '); + assert.equal(buffer.lines.get(1)!.translateToString(false), ' cd'); + buffer.resize(2, 10); + assert.equal(buffer.y, 3); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(false), 'ab'); + assert.equal(buffer.lines.get(1)!.translateToString(false), ' '); + assert.equal(buffer.lines.get(2)!.translateToString(false), 'cd'); + }); + it('should wrap wide characters correctly when reflowing smaller', () => { + buffer.fillViewportRows(); + buffer.resize(12, 10); + buffer.y = 2; + for (let i = 0; i < 12; i += 4) { + buffer.lines.get(0)!.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]); + buffer.lines.get(1)!.set(i, [0, '汉', 2, '汉'.charCodeAt(0)]); + } + for (let i = 2; i < 12; i += 4) { + buffer.lines.get(0)!.set(i, [0, '语', 2, '语'.charCodeAt(0)]); + buffer.lines.get(1)!.set(i, [0, '语', 2, '语'.charCodeAt(0)]); + } + for (let i = 1; i < 12; i += 2) { + buffer.lines.get(0)!.set(i, [0, '', 0, 0]); + buffer.lines.get(1)!.set(i, [0, '', 0, 0]); + } + buffer.lines.get(1)!.isWrapped = true; + // Buffer: + // 汉语汉语汉语 (wrapped) + // 汉语汉语汉语 + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉语'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语汉语'); + buffer.resize(11, 10); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语汉语'); + assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语'); + buffer.resize(10, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语汉'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语汉语'); + assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语'); + buffer.resize(9, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语'); + assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语汉语'); + buffer.resize(8, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉语'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '汉语汉语'); + assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语汉语'); + buffer.resize(7, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语'); + assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语汉'); + assert.equal(buffer.lines.get(3)!.translateToString(true), '语汉语'); + buffer.resize(6, 10); + assert.equal(buffer.lines.get(0)!.translateToString(true), '汉语汉'); + assert.equal(buffer.lines.get(1)!.translateToString(true), '语汉语'); + assert.equal(buffer.lines.get(2)!.translateToString(true), '汉语汉'); + assert.equal(buffer.lines.get(3)!.translateToString(true), '语汉语'); + }); + + describe('reflowLarger cases', () => { + beforeEach(() => { + // Setup buffer state: + // 'ab' + // 'cd' (wrapped) + // 'ef' + // 'gh' (wrapped) + // 'ij' + // 'kl' (wrapped) + // ' ' + // ' ' + // ' ' + // ' ' + buffer.fillViewportRows(); + buffer.resize(2, 10); + buffer.lines.get(0)!.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]); + buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]); + buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]); + buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]); + buffer.lines.get(1)!.isWrapped = true; + buffer.lines.get(2)!.set(0, [0, 'e', 1, 'e'.charCodeAt(0)]); + buffer.lines.get(2)!.set(1, [0, 'f', 1, 'f'.charCodeAt(0)]); + buffer.lines.get(3)!.set(0, [0, 'g', 1, 'g'.charCodeAt(0)]); + buffer.lines.get(3)!.set(1, [0, 'h', 1, 'h'.charCodeAt(0)]); + buffer.lines.get(3)!.isWrapped = true; + buffer.lines.get(4)!.set(0, [0, 'i', 1, 'i'.charCodeAt(0)]); + buffer.lines.get(4)!.set(1, [0, 'j', 1, 'j'.charCodeAt(0)]); + buffer.lines.get(5)!.set(0, [0, 'k', 1, 'k'.charCodeAt(0)]); + buffer.lines.get(5)!.set(1, [0, 'l', 1, 'l'.charCodeAt(0)]); + buffer.lines.get(5)!.isWrapped = true; + }); + describe('viewport not yet filled', () => { + it('should move the cursor up and add empty lines', () => { + buffer.y = 6; + buffer.resize(4, 10); + assert.equal(buffer.y, 3); + assert.equal(buffer.ydisp, 0); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcd'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'efgh'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'ijkl'); + for (let i = 3; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines: number[] = []; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + describe('viewport filled, scrollback remaining', () => { + beforeEach(() => { + buffer.y = 9; + }); + describe('ybase === 0', () => { + it('should move the cursor up and add empty lines', () => { + buffer.resize(4, 10); + assert.equal(buffer.y, 6); + assert.equal(buffer.ydisp, 0); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(), 'abcd'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'efgh'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'ijkl'); + for (let i = 3; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines: number[] = []; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + describe('ybase !== 0', () => { + beforeEach(() => { + // Add 10 empty rows to start + for (let i = 0; i < 10; i++) { + buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); + } + buffer.ybase = 10; + }); + describe('&& ydisp === ybase', () => { + it('should adjust the viewport and keep ydisp = ybase', () => { + buffer.ydisp = 10; + buffer.resize(4, 10); + assert.equal(buffer.y, 9); + assert.equal(buffer.ydisp, 7); + assert.equal(buffer.ybase, 7); + assert.equal(buffer.lines.length, 17); + for (let i = 0; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + assert.equal(buffer.lines.get(10)!.translateToString(), 'abcd'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'efgh'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'ijkl'); + for (let i = 13; i < 17; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines: number[] = []; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + describe('&& ydisp !== ybase', () => { + it('should keep ydisp at the same value', () => { + buffer.ydisp = 5; + buffer.resize(4, 10); + assert.equal(buffer.y, 9); + assert.equal(buffer.ydisp, 5); + assert.equal(buffer.ybase, 7); + assert.equal(buffer.lines.length, 17); + for (let i = 0; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + assert.equal(buffer.lines.get(10)!.translateToString(), 'abcd'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'efgh'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'ijkl'); + for (let i = 13; i < 17; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines: number[] = []; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + }); + }); + describe('viewport filled, no scrollback remaining', () => { + // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported + describe('ybase !== 0', () => { + beforeEach(() => { + optionsService.options.scrollback = 10; + // Add 10 empty rows to start + for (let i = 0; i < 10; i++) { + buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); + } + buffer.y = 9; + buffer.ybase = 10; + }); + describe('&& ydisp === ybase', () => { + it('should trim lines and keep ydisp = ybase', () => { + buffer.ydisp = 10; + buffer.resize(4, 10); + assert.equal(buffer.y, 9); + assert.equal(buffer.ydisp, 7); + assert.equal(buffer.ybase, 7); + assert.equal(buffer.lines.length, 17); + for (let i = 0; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + assert.equal(buffer.lines.get(10)!.translateToString(), 'abcd'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'efgh'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'ijkl'); + for (let i = 13; i < 17; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines: number[] = []; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + describe('&& ydisp !== ybase', () => { + it('should trim lines and not change ydisp', () => { + buffer.ydisp = 5; + buffer.resize(4, 10); + assert.equal(buffer.y, 9); + assert.equal(buffer.ydisp, 5); + assert.equal(buffer.ybase, 7); + assert.equal(buffer.lines.length, 17); + for (let i = 0; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + assert.equal(buffer.lines.get(10)!.translateToString(), 'abcd'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'efgh'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'ijkl'); + for (let i = 13; i < 17; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines: number[] = []; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + }); + }); + }); + describe('reflowSmaller cases', () => { + beforeEach(() => { + // Setup buffer state: + // 'abcd' + // 'efgh' (wrapped) + // 'ijkl' + // ' ' + // ' ' + // ' ' + // ' ' + // ' ' + // ' ' + // ' ' + buffer.fillViewportRows(); + buffer.resize(4, 10); + buffer.lines.get(0)!.set(0, [0, 'a', 1, 'a'.charCodeAt(0)]); + buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]); + buffer.lines.get(0)!.set(2, [0, 'c', 1, 'c'.charCodeAt(0)]); + buffer.lines.get(0)!.set(3, [0, 'd', 1, 'd'.charCodeAt(0)]); + buffer.lines.get(1)!.set(0, [0, 'e', 1, 'e'.charCodeAt(0)]); + buffer.lines.get(1)!.set(1, [0, 'f', 1, 'f'.charCodeAt(0)]); + buffer.lines.get(1)!.set(2, [0, 'g', 1, 'g'.charCodeAt(0)]); + buffer.lines.get(1)!.set(3, [0, 'h', 1, 'h'.charCodeAt(0)]); + buffer.lines.get(2)!.set(0, [0, 'i', 1, 'i'.charCodeAt(0)]); + buffer.lines.get(2)!.set(1, [0, 'j', 1, 'j'.charCodeAt(0)]); + buffer.lines.get(2)!.set(2, [0, 'k', 1, 'k'.charCodeAt(0)]); + buffer.lines.get(2)!.set(3, [0, 'l', 1, 'l'.charCodeAt(0)]); + }); + describe('viewport not yet filled', () => { + it('should move the cursor down', () => { + buffer.y = 3; + buffer.resize(2, 10); + assert.equal(buffer.y, 6); + assert.equal(buffer.ydisp, 0); + assert.equal(buffer.ybase, 0); + assert.equal(buffer.lines.length, 10); + assert.equal(buffer.lines.get(0)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(3)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(4)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(5)!.translateToString(), 'kl'); + for (let i = 6; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines = [1, 3, 5]; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + describe('viewport filled, scrollback remaining', () => { + beforeEach(() => { + buffer.y = 9; + }); + describe('ybase === 0', () => { + it('should trim the top', () => { + buffer.resize(2, 10); + assert.equal(buffer.y, 9); + assert.equal(buffer.ydisp, 3); + assert.equal(buffer.ybase, 3); + assert.equal(buffer.lines.length, 13); + assert.equal(buffer.lines.get(0)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(1)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(2)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(3)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(4)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(5)!.translateToString(), 'kl'); + for (let i = 6; i < 13; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines = [1, 3, 5]; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + describe('ybase !== 0', () => { + beforeEach(() => { + // Add 10 empty rows to start + for (let i = 0; i < 10; i++) { + buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); + } + buffer.ybase = 10; + }); + describe('&& ydisp === ybase', () => { + it('should adjust the viewport and keep ydisp = ybase', () => { + buffer.ydisp = 10; + buffer.resize(2, 10); + assert.equal(buffer.ydisp, 13); + assert.equal(buffer.ybase, 13); + assert.equal(buffer.lines.length, 23); + for (let i = 0; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + assert.equal(buffer.lines.get(10)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(13)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(14)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(15)!.translateToString(), 'kl'); + for (let i = 16; i < 23; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines = [11, 13, 15]; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + describe('&& ydisp !== ybase', () => { + it('should keep ydisp at the same value', () => { + buffer.ydisp = 5; + buffer.resize(2, 10); + assert.equal(buffer.ydisp, 5); + assert.equal(buffer.ybase, 13); + assert.equal(buffer.lines.length, 23); + for (let i = 0; i < 10; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + assert.equal(buffer.lines.get(10)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(13)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(14)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(15)!.translateToString(), 'kl'); + for (let i = 16; i < 23; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines = [11, 13, 15]; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + }); + }); + describe('viewport filled, no scrollback remaining', () => { + // ybase === 0 doesn't make sense here as scrollback=0 isn't really supported + describe('ybase !== 0', () => { + beforeEach(() => { + optionsService.options.scrollback = 10; + // Add 10 empty rows to start + for (let i = 0; i < 10; i++) { + buffer.lines.splice(0, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA)); + } + buffer.ybase = 10; + }); + describe('&& ydisp === ybase', () => { + it('should trim lines and keep ydisp = ybase', () => { + buffer.ydisp = 10; + buffer.y = 13; + buffer.resize(2, 10); + assert.equal(buffer.ydisp, 10); + assert.equal(buffer.ybase, 10); + assert.equal(buffer.lines.length, 20); + for (let i = 0; i < 7; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + assert.equal(buffer.lines.get(7)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(8)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(9)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(10)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'kl'); + for (let i = 13; i < 20; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines = [8, 10, 12]; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + describe('&& ydisp !== ybase', () => { + it('should trim lines and not change ydisp', () => { + buffer.ydisp = 5; + buffer.y = 13; + buffer.resize(2, 10); + assert.equal(buffer.ydisp, 5); + assert.equal(buffer.ybase, 10); + assert.equal(buffer.lines.length, 20); + for (let i = 0; i < 7; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + assert.equal(buffer.lines.get(7)!.translateToString(), 'ab'); + assert.equal(buffer.lines.get(8)!.translateToString(), 'cd'); + assert.equal(buffer.lines.get(9)!.translateToString(), 'ef'); + assert.equal(buffer.lines.get(10)!.translateToString(), 'gh'); + assert.equal(buffer.lines.get(11)!.translateToString(), 'ij'); + assert.equal(buffer.lines.get(12)!.translateToString(), 'kl'); + for (let i = 13; i < 20; i++) { + assert.equal(buffer.lines.get(i)!.translateToString(), ' '); + } + const wrappedLines = [8, 10, 12]; + for (let i = 0; i < buffer.lines.length; i++) { + assert.equal(buffer.lines.get(i)!.isWrapped, wrappedLines.indexOf(i) !== -1, `line ${i} isWrapped must equal ${wrappedLines.indexOf(i) !== -1}`); + } + }); + }); + }); + }); + }); + }); + }); + + describe('buffer marked to have no scrollback', () => { + it('should always have a scrollback of 0', () => { + // Test size on initialization + buffer = new Buffer(false, new MockOptionsService({ scrollback: 1000 }), bufferService); + buffer.fillViewportRows(); + assert.equal(buffer.lines.maxLength, INIT_ROWS); + // Test size on buffer increase + buffer.resize(INIT_COLS, INIT_ROWS * 2); + assert.equal(buffer.lines.maxLength, INIT_ROWS * 2); + // Test size on buffer decrease + buffer.resize(INIT_COLS, INIT_ROWS / 2); + assert.equal(buffer.lines.maxLength, INIT_ROWS / 2); + }); + }); + + describe('addMarker', () => { + it('should adjust a marker line when the buffer is trimmed', () => { + buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); + buffer.fillViewportRows(); + const marker = buffer.addMarker(buffer.lines.length - 1); + assert.equal(marker.line, buffer.lines.length - 1); + buffer.lines.onTrimEmitter.fire(1); + assert.equal(marker.line, buffer.lines.length - 2); + }); + it('should dispose of a marker if it is trimmed off the buffer', () => { + buffer = new Buffer(true, new MockOptionsService({ scrollback: 0 }), bufferService); + buffer.fillViewportRows(); + assert.equal(buffer.markers.length, 0); + const marker = buffer.addMarker(0); + assert.equal(marker.isDisposed, false); + assert.equal(buffer.markers.length, 1); + buffer.lines.onTrimEmitter.fire(1); + assert.equal(marker.isDisposed, true); + assert.equal(buffer.markers.length, 0); + }); + }); + + describe ('translateBufferLineToString', () => { + it('should handle selecting a section of ascii text', () => { + const line = new BufferLine(4); + line.setCell(0, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(1, CellData.fromCharData([ 0, 'b', 1, 'b'.charCodeAt(0)])); + line.setCell(2, CellData.fromCharData([ 0, 'c', 1, 'c'.charCodeAt(0)])); + line.setCell(3, CellData.fromCharData([ 0, 'd', 1, 'd'.charCodeAt(0)])); + 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', () => { + const line = new BufferLine(3); + line.setCell(0, CellData.fromCharData([ 0, '語', 2, 35486 ])); + line.setCell(1, CellData.fromCharData([ 0, '', 0, 0])); + line.setCell(2, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + 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', () => { + const line = new BufferLine(3); + line.setCell(0, CellData.fromCharData([ 0, '語', 2, '語'.charCodeAt(0) ])); + line.setCell(1, CellData.fromCharData([ 0, '', 0, 0])); + line.setCell(2, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + buffer.lines.set(0, line); + + const str0 = buffer.translateBufferLineToString(0, true, 0, 1); + assert.equal(str0, '語'); + + const str1 = buffer.translateBufferLineToString(0, true, 0, 2); + assert.equal(str1, '語'); + + const str2 = buffer.translateBufferLineToString(0, true, 0, 3); + assert.equal(str2, '語a'); + }); + + it('should handle single width emojis', () => { + const line = new BufferLine(2); + line.setCell(0, CellData.fromCharData([ 0, '😁', 1, '😁'.charCodeAt(0) ])); + line.setCell(1, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + buffer.lines.set(0, line); + + const str1 = buffer.translateBufferLineToString(0, true, 0, 1); + assert.equal(str1, '😁'); + + const str2 = buffer.translateBufferLineToString(0, true, 0, 2); + assert.equal(str2, '😁a'); + }); + + it('should handle double width emojis', () => { + const line = new BufferLine(2); + line.setCell(0, CellData.fromCharData([ 0, '😁', 2, '😁'.charCodeAt(0) ])); + line.setCell(1, CellData.fromCharData([ 0, '', 0, 0])); + buffer.lines.set(0, line); + + const str1 = buffer.translateBufferLineToString(0, true, 0, 1); + assert.equal(str1, '😁'); + + const str2 = buffer.translateBufferLineToString(0, true, 0, 2); + assert.equal(str2, '😁'); + + const line2 = new BufferLine(3); + line2.setCell(0, CellData.fromCharData([ 0, '😁', 2, '😁'.charCodeAt(0) ])); + line2.setCell(1, CellData.fromCharData([ 0, '', 0, 0])); + line2.setCell(2, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + buffer.lines.set(0, line2); + + const str3 = buffer.translateBufferLineToString(0, true, 0, 3); + assert.equal(str3, '😁a'); + }); + }); + // describe('stringIndexToBufferIndex', () => { + // let terminal: TestTerminal; + + // beforeEach(() => { + // terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5}); + // }); + + // it('multiline ascii', () => { + // const input = 'This is ASCII text spanning multiple lines.'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // for (let i = 0; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); + // } + // }); + + // it('combining e\u0301 in a sentence', () => { + // const input = 'Sitting in the cafe\u0301 drinking coffee.'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // for (let i = 0; i < 19; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); + // } + // // string index 18 & 19 point to combining char e\u0301 ---> same buffer Index + // assert.deepEqual( + // terminal.buffer.stringIndexToBufferIndex(0, 18), + // terminal.buffer.stringIndexToBufferIndex(0, 19)); + // // after the combining char every string index has an offset of -1 + // for (let i = 19; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex); + // } + // }); + + // it('multiline combining e\u0301', () => { + // const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // // every buffer cell index contains 2 string indices + // for (let i = 0; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex); + // } + // }); + + // it('surrogate char in a sentence', () => { + // const input = 'The 𝄞 is a clef widely used in modern notation.'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // for (let i = 0; i < 5; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); + // } + // // string index 4 & 5 point to surrogate char 𝄞 ---> same buffer Index + // assert.deepEqual( + // terminal.buffer.stringIndexToBufferIndex(0, 4), + // terminal.buffer.stringIndexToBufferIndex(0, 5)); + // // after the combining char every string index has an offset of -1 + // for (let i = 5; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex); + // } + // }); + + // it('multiline surrogate char', () => { + // const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // // every buffer cell index contains 2 string indices + // for (let i = 0; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex); + // } + // }); + + // it('surrogate char with combining', () => { + // // eye of Ra with acute accent - string length of 3 + // const input = '𓂀\u0301 - the eye hiroglyph with an acute accent.'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // // index 0..2 should map to 0 + // assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 1)); + // assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 2)); + // for (let i = 2; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([((i - 2) / terminal.cols) | 0, (i - 2) % terminal.cols], bufferIndex); + // } + // }); + + // it('multiline surrogate with combining', () => { + // const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // // every buffer cell index contains 3 string indices + // for (let i = 0; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([(((i / 3) | 0) / terminal.cols) | 0, ((i / 3) | 0) % terminal.cols], bufferIndex); + // } + // }); + + // it('fullwidth chars', () => { + // const input = 'These 123 are some fat numbers.'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // for (let i = 0; i < 6; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); + // } + // // string index 6, 7, 8 take 2 cells + // assert.deepEqual([0, 8], terminal.buffer.stringIndexToBufferIndex(0, 7)); + // assert.deepEqual([1, 0], terminal.buffer.stringIndexToBufferIndex(0, 8)); + // // rest of the string has offset of +3 + // for (let i = 9; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([((i + 3) / terminal.cols) | 0, (i + 3) % terminal.cols], bufferIndex); + // } + // }); + + // it('multiline fullwidth chars', () => { + // const input = '12345678901234567890'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // for (let i = 9; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); + // assert.deepEqual([((i << 1) / terminal.cols) | 0, (i << 1) % terminal.cols], bufferIndex); + // } + // }); + + // it('fullwidth combining with emoji - match emoji cell', () => { + // const input = 'Lots of ¥\u0301 make me 😃.'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // const stringIndex = s.match(/😃/).index; + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex); + // assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars(), '😃'); + // }); + + // it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => { + // const input = 'a12345678901234567890'; + // // the 'a' at the beginning moves all fullwidth chars one to the right + // // now the end of the line contains a dangling empty cell since + // // the next fullwidth char has to wrap early + // // the dangling last cell is wrongly added in the string + // // --> fixable after resolving #1685 + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // for (let i = 10; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); + // const j = (i - 0) << 1; + // assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex); + // } + // }); + + // it('test fully wrapped buffer up to last char', () => { + // const input = Array(6).join('1234567890'); + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // for (let i = 0; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); + // assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars()); + // } + // }); + + // it('test fully wrapped buffer up to last char with full width odd', () => { + // const input = 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301' + // + 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(input, s); + // for (let i = 0; i < input.length; ++i) { + // const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); + // assert.equal( + // (!(i % 3)) + // ? input[i] + // : (i % 3 === 1) + // ? input.substr(i, 2) + // : input.substr(i - 1, 2), + // terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars()); + // } + // }); + + // it('should handle \t in lines correctly', () => { + // const input = '\thttps://google.de'; + // terminal.writeSync(input); + // const s = terminal.buffer.iterator(true).next().content; + // assert.equal(s, Array(optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de'); + // }); + // }); + // describe('BufferStringIterator', function(): void { + // it('iterator does not overflow buffer limits', function(): void { + // const terminal = new TestTerminal({rows: 5, cols: 10, scrollback: 5}); + // const data = [ + // 'aaaaaaaaaa', + // 'aaaaaaaaa\n', + // 'aaaaaaaaaa', + // 'aaaaaaaaa\n', + // 'aaaaaaaaaa', + // 'aaaaaaaaaa', + // 'aaaaaaaaaa', + // 'aaaaaaaaa\n', + // 'aaaaaaaaaa', + // 'aaaaaaaaaa' + // ]; + // terminal.writeSync(data.join('')); + // // brute force test with insane values + // expect(() => { + // for (let overscan = 0; overscan < 20; ++overscan) { + // for (let start = -10; start < 20; ++start) { + // for (let end = -10; end < 20; ++end) { + // const it = terminal.buffer.iterator(false, start, end, overscan, overscan); + // while (it.hasNext()) { + // it.next(); + // } + // } + // } + // } + // }).to.not.throw(); + // }); + // }); +}); From 62ff97c82d0c438ec1d06d71501260210674ca55 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 17:58:56 -0700 Subject: [PATCH 10/13] Move BufferSet deps to common --- src/BufferSet.ts | 5 ++--- src/TestUtils.test.ts | 4 ++-- src/Types.ts | 13 +------------ src/common/buffer/Types.ts | 12 ++++++++++++ 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/BufferSet.ts b/src/BufferSet.ts index fc07b9a7..cc8fe737 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -3,10 +3,9 @@ * @license MIT */ -import { IBufferSet } from './Types'; -import { IBuffer } from 'common/buffer/Types'; +import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IAttributeData } from 'common/Types'; -import { Buffer } from './common/buffer/Buffer'; +import { Buffer } from 'common/buffer/Buffer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { IOptionsService, IBufferService } from 'common/services/Services'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index fa48b4b0..12267f98 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,8 +4,8 @@ */ import { IRenderer, IRenderDimensions } from './renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler } from './Types'; -import { IBuffer, IBufferStringIterator } from 'common/buffer/Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler } from './Types'; +import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; import { Buffer } from './common/buffer/Buffer'; import * as Browser from 'common/Platform'; diff --git a/src/Types.ts b/src/Types.ts index 40328cf4..9dafbf79 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -8,7 +8,7 @@ import { ICharset, IAttributeData, CharData } from 'common/Types'; import { IEvent } from 'common/EventEmitter2'; import { IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; -import { IBuffer } from 'common/buffer/Types'; +import { IBuffer, IBufferSet } from 'common/buffer/Types'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -296,17 +296,6 @@ export interface ITerminalOptions extends IPublicTerminalOptions { useFlowControl?: boolean; } -export interface IBufferSet { - alt: IBuffer; - normal: IBuffer; - active: IBuffer; - - onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>; - - activateNormalBuffer(): void; - activateAltBuffer(fillAttr?: IAttributeData): void; -} - export interface ISelectionManager { selectionText: string; selectionStart: [number, number]; diff --git a/src/common/buffer/Types.ts b/src/common/buffer/Types.ts index 9a483735..37ce2b7e 100644 --- a/src/common/buffer/Types.ts +++ b/src/common/buffer/Types.ts @@ -4,6 +4,7 @@ */ import { IAttributeData, ICircularList, IBufferLine, ICellData } from 'common/Types'; +import { IEvent } from 'common/EventEmitter2'; // BufferIndex denotes a position in the buffer: [rowIndex, colIndex] export type BufferIndex = [number, number]; @@ -42,3 +43,14 @@ export interface IBuffer { getNullCell(attr?: IAttributeData): ICellData; getWhitespaceCell(attr?: IAttributeData): ICellData; } + +export interface IBufferSet { + alt: IBuffer; + normal: IBuffer; + active: IBuffer; + + onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>; + + activateNormalBuffer(): void; + activateAltBuffer(fillAttr?: IAttributeData): void; +} From 9a237cb205152a71895be5cfd1b2844c8eca6ac2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 18:00:04 -0700 Subject: [PATCH 11/13] Move BufferSet* into common --- src/SelectionManager.test.ts | 2 +- src/SelectionModel.test.ts | 2 +- src/Terminal.ts | 2 +- src/{ => common/buffer}/BufferSet.test.ts | 4 ++-- src/{ => common/buffer}/BufferSet.ts | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename src/{ => common/buffer}/BufferSet.test.ts (95%) rename src/{ => common/buffer}/BufferSet.ts (100%) diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index f81cdcbe..d60e9d0f 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; -import { BufferSet } from './BufferSet'; +import { BufferSet } from './common/buffer/BufferSet'; import { ITerminal } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 479e20d3..7831cbb1 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; -import { BufferSet } from './BufferSet'; +import { BufferSet } from './common/buffer/BufferSet'; import { MockTerminal } from './TestUtils.test'; import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; diff --git a/src/Terminal.ts b/src/Terminal.ts index a15c9229..a53b65e7 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -23,7 +23,7 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IMouseZoneManager } from './Types'; import { IRenderer } from './renderer/Types'; -import { BufferSet } from './BufferSet'; +import { BufferSet } from './common/buffer/BufferSet'; import { Buffer } from './common/buffer/Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from 'common/EventEmitter'; diff --git a/src/BufferSet.test.ts b/src/common/buffer/BufferSet.test.ts similarity index 95% rename from src/BufferSet.test.ts rename to src/common/buffer/BufferSet.test.ts index 9dab0f7a..894ebb20 100644 --- a/src/BufferSet.test.ts +++ b/src/common/buffer/BufferSet.test.ts @@ -4,8 +4,8 @@ */ import { assert } from 'chai'; -import { BufferSet } from './BufferSet'; -import { Buffer } from './common/buffer/Buffer'; +import { BufferSet } from 'common/buffer/BufferSet'; +import { Buffer } from 'common/buffer/Buffer'; import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; describe('BufferSet', () => { diff --git a/src/BufferSet.ts b/src/common/buffer/BufferSet.ts similarity index 100% rename from src/BufferSet.ts rename to src/common/buffer/BufferSet.ts From f2e60d4c43e3264ee135ff67b473dd4b20e8b9be Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 18:01:27 -0700 Subject: [PATCH 12/13] Remove ! in Buffer ctor --- src/common/buffer/Buffer.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 4269f79c..1e6edae9 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -21,13 +21,14 @@ export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 * - scroll position */ export class Buffer implements IBuffer { - public lines!: CircularList; + public lines: CircularList; public ydisp: number = 0; public ybase: number = 0; public y: number = 0; public x: number = 0; - public scrollBottom!: number; - public scrollTop!: number; + public scrollBottom: number; + public scrollTop: number; + // TODO: Type me public tabs: any; public savedY: number = 0; public savedX: number = 0; @@ -45,7 +46,10 @@ export class Buffer implements IBuffer { ) { this._cols = this._bufferService.cols; this._rows = this._bufferService.rows; - this.clear(); + this.lines = new CircularList(this._getCorrectBufferLength(this._rows)); + this.scrollTop = 0; + this.scrollBottom = this._rows - 1; + this.setupTabStops(); } public getNullCell(attr?: IAttributeData): ICellData { From b1eecf10ffb8f52ca287c7deb24dad38630ea090 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 18:05:12 -0700 Subject: [PATCH 13/13] Remove relative imports with common --- src/InputHandler.ts | 2 +- src/SelectionManager.test.ts | 2 +- src/SelectionModel.test.ts | 2 +- src/Terminal.ts | 4 ++-- src/TestUtils.test.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 78bebfa5..b4689cd5 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -7,7 +7,7 @@ import { IInputHandler, IInputHandlingTerminal } from './Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; -import { wcwidth } from './common/CharWidth'; +import { wcwidth } from 'common/CharWidth'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { IDisposable } from 'xterm'; import { Disposable } from 'common/Lifecycle'; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index d60e9d0f..87f7386a 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; -import { BufferSet } from './common/buffer/BufferSet'; +import { BufferSet } from 'common/buffer/BufferSet'; import { ITerminal } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; diff --git a/src/SelectionModel.test.ts b/src/SelectionModel.test.ts index 7831cbb1..605ba9e8 100644 --- a/src/SelectionModel.test.ts +++ b/src/SelectionModel.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { ITerminal } from './Types'; import { SelectionModel } from './SelectionModel'; -import { BufferSet } from './common/buffer/BufferSet'; +import { BufferSet } from 'common/buffer/BufferSet'; import { MockTerminal } from './TestUtils.test'; import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; import { IBufferService } from 'common/services/Services'; diff --git a/src/Terminal.ts b/src/Terminal.ts index a53b65e7..851f4d6e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -23,8 +23,8 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IMouseZoneManager } from './Types'; import { IRenderer } from './renderer/Types'; -import { BufferSet } from './common/buffer/BufferSet'; -import { Buffer } from './common/buffer/Buffer'; +import { BufferSet } from 'common/buffer/BufferSet'; +import { Buffer } from 'common/buffer/Buffer'; import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from 'common/EventEmitter'; import { Viewport } from './Viewport'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 12267f98..4697eefb 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IRenderer, IRenderDimensions } from './renderer/Types'; import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; -import { Buffer } from './common/buffer/Buffer'; +import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal';