From 2e243feebeeebebd46b2b21f7311e48c0a61193b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 14:54:05 -0700 Subject: [PATCH 01/39] Create ICharDimensionsService --- src/ui/services/CharDimensionsService.ts | 80 ++++++++++++++++++++++++ src/ui/services/Services.d.ts | 13 ++++ 2 files changed, 93 insertions(+) create mode 100644 src/ui/services/CharDimensionsService.ts create mode 100644 src/ui/services/Services.d.ts diff --git a/src/ui/services/CharDimensionsService.ts b/src/ui/services/CharDimensionsService.ts new file mode 100644 index 00000000..af44c730 --- /dev/null +++ b/src/ui/services/CharDimensionsService.ts @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IOptionsService } from 'common/options/Types'; +import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; +import { ICharDimensionsService } from 'ui/services/Services'; + +export class CharDimensionsService implements ICharDimensionsService { + public width: number = 0; + public height: number = 0; + private _charDimensionsStrategy: ICharDimensionsStrategy; + + private _onCharDimensionsChange = new EventEmitter2(); + public get onCharDimensionsChange(): IEvent { return this._onCharDimensionsChange.event; } + + constructor( + document: Document, + parentElement: HTMLElement, + private _optionsService: IOptionsService + ) { + this._charDimensionsStrategy = new DomCharDimensionsStrategy(document, parentElement, this._optionsService); + } + + public measure(): void { + const result = this._charDimensionsStrategy.measure(); + this.width = result.width; + this.height = result.height; + } +} + +interface ICharDimensionsStrategy { + measure(): IReadonlyMeasureResult; +} + +interface IReadonlyMeasureResult { + readonly width: number; + readonly height: number; +} + +interface IMeasureResult { + width: number; + height: number; +} + +// TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses ctx.measureText +class DomCharDimensionsStrategy implements ICharDimensionsStrategy { + private _result: IMeasureResult = { width: 0, height: 0 }; + private _measureElement: HTMLElement; + + constructor( + private _document: Document, + private _parentElement: HTMLElement, + private _optionsService: IOptionsService + ) { + this._measureElement = this._document.createElement('span'); + this._measureElement.classList.add('xterm-char-measure-element'); + this._measureElement.textContent = 'W'; + this._measureElement.setAttribute('aria-hidden', 'true'); + this._parentElement.appendChild(this._measureElement); + } + + public measure(): IReadonlyMeasureResult { + this._measureElement.style.fontFamily = this._optionsService.options.fontFamily; + this._measureElement.style.fontSize = `${this._optionsService.options.fontSize}px`; + + // Note that this triggers a synchronous layout + const geometry = this._measureElement.getBoundingClientRect(); + + // 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) { + this._result.width = geometry.width; + this._result.height = Math.ceil(geometry.height); + } + + return this._result; + } +} diff --git a/src/ui/services/Services.d.ts b/src/ui/services/Services.d.ts new file mode 100644 index 00000000..f0c09cf9 --- /dev/null +++ b/src/ui/services/Services.d.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IEvent } from 'common/EventEmitter2'; + +export interface ICharDimensionsService { + readonly width: number; + readonly height: number; + readonly onCharDimensionsChange: IEvent; + measure(): void; +} From 38bb122bbd535ac056c7e1278ed8efb21e79266b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:09:55 -0700 Subject: [PATCH 02/39] CharDimensions -> CharSize --- ...imensionsService.ts => CharSizeService.ts} | 20 ++++++++++--------- src/ui/services/Services.d.ts | 7 +++++-- 2 files changed, 16 insertions(+), 11 deletions(-) rename src/ui/services/{CharDimensionsService.ts => CharSizeService.ts} (76%) diff --git a/src/ui/services/CharDimensionsService.ts b/src/ui/services/CharSizeService.ts similarity index 76% rename from src/ui/services/CharDimensionsService.ts rename to src/ui/services/CharSizeService.ts index af44c730..2fddb802 100644 --- a/src/ui/services/CharDimensionsService.ts +++ b/src/ui/services/CharSizeService.ts @@ -5,32 +5,34 @@ import { IOptionsService } from 'common/options/Types'; import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; -import { ICharDimensionsService } from 'ui/services/Services'; +import { ICharSizeService } from 'ui/services/Services'; -export class CharDimensionsService implements ICharDimensionsService { +export class CharSizeService implements ICharSizeService { public width: number = 0; public height: number = 0; - private _charDimensionsStrategy: ICharDimensionsStrategy; + private _measureStrategy: IMeasureStrategy; - private _onCharDimensionsChange = new EventEmitter2(); - public get onCharDimensionsChange(): IEvent { return this._onCharDimensionsChange.event; } + public get hasValidDimensions(): boolean { return this.width > 0 && this.height > 0; } + + private _onCharSizeChange = new EventEmitter2(); + public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; } constructor( document: Document, parentElement: HTMLElement, private _optionsService: IOptionsService ) { - this._charDimensionsStrategy = new DomCharDimensionsStrategy(document, parentElement, this._optionsService); + this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService); } public measure(): void { - const result = this._charDimensionsStrategy.measure(); + const result = this._measureStrategy.measure(); this.width = result.width; this.height = result.height; } } -interface ICharDimensionsStrategy { +interface IMeasureStrategy { measure(): IReadonlyMeasureResult; } @@ -45,7 +47,7 @@ interface IMeasureResult { } // TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses ctx.measureText -class DomCharDimensionsStrategy implements ICharDimensionsStrategy { +class DomMeasureStrategy implements IMeasureStrategy { private _result: IMeasureResult = { width: 0, height: 0 }; private _measureElement: HTMLElement; diff --git a/src/ui/services/Services.d.ts b/src/ui/services/Services.d.ts index f0c09cf9..51dd043a 100644 --- a/src/ui/services/Services.d.ts +++ b/src/ui/services/Services.d.ts @@ -5,9 +5,12 @@ import { IEvent } from 'common/EventEmitter2'; -export interface ICharDimensionsService { +export interface ICharSizeService { readonly width: number; readonly height: number; - readonly onCharDimensionsChange: IEvent; + readonly hasValidDimensions: boolean; + + readonly onCharSizeChange: IEvent; + measure(): void; } From 6246bb5026c073589c86b030c8fb0382462916de Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:20:03 -0700 Subject: [PATCH 03/39] Adopt CharSizeService in MouseHelper --- src/MouseHelper.test.ts | 31 +++++++++--------------------- src/MouseHelper.ts | 16 ++++++++------- src/MouseZoneManager.ts | 2 +- src/SelectionManager.ts | 2 +- src/Terminal.ts | 14 ++++++++++---- src/TestUtils.test.ts | 17 ++++++++-------- src/Types.ts | 4 ++-- src/handlers/AltClickHandler.ts | 1 - src/ui/services/CharSizeService.ts | 2 +- src/ui/services/Services.d.ts | 2 +- 10 files changed, 42 insertions(+), 49 deletions(-) diff --git a/src/MouseHelper.test.ts b/src/MouseHelper.test.ts index 0925d49f..7ca165e8 100644 --- a/src/MouseHelper.test.ts +++ b/src/MouseHelper.test.ts @@ -3,62 +3,49 @@ * @license MIT */ -import jsdom = require('jsdom'); import { assert } from 'chai'; import { MouseHelper } from './MouseHelper'; -import { MockCharMeasure, MockRenderer } from './TestUtils.test'; +import { MockRenderer, MockCharSizeService } from './TestUtils.test'; const CHAR_WIDTH = 10; const CHAR_HEIGHT = 20; describe('MouseHelper.getCoords', () => { - let dom: jsdom.JSDOM; - let window: Window; - let document: Document; let mouseHelper: MouseHelper; - let charMeasure: MockCharMeasure; - beforeEach(() => { - dom = new jsdom.JSDOM(''); - window = dom.window; - document = window.document; - charMeasure = new MockCharMeasure(); - charMeasure.width = CHAR_WIDTH; - charMeasure.height = CHAR_HEIGHT; const renderer = new MockRenderer(); renderer.dimensions = { actualCellWidth: CHAR_WIDTH, actualCellHeight: CHAR_HEIGHT }; - mouseHelper = new MouseHelper(renderer as any); + mouseHelper = new MouseHelper(renderer as any, new MockCharSizeService(CHAR_WIDTH, CHAR_HEIGHT)); }); describe('when charMeasure is not initialized', () => { it('should return null', () => { - charMeasure = new MockCharMeasure(); - assert.equal(mouseHelper.getCoords({ clientX: 0, clientY: 0 }, document.createElement('div'), charMeasure, 10, 10), null); + assert.equal(mouseHelper.getCoords({ clientX: 0, clientY: 0 }, document.createElement('div'), 10, 10), null); }); }); it('should return the cell that was clicked', () => { let coords: [number, number]; - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 10, 10); + coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10); assert.deepEqual(coords, [1, 1]); - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10); + coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10); assert.deepEqual(coords, [1, 1]); - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 10, 10); + coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10); assert.deepEqual(coords, [1, 2]); - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10); + coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10); assert.deepEqual(coords, [2, 1]); }); it('should ensure the coordinates are returned within the terminal bounds', () => { let coords: [number, number]; - coords = mouseHelper.getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), charMeasure, 10, 10); + coords = mouseHelper.getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10); assert.deepEqual(coords, [1, 1]); // Event are double the cols/rows - coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 10, 10); + coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10); assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal'); }); }); diff --git a/src/MouseHelper.ts b/src/MouseHelper.ts index 12905222..091d00ae 100644 --- a/src/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -3,12 +3,14 @@ * @license MIT */ -import { ICharMeasure, IMouseHelper } from './Types'; +import { IMouseHelper } from './Types'; import { RenderCoordinator } from './renderer/RenderCoordinator'; +import { ICharSizeService } from 'ui/services/Services'; export class MouseHelper implements IMouseHelper { constructor( - private _renderCoordinator: RenderCoordinator + private _renderCoordinator: RenderCoordinator, + private _charSizeService: ICharSizeService ) { } @@ -30,9 +32,9 @@ export class MouseHelper implements IMouseHelper { * apply an offset to the x value such that the left half of the cell will * select that cell and the right half will select the next cell. */ - public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { - // Coordinates cannot be measured if charMeasure has not been initialized - if (!charMeasure.width || !charMeasure.height) { + public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { + // Coordinates cannot be measured if there are no valid + if (!this._charSizeService.hasValidSize) { return null; } @@ -63,8 +65,8 @@ export class MouseHelper implements IMouseHelper { * @param colCount The number of columns in the terminal. * @param rowCount The number of rows in the terminal. */ - public getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number } { - const coords = this.getCoords(event, element, charMeasure, colCount, rowCount); + public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } { + const coords = this.getCoords(event, element, colCount, rowCount); let x = coords[0]; let y = coords[1]; diff --git a/src/MouseZoneManager.ts b/src/MouseZoneManager.ts index 737bfc91..109bc517 100644 --- a/src/MouseZoneManager.ts +++ b/src/MouseZoneManager.ts @@ -203,7 +203,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { } private _findZoneEventAt(e: MouseEvent): IMouseZone { - const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.cols, this._terminal.rows); + const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.cols, this._terminal.rows); if (!coords) { return null; } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 21283b8f..d0cba71d 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -354,7 +354,7 @@ export class SelectionManager implements ISelectionManager { * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.cols, this._terminal.rows, true); + const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._terminal.cols, this._terminal.rows, true); if (!coords) { return null; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 6feedcbb..f68d3105 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -55,6 +55,8 @@ import { ColorManager } from 'ui/ColorManager'; import { RenderCoordinator } from './renderer/RenderCoordinator'; import { IOptionsService } from 'common/options/Types'; import { OptionsService } from 'common/options/OptionsService'; +import { ICharSizeService } from 'ui/services/Services'; +import { CharSizeService } from 'ui/services/CharSizeService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -107,9 +109,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _customKeyEventHandler: CustomKeyEventHandler; - // services + // common services public optionsService: IOptionsService; + // browser services + private _charSizeService: ICharSizeService; + // modes public applicationKeypad: boolean; public applicationCursor: boolean; @@ -615,6 +620,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._helperContainer.appendChild(this._compositionView); this.charMeasure = new CharMeasure(document, this._helperContainer); + this._charSizeService = new CharSizeService(this._document, this._helperContainer, this.optionsService); // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); @@ -658,7 +664,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this.selectionManager.refresh())); - this.mouseHelper = new MouseHelper(this._renderCoordinator); + this.mouseHelper = new MouseHelper(this._renderCoordinator, this._charSizeService); // apply mouse event classes set by escape codes before terminal was attached this.element.classList.toggle('enable-mouse-events', this.mouseEvents); if (this.mouseEvents) { @@ -738,7 +744,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II button = getButton(ev); // get mouse coordinates - pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows); + pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); if (!pos) return; sendEvent(button, pos); @@ -764,7 +770,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< function sendMove(ev: MouseEvent): void { let button = pressed; - const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.cols, self.rows); + const pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); if (!pos) return; // buttons marked as motions diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index e44489ea..32b7a65c 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -14,6 +14,7 @@ import { Terminal } from './Terminal'; import { AttributeData } from 'core/buffer/BufferLine'; import { IColorManager, IColorSet } from 'ui/Types'; import { IOptionsService } from 'common/options/Types'; +import { ICharSizeService } from 'ui/services/Services'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -182,15 +183,6 @@ export class MockTerminal implements ITerminal { deregisterCharacterJoiner(joinerId: number): void { } } -export class MockCharMeasure implements ICharMeasure { - onCharSizeChanged: IEvent; - width: number; - height: number; - measure(options: ITerminalOptions): void { - throw new Error('Method not implemented.'); - } -} - export class MockInputHandlingTerminal implements IInputHandlingTerminal { element: HTMLElement; options: ITerminalOptions = {}; @@ -438,3 +430,10 @@ export class MockCompositionHelper implements ICompositionHelper { return true; } } + +export class MockCharSizeService implements ICharSizeService { + get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } + onCharSizeChange: IEvent; + constructor(public width: number, public height: number) {} + measure(): void {} +} diff --git a/src/Types.ts b/src/Types.ts index 7b9c68c5..0aecdc96 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -284,8 +284,8 @@ export interface ILinkifierAccessor { } export interface IMouseHelper { - getCoords(event: { clientX: number, clientY: number }, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number]; - getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number }; + getCoords(event: { clientX: number, clientY: number }, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number]; + getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number }; } export interface ICharMeasure { diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index 43c26498..eebabd7d 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -33,7 +33,6 @@ export class AltClickHandler { const coordinates = this._terminal.mouseHelper.getCoords( this._mouseEvent, this._terminal.element, - this._terminal.charMeasure, this._terminal.cols, this._terminal.rows, false diff --git a/src/ui/services/CharSizeService.ts b/src/ui/services/CharSizeService.ts index 2fddb802..7d9d3a2c 100644 --- a/src/ui/services/CharSizeService.ts +++ b/src/ui/services/CharSizeService.ts @@ -12,7 +12,7 @@ export class CharSizeService implements ICharSizeService { public height: number = 0; private _measureStrategy: IMeasureStrategy; - public get hasValidDimensions(): boolean { return this.width > 0 && this.height > 0; } + public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } private _onCharSizeChange = new EventEmitter2(); public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; } diff --git a/src/ui/services/Services.d.ts b/src/ui/services/Services.d.ts index 51dd043a..17cc4f3b 100644 --- a/src/ui/services/Services.d.ts +++ b/src/ui/services/Services.d.ts @@ -8,7 +8,7 @@ import { IEvent } from 'common/EventEmitter2'; export interface ICharSizeService { readonly width: number; readonly height: number; - readonly hasValidDimensions: boolean; + readonly hasValidSize: boolean; readonly onCharSizeChange: IEvent; From bfcd2b52c2fdf4dce6cc2969ee5aeeab9a0fde8e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:22:17 -0700 Subject: [PATCH 04/39] Adopt CharSizeService in CompositionHelper --- src/CompositionHelper.test.ts | 7 ++----- src/CompositionHelper.ts | 8 +++++--- src/MouseHelper.test.ts | 6 ------ src/MouseHelper.ts | 2 -- src/Terminal.ts | 10 +++++----- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 053a0c29..156f5a44 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -6,6 +6,7 @@ import { assert } from 'chai'; import { CompositionHelper } from './CompositionHelper'; import { ITerminal } from './Types'; +import { MockCharSizeService } from 'TestUtils.test'; describe('CompositionHelper', () => { let terminal: ITerminal; @@ -48,16 +49,12 @@ describe('CompositionHelper', () => { buffer: { isCursorInViewport: true }, - charMeasure: { - height: 10, - width: 10 - }, options: { lineHeight: 1 } } as any; handledText = ''; - compositionHelper = new CompositionHelper(textarea, compositionView, terminal); + compositionHelper = new CompositionHelper(textarea, compositionView, terminal, new MockCharSizeService(10, 10)); }); describe('Input', () => { diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index 840bef55..e1179fed 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -4,6 +4,7 @@ */ import { ITerminal } from './Types'; +import { ICharSizeService } from 'ui/services/Services'; interface IPosition { start: number; @@ -42,7 +43,8 @@ export class CompositionHelper { constructor( private _textarea: HTMLTextAreaElement, private _compositionView: HTMLElement, - private _terminal: ITerminal + private _terminal: ITerminal, + private _charSizeService: ICharSizeService ) { this._isComposing = false; this._isSendingComposition = false; @@ -195,9 +197,9 @@ export class CompositionHelper { } if (this._terminal.buffer.isCursorInViewport) { - const cellHeight = Math.ceil(this._terminal.charMeasure.height * this._terminal.options.lineHeight); + const cellHeight = Math.ceil(this._charSizeService.height * this._terminal.options.lineHeight); const cursorTop = this._terminal.buffer.y * cellHeight; - const cursorLeft = this._terminal.buffer.x * this._terminal.charMeasure.width; + const cursorLeft = this._terminal.buffer.x * this._charSizeService.width; this._compositionView.style.left = cursorLeft + 'px'; this._compositionView.style.top = cursorTop + 'px'; diff --git a/src/MouseHelper.test.ts b/src/MouseHelper.test.ts index 7ca165e8..946b3cb8 100644 --- a/src/MouseHelper.test.ts +++ b/src/MouseHelper.test.ts @@ -22,12 +22,6 @@ describe('MouseHelper.getCoords', () => { mouseHelper = new MouseHelper(renderer as any, new MockCharSizeService(CHAR_WIDTH, CHAR_HEIGHT)); }); - describe('when charMeasure is not initialized', () => { - it('should return null', () => { - assert.equal(mouseHelper.getCoords({ clientX: 0, clientY: 0 }, document.createElement('div'), 10, 10), null); - }); - }); - it('should return the cell that was clicked', () => { let coords: [number, number]; coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10); diff --git a/src/MouseHelper.ts b/src/MouseHelper.ts index 091d00ae..d5662ab0 100644 --- a/src/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -25,7 +25,6 @@ export class MouseHelper implements IMouseHelper { * little faster and this function is used in some low level code. * @param event The mouse event. * @param element The terminal's container element. - * @param charMeasure The char measure object used to determine character sizes. * @param colCount The number of columns in the terminal. * @param rowCount The number of rows n the terminal. * @param isSelection Whether the request is for the selection or not. This will @@ -61,7 +60,6 @@ export class MouseHelper implements IMouseHelper { * as expected by xterm. * @param event The mouse event. * @param element The terminal's container element. - * @param charMeasure The char measure object used to determine character sizes. * @param colCount The number of columns in the terminal. * @param rowCount The number of rows in the terminal. */ diff --git a/src/Terminal.ts b/src/Terminal.ts index f68d3105..1f010382 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -614,14 +614,14 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur())); this._helperContainer.appendChild(this.textarea); - this._compositionView = document.createElement('div'); - this._compositionView.classList.add('composition-view'); - this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this); - this._helperContainer.appendChild(this._compositionView); - this.charMeasure = new CharMeasure(document, this._helperContainer); this._charSizeService = new CharSizeService(this._document, this._helperContainer, this.optionsService); + this._compositionView = document.createElement('div'); + this._compositionView.classList.add('composition-view'); + this._compositionHelper = new CompositionHelper(this.textarea, this._compositionView, this, this._charSizeService); + this._helperContainer.appendChild(this._compositionView); + // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); From 760128e0f62b9a1b01c1bfabe5a454185619a5d1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:24:30 -0700 Subject: [PATCH 05/39] Adopt in renderers --- src/Terminal.ts | 4 ++-- src/renderer/Renderer.ts | 18 +++++++++--------- src/renderer/dom/DomRenderer.ts | 8 +++++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 1f010382..414e7dac 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -697,8 +697,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return new Renderer(this, this._colorManager.colors); break; - case 'dom': return new DomRenderer(this, this._colorManager.colors); break; + case 'canvas': return new Renderer(this, this._colorManager.colors, this._charSizeService); break; + case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService); break; default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2fa09824..19adddbf 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -12,6 +12,7 @@ import { LinkRenderLayer } from './LinkRenderLayer'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; import { Disposable } from 'common/Lifecycle'; import { IColorSet } from 'ui/Types'; +import { ICharSizeService } from 'ui/services/Services'; export class Renderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -22,7 +23,8 @@ export class Renderer extends Disposable implements IRenderer { constructor( private _terminal: ITerminal, - private _colors: IColorSet + private _colors: IColorSet, + private _charSizeService: ICharSizeService ) { super(); const allowTransparency = this._terminal.options.allowTransparency; @@ -133,8 +135,7 @@ export class Renderer extends Disposable implements IRenderer { * Recalculates the character and canvas dimensions. */ private _updateDimensions(): void { - // Perform a new measure if the CharMeasure dimensions are not yet available - if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + if (!this._charSizeService.hasValidSize) { return; } @@ -142,12 +143,12 @@ export class Renderer extends Disposable implements IRenderer { // drawn to an integer grid in order for the CharAtlas "stamps" to not be // blurry. When text is drawn to the grid not using the CharAtlas, it is // clipped to ensure there is no overlap with the next cell. - this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio); + this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * window.devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case // devicePixelRatio is a floating point number in order to ensure there is // enough space to draw the character to the cell. - this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio); // Calculate the scaled cell height, if lineHeight is not 1 then the value // will be floored because since lineHeight can never be lower then 1, there @@ -181,10 +182,9 @@ export class Renderer extends Disposable implements IRenderer { // Get the _actual_ dimensions of an individual cell. This needs to be // derived from the canvasWidth/Height calculated above which takes into - // account window.devicePixelRatio. CharMeasure.width/height by itself is - // insufficient when the page is not at 100% zoom level as CharMeasure is - // measured in CSS pixels, but the actual char size on the canvas can - // differ. + // account window.devicePixelRatio. ICharSizeService.width/height by itself + // is insufficient when the page is not at 100% zoom level as it's measured + // in CSS pixels, but the actual char size on the canvas can differ. this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; } diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index a1922541..77cfcba8 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -9,6 +9,7 @@ import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSO import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { Disposable } from 'common/Lifecycle'; import { IColorSet } from 'ui/Types'; +import { ICharSizeService } from 'ui/services/Services'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -41,7 +42,8 @@ export class DomRenderer extends Disposable implements IRenderer { constructor( private _terminal: ITerminal, - private _colors: IColorSet + private _colors: IColorSet, + private _charSizeService: ICharSizeService ) { super(); @@ -91,8 +93,8 @@ export class DomRenderer extends Disposable implements IRenderer { } private _updateDimensions(): void { - this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio; - this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio); + this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio; + this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio); this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); this.dimensions.scaledCharLeft = 0; From 53c3151de6f2e8d6d286098aac3c1eadf28caba3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:25:34 -0700 Subject: [PATCH 06/39] Adopt in Viewport --- src/Terminal.ts | 2 +- src/Viewport.ts | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 414e7dac..a80bbf52 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -635,7 +635,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._renderCoordinator.onRender(e => this._onRender.fire(e)); this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows)); - this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this.charMeasure, this._renderCoordinator.dimensions); + this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderCoordinator.dimensions, this._charSizeService); this.viewport.onThemeChange(this._colorManager.colors); this.register(this.viewport); diff --git a/src/Viewport.ts b/src/Viewport.ts index a7afdb9e..90565a34 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -4,11 +4,11 @@ */ import { ITerminal, IViewport } from './Types'; -import { CharMeasure } from './CharMeasure'; import { Disposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'ui/Lifecycle'; import { IColorSet } from 'ui/Types'; import { IRenderDimensions } from './renderer/Types'; +import { ICharSizeService } from 'ui/services/Services'; const FALLBACK_SCROLL_BAR_WIDTH = 15; @@ -33,19 +33,12 @@ export class Viewport extends Disposable implements IViewport { private _refreshAnimationFrame: number | null = null; private _ignoreNextScrollEvent: boolean = false; - /** - * Creates a new Viewport. - * @param _terminal The terminal this viewport belongs to. - * @param _viewportElement The DOM element acting as the viewport. - * @param _scrollArea The DOM element acting as the scroll area. - * @param _charMeasure A DOM element used to measure the character size of. the terminal. - */ constructor( private _terminal: ITerminal, private _viewportElement: HTMLElement, private _scrollArea: HTMLElement, - private _charMeasure: CharMeasure, - private _dimensions: IRenderDimensions + private _dimensions: IRenderDimensions, + private _charSizeService: ICharSizeService ) { super(); @@ -55,7 +48,7 @@ export class Viewport extends Disposable implements IViewport { this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this))); - // Perform this async to ensure the CharMeasure is ready. + // Perform this async to ensure the ICharSizeService is ready. setTimeout(() => this.syncScrollArea(), 0); } @@ -78,7 +71,7 @@ export class Viewport extends Disposable implements IViewport { } private _innerRefresh(): void { - if (this._charMeasure.height > 0) { + if (this._charSizeService.height > 0) { this._currentRowHeight = this._dimensions.scaledCellHeight / window.devicePixelRatio; this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._dimensions.canvasHeight); From 0f2d560e01f104f74545dfd1b3246c8014601fd5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:26:53 -0700 Subject: [PATCH 07/39] Adopt in SelectionManager --- src/SelectionManager.test.ts | 10 ++++------ src/SelectionManager.ts | 6 +++--- src/Terminal.ts | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 77a1612f..538abc91 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -4,13 +4,12 @@ */ import { assert } from 'chai'; -import { CharMeasure } from './CharMeasure'; import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer } from './Types'; import { IBufferLine } from 'core/Types'; -import { MockTerminal } from './TestUtils.test'; +import { MockTerminal, MockCharSizeService } from './TestUtils.test'; import { BufferLine, CellData } from 'core/buffer/BufferLine'; class TestMockTerminal extends MockTerminal { @@ -19,10 +18,9 @@ class TestMockTerminal extends MockTerminal { class TestSelectionManager extends SelectionManager { constructor( - terminal: ITerminal, - charMeasure: CharMeasure + terminal: ITerminal ) { - super(terminal, charMeasure); + super(terminal, new MockCharSizeService(10, 10)); } public get model(): SelectionModel { return this._model; } @@ -52,7 +50,7 @@ describe('SelectionManager', () => { terminal.buffers = new BufferSet(terminal); terminal.buffer = terminal.buffers.active; buffer = terminal.buffer; - selectionManager = new TestSelectionManager(terminal, null); + selectionManager = new TestSelectionManager(terminal); }); function stringToRow(text: string): IBufferLine { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index d0cba71d..0b939562 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -7,12 +7,12 @@ import { ITerminal, ISelectionManager, IBuffer, ISelectionRedrawRequestEvent } f import { IBufferLine } from 'core/Types'; import { MouseHelper } from './MouseHelper'; import * as Browser from 'common/Platform'; -import { CharMeasure } from './CharMeasure'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from 'core/buffer/BufferLine'; import { IDisposable } from 'xterm'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { ICharSizeService } from 'ui/services/Services'; /** * The number of pixels the mouse needs to be above or below the viewport in @@ -117,7 +117,7 @@ export class SelectionManager implements ISelectionManager { constructor( private _terminal: ITerminal, - private _charMeasure: CharMeasure + private _charSizeService: ICharSizeService ) { this._initListeners(); this.enable(); @@ -375,7 +375,7 @@ export class SelectionManager implements ISelectionManager { */ private _getMouseEventScrollAmount(event: MouseEvent): number { let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; - const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight); + const terminalHeight = this._terminal.rows * Math.ceil(this._charSizeService.height * this._terminal.options.lineHeight); if (offset >= 0 && offset <= terminalHeight) { return 0; } diff --git a/src/Terminal.ts b/src/Terminal.ts index a80bbf52..85594fb7 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -646,7 +646,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.charMeasure.onCharSizeChanged(() => this._renderCoordinator.onCharSizeChanged())); this.register(this._renderCoordinator.onDimensionsChange(() => this.viewport.syncScrollArea())); - this.selectionManager = new SelectionManager(this, this.charMeasure); + this.selectionManager = new SelectionManager(this, this._charSizeService); 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 47fab14efc541b9f855ab97b9930313e46d1ca75 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:29:16 -0700 Subject: [PATCH 08/39] Adopt in Terminal --- src/CharMeasure.test.ts | 54 -------------------------------------- src/CharMeasure.ts | 58 ----------------------------------------- src/Terminal.ts | 20 +++++++------- src/TestUtils.test.ts | 3 +-- src/Types.ts | 10 ------- 5 files changed, 11 insertions(+), 134 deletions(-) delete mode 100644 src/CharMeasure.test.ts delete mode 100644 src/CharMeasure.ts diff --git a/src/CharMeasure.test.ts b/src/CharMeasure.test.ts deleted file mode 100644 index 5fd17eb2..00000000 --- a/src/CharMeasure.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import jsdom = require('jsdom'); -import { ICharMeasure } from './Types'; -import { assert } from 'chai'; -import { CharMeasure } from './CharMeasure'; - -describe('CharMeasure', () => { - let dom: jsdom.JSDOM; - let window: Window; - let document: Document; - let container: HTMLElement; - let charMeasure: ICharMeasure; - - beforeEach(() => { - dom = new jsdom.JSDOM(''); - window = dom.window; - document = window.document; - container = document.createElement('div'); - document.body.appendChild(container); - charMeasure = new CharMeasure(document, container); - }); - - describe('measure', () => { - it('should have _measureElement', () => { - assert.isDefined((charMeasure)._measureElement, 'new CharMeasure() should have created _measureElement'); - }); - - it('should be performed sync', () => { - // Mock getBoundingClientRect since jsdom doesn't have a layout engine - (charMeasure)._measureElement.getBoundingClientRect = () => { - return { width: 1, height: 1 }; - }; - charMeasure.measure({}); - assert.equal(charMeasure.height, 1); - assert.equal(charMeasure.width, 1); - }); - - it('should NOT do a measure when the parent is hidden', done => { - charMeasure.measure({}); - setTimeout(() => { - const firstWidth = charMeasure.width; - container.style.display = 'none'; - container.style.fontSize = '2em'; - charMeasure.measure({}); - assert.equal(charMeasure.width, firstWidth); - done(); - }, 0); - }); - }); -}); diff --git a/src/CharMeasure.ts b/src/CharMeasure.ts deleted file mode 100644 index 9ef22b63..00000000 --- a/src/CharMeasure.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ICharMeasure, ITerminalOptions } from './Types'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; - -/** - * Utility class that measures the size of a character. Measurements are done in - * the DOM rather than with a canvas context because support for extracting the - * height of characters is patchy across browsers. - */ -export class CharMeasure implements ICharMeasure { - private _document: Document; - private _parentElement: HTMLElement; - private _measureElement: HTMLElement; - private _width: number; - private _height: number; - - private _onCharSizeChanged = new EventEmitter2(); - public get onCharSizeChanged(): IEvent { return this._onCharSizeChanged.event; } - - constructor(document: Document, parentElement: HTMLElement) { - this._document = document; - this._parentElement = parentElement; - this._measureElement = this._document.createElement('span'); - this._measureElement.classList.add('xterm-char-measure-element'); - this._measureElement.textContent = 'W'; - this._measureElement.setAttribute('aria-hidden', 'true'); - this._parentElement.appendChild(this._measureElement); - } - - public get width(): number { - return this._width; - } - - public get height(): number { - return this._height; - } - - public measure(options: ITerminalOptions): void { - this._measureElement.style.fontFamily = options.fontFamily; - this._measureElement.style.fontSize = `${options.fontSize}px`; - const geometry = this._measureElement.getBoundingClientRect(); - // The element is likely currently display:none, we should retain the - // previous value. - if (geometry.width === 0 || geometry.height === 0) { - return; - } - const adjustedHeight = Math.ceil(geometry.height); - if (this._width !== geometry.width || this._height !== adjustedHeight) { - this._width = geometry.width; - this._height = adjustedHeight; - this._onCharSizeChanged.fire(); - } - } -} diff --git a/src/Terminal.ts b/src/Terminal.ts index 85594fb7..1a02fe2a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -34,7 +34,6 @@ import { InputHandler } from './InputHandler'; import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; -import { CharMeasure } from './CharMeasure'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'ui/Lifecycle'; import * as Strings from './Strings'; @@ -180,7 +179,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public buffers: BufferSet; public viewport: IViewport; private _compositionHelper: ICompositionHelper; - public charMeasure: CharMeasure; private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; private _accessibilityManager: AccessibilityManager; @@ -365,7 +363,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // When the font changes the size of the cells may change which requires a renderer clear if (this._renderCoordinator) { this._renderCoordinator.clear(); - this.charMeasure.measure(this.options); + } + if (this._charSizeService) { + this._charSizeService.measure(); } break; case 'drawBoldTextInBrightColors': @@ -614,7 +614,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur())); this._helperContainer.appendChild(this.textarea); - this.charMeasure = new CharMeasure(document, this._helperContainer); this._charSizeService = new CharSizeService(this._document, this._helperContainer, this.optionsService); this._compositionView = document.createElement('div'); @@ -643,7 +642,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.onResize(() => this._renderCoordinator.onResize(this.cols, this.rows))); this.register(this.addDisposableListener('blur', () => this._renderCoordinator.onBlur())); this.register(this.addDisposableListener('focus', () => this._renderCoordinator.onFocus())); - this.register(this.charMeasure.onCharSizeChanged(() => this._renderCoordinator.onCharSizeChanged())); + // TODO: Move to RenderCoordinator + this.register(this._charSizeService.onCharSizeChange(() => this._renderCoordinator.onCharSizeChanged())); this.register(this._renderCoordinator.onDimensionsChange(() => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this._charSizeService); @@ -681,7 +681,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } // Measure the character size - this.charMeasure.measure(this.options); + this._charSizeService.measure(); // Setup loop that draws to screen this.refresh(0, this.rows - 1); @@ -1737,8 +1737,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (x === this.cols && y === this.rows) { // Check if we still need to measure the char size (fixes #785). - if (this.charMeasure && (!this.charMeasure.width || !this.charMeasure.height)) { - this.charMeasure.measure(this.options); + if (this._charSizeService && !this._charSizeService.hasValidSize) { + this._charSizeService.measure(); } return; } @@ -1752,8 +1752,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.rows = y; this.buffers.setupTabStops(this.cols); - if (this.charMeasure) { - this.charMeasure.measure(this.options); + if (this._charSizeService) { + this._charSizeService.measure(); } this.refresh(0, this.rows - 1); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 32b7a65c..490a1707 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, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; import { IBufferLine, ICellData, IAttributeData } from 'core/Types'; import { ICircularList, XtermListener } from 'common/Types'; import { Buffer } from './Buffer'; @@ -129,7 +129,6 @@ export class MockTerminal implements ITerminal { rowContainer: HTMLElement; selectionContainer: HTMLElement; selectionManager: ISelectionManager; - charMeasure: ICharMeasure; textarea: HTMLTextAreaElement; rows: number; cols: number; diff --git a/src/Types.ts b/src/Types.ts index 0aecdc96..2225ef3f 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -199,7 +199,6 @@ export interface ILinkifierEvent { export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { screenElement: HTMLElement; selectionManager: ISelectionManager; - charMeasure: ICharMeasure; browser: IBrowser; writeBuffer: string[]; cursorHidden: boolean; @@ -288,15 +287,6 @@ export interface IMouseHelper { getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number }; } -export interface ICharMeasure { - width: number; - height: number; - - onCharSizeChanged: IEvent; - - measure(options: ITerminalOptions): void; -} - // TODO: The options that are not in the public API should be reviewed export interface ITerminalOptions extends IPublicTerminalOptions { [key: string]: any; From 6375d9b88e5b66031ea8fbafd35be2b1b9d3811b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:45:07 -0700 Subject: [PATCH 09/39] Fix event firing --- src/TestUtils.test.ts | 2 +- src/ui/services/CharSizeService.ts | 13 ++++++++----- src/ui/services/Services.d.ts | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 490a1707..06da0ac1 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -432,7 +432,7 @@ export class MockCompositionHelper implements ICompositionHelper { export class MockCharSizeService implements ICharSizeService { get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - onCharSizeChange: IEvent; + onCharSizeChange: IEvent; constructor(public width: number, public height: number) {} measure(): void {} } diff --git a/src/ui/services/CharSizeService.ts b/src/ui/services/CharSizeService.ts index 7d9d3a2c..4a297fe7 100644 --- a/src/ui/services/CharSizeService.ts +++ b/src/ui/services/CharSizeService.ts @@ -14,8 +14,8 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - private _onCharSizeChange = new EventEmitter2(); - public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; } + private _onCharSizeChange = new EventEmitter2(); + public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; } constructor( document: Document, @@ -27,8 +27,11 @@ export class CharSizeService implements ICharSizeService { public measure(): void { const result = this._measureStrategy.measure(); - this.width = result.width; - this.height = result.height; + if (result.width !== this.width || result.height !== this.height) { + this.width = result.width; + this.height = result.height; + this._onCharSizeChange.fire(); + } } } @@ -69,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/ui/services/Services.d.ts b/src/ui/services/Services.d.ts index 17cc4f3b..b7a94ea1 100644 --- a/src/ui/services/Services.d.ts +++ b/src/ui/services/Services.d.ts @@ -10,7 +10,7 @@ export interface ICharSizeService { readonly height: number; readonly hasValidSize: boolean; - readonly onCharSizeChange: IEvent; + readonly onCharSizeChange: IEvent; measure(): void; } From 9df3a3671da998ec0672c11762f65cf3352b62bc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:46:12 -0700 Subject: [PATCH 10/39] Adopt CharSizeService in RenderCoordinator --- src/Terminal.ts | 4 +--- src/renderer/RenderCoordinator.ts | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 1a02fe2a..f6e38594 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -630,7 +630,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._colorManager.setTheme(this._theme); const renderer = this._createRenderer(); - this._renderCoordinator = new RenderCoordinator(renderer, this.rows, this.screenElement, this.optionsService); + this._renderCoordinator = new RenderCoordinator(renderer, this.rows, this.screenElement, this.optionsService, this._charSizeService); this._renderCoordinator.onRender(e => this._onRender.fire(e)); this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows)); @@ -642,8 +642,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.onResize(() => this._renderCoordinator.onResize(this.cols, this.rows))); this.register(this.addDisposableListener('blur', () => this._renderCoordinator.onBlur())); this.register(this.addDisposableListener('focus', () => this._renderCoordinator.onFocus())); - // TODO: Move to RenderCoordinator - this.register(this._charSizeService.onCharSizeChange(() => this._renderCoordinator.onCharSizeChanged())); this.register(this._renderCoordinator.onDimensionsChange(() => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this._charSizeService); diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index 98c9ae86..9136b063 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -12,6 +12,7 @@ import { addDisposableDomListener } from 'ui/Lifecycle'; import { IColorSet } from 'ui/Types'; import { CharacterJoinerHandler } from '../Types'; import { IOptionsService } from 'common/options/Types'; +import { ICharSizeService } from 'ui/services/Services'; export class RenderCoordinator extends Disposable { private _renderDebouncer: RenderDebouncer; @@ -35,7 +36,8 @@ export class RenderCoordinator extends Disposable { private _renderer: IRenderer, private _rowCount: number, screenElement: HTMLElement, - optionsService: IOptionsService + optionsService: IOptionsService, + charSizeService: ICharSizeService ) { super(); this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end)); @@ -46,6 +48,7 @@ export class RenderCoordinator extends Disposable { this.register(this._screenDprMonitor); this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); + this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged())); // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. From 1704548b46fc9ec97830cd380fedd502c1dd4ceb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 15:58:44 -0700 Subject: [PATCH 11/39] Move OptionsService into services folder --- src/Terminal.ts | 4 ++-- src/TestUtils.test.ts | 2 +- src/Types.ts | 2 +- src/common/{options => services}/OptionsService.ts | 2 +- src/common/{options/Types.ts => services/Services.d.ts} | 4 ++-- src/renderer/RenderCoordinator.ts | 2 +- src/ui/services/CharSizeService.ts | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) rename src/common/{options => services}/OptionsService.ts (99%) rename src/common/{options/Types.ts => services/Services.d.ts} (99%) diff --git a/src/Terminal.ts b/src/Terminal.ts index f6e38594..0ea8a335 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,8 +52,8 @@ import { Attributes, DEFAULT_ATTR_DATA } from 'core/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from 'ui/ColorManager'; import { RenderCoordinator } from './renderer/RenderCoordinator'; -import { IOptionsService } from 'common/options/Types'; -import { OptionsService } from 'common/options/OptionsService'; +import { IOptionsService } from 'common/services/Services'; +import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService } from 'ui/services/Services'; import { CharSizeService } from 'ui/services/CharSizeService'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 06da0ac1..05e7fa61 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -13,7 +13,7 @@ import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'core/buffer/BufferLine'; import { IColorManager, IColorSet } from 'ui/Types'; -import { IOptionsService } from 'common/options/Types'; +import { IOptionsService } from 'common/services/Services'; import { ICharSizeService } from 'ui/services/Services'; export class TestTerminal extends Terminal { diff --git a/src/Types.ts b/src/Types.ts index 2225ef3f..3eca3ac4 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -8,7 +8,7 @@ import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from 'core import { ICircularList } from 'common/Types'; import { IEvent } from 'common/EventEmitter2'; import { IColorSet } from 'ui/Types'; -import { IOptionsService } from 'common/options/Types'; +import { IOptionsService } from 'common/services/Services'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; diff --git a/src/common/options/OptionsService.ts b/src/common/services/OptionsService.ts similarity index 99% rename from src/common/options/OptionsService.ts rename to src/common/services/OptionsService.ts index 3523198c..0fce405d 100644 --- a/src/common/options/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/options/Types'; +import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { isMac } from 'common/Platform'; import { clone } from 'common/Clone'; diff --git a/src/common/options/Types.ts b/src/common/services/Services.d.ts similarity index 99% rename from src/common/options/Types.ts rename to src/common/services/Services.d.ts index 5c5c3323..651e8636 100644 --- a/src/common/options/Types.ts +++ b/src/common/services/Services.d.ts @@ -6,10 +6,10 @@ import { IEvent } from 'common/EventEmitter2'; export interface IOptionsService { - readonly onOptionChange: IEvent; - // TODO: as const? readonly options: ITerminalOptions; + readonly onOptionChange: IEvent; + setOption(key: string, value: T): void; getOption(key: string): T | undefined; } diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index 9136b063..dc6e0694 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -11,7 +11,7 @@ import { ScreenDprMonitor } from 'ui/ScreenDprMonitor'; import { addDisposableDomListener } from 'ui/Lifecycle'; import { IColorSet } from 'ui/Types'; import { CharacterJoinerHandler } from '../Types'; -import { IOptionsService } from 'common/options/Types'; +import { IOptionsService } from 'common/services/Services'; import { ICharSizeService } from 'ui/services/Services'; export class RenderCoordinator extends Disposable { diff --git a/src/ui/services/CharSizeService.ts b/src/ui/services/CharSizeService.ts index 4a297fe7..61405909 100644 --- a/src/ui/services/CharSizeService.ts +++ b/src/ui/services/CharSizeService.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IOptionsService } from 'common/options/Types'; +import { IOptionsService } from 'common/services/Services'; import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; import { ICharSizeService } from 'ui/services/Services'; From f8e7ff24d9af0daea8df15dcad89da8a07670561 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 16:09:27 -0700 Subject: [PATCH 12/39] Merge core into common Part of #1507 --- src/Buffer.test.ts | 2 +- src/Buffer.ts | 8 +- src/BufferSet.ts | 2 +- src/InputHandler.test.ts | 4 +- src/InputHandler.ts | 10 +- src/Linkifier.test.ts | 4 +- src/MouseHelper.test.ts | 3 + src/SelectionManager.test.ts | 4 +- src/SelectionManager.ts | 4 +- src/Terminal.test.ts | 2 +- src/Terminal.ts | 6 +- src/Terminal2.test.ts | 2 +- src/TestUtils.test.ts | 4 +- src/Types.ts | 2 +- src/WindowsMode.ts | 2 +- src/common/Types.ts | 102 +++++++++++++++++ .../buffer/BufferLine.test.ts | 0 src/{core => common}/buffer/BufferLine.ts | 5 +- .../buffer/BufferReflow.test.ts | 4 +- src/{core => common}/buffer/BufferReflow.ts | 4 +- src/{core => common}/buffer/Marker.ts | 2 +- src/{core => common}/data/Charsets.ts | 2 +- src/{core => common}/input/Keyboard.test.ts | 5 +- src/{core => common}/input/Keyboard.ts | 3 +- .../input/TextDecoder.test.ts | 2 +- src/{core => common}/input/TextDecoder.ts | 0 .../parser/EscapeSequenceParser.test.ts | 6 +- .../parser/EscapeSequenceParser.ts | 4 +- src/{core => common}/parser/Types.ts | 0 src/core/Types.ts | 108 ------------------ src/core/tsconfig.json | 17 --- src/handlers/AltClickHandler.ts | 3 +- src/public/Terminal.ts | 2 +- src/renderer/BaseRenderLayer.ts | 4 +- src/renderer/CharacterJoinerRegistry.test.ts | 4 +- src/renderer/CharacterJoinerRegistry.ts | 4 +- src/renderer/CursorRenderLayer.ts | 4 +- src/renderer/TextRenderLayer.ts | 4 +- .../dom/DomRendererRowFactory.test.ts | 4 +- src/renderer/dom/DomRendererRowFactory.ts | 4 +- src/tsconfig.json | 2 - 41 files changed, 166 insertions(+), 192 deletions(-) rename src/{core => common}/buffer/BufferLine.test.ts (100%) rename src/{core => common}/buffer/BufferLine.ts (99%) rename src/{core => common}/buffer/BufferReflow.test.ts (97%) rename src/{core => common}/buffer/BufferReflow.ts (98%) rename src/{core => common}/buffer/Marker.ts (95%) rename src/{core => common}/data/Charsets.ts (99%) rename src/{core => common}/input/Keyboard.test.ts (99%) rename src/{core => common}/input/Keyboard.ts (98%) rename src/{core => common}/input/TextDecoder.test.ts (99%) rename src/{core => common}/input/TextDecoder.ts (100%) rename src/{core => common}/parser/EscapeSequenceParser.test.ts (99%) rename src/{core => common}/parser/EscapeSequenceParser.ts (99%) rename src/{core => common}/parser/Types.ts (100%) delete mode 100644 src/core/Types.ts delete mode 100644 src/core/tsconfig.json diff --git a/src/Buffer.test.ts b/src/Buffer.test.ts index e1572889..101912bb 100644 --- a/src/Buffer.test.ts +++ b/src/Buffer.test.ts @@ -8,7 +8,7 @@ import { ITerminal } from './Types'; import { Buffer } from './Buffer'; import { CircularList } from 'common/CircularList'; import { MockTerminal, TestTerminal } from './TestUtils.test'; -import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'core/buffer/BufferLine'; +import { BufferLine, CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; diff --git a/src/Buffer.ts b/src/Buffer.ts index a0d590a5..6b89884e 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -5,10 +5,10 @@ import { CircularList, IInsertEvent } from 'common/CircularList'; import { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types'; -import { IBufferLine, ICellData, IAttributeData } from 'core/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 'core/buffer/BufferLine'; -import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from 'core/buffer/BufferReflow'; -import { Marker } from 'core/buffer/Marker'; +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'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 diff --git a/src/BufferSet.ts b/src/BufferSet.ts index c2aee6a7..1d4ae2c6 100644 --- a/src/BufferSet.ts +++ b/src/BufferSet.ts @@ -4,7 +4,7 @@ */ import { ITerminal, IBufferSet, IBuffer } from './Types'; -import { IAttributeData } from 'core/Types'; +import { IAttributeData } from 'common/Types'; import { Buffer } from './Buffer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index f3a217a1..3e5cef57 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -7,8 +7,8 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; import { Terminal } from './Terminal'; -import { IBufferLine } from 'core/Types'; -import { CellData, Attributes, AttributeData, DEFAULT_ATTR_DATA } from 'core/buffer/BufferLine'; +import { IBufferLine } from 'common/Types'; +import { CellData, Attributes, AttributeData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; describe('InputHandler', () => { describe('save and restore cursor', () => { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 354f3962..78bebfa5 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -6,16 +6,16 @@ import { IInputHandler, IInputHandlingTerminal } from './Types'; import { C0, C1 } from 'common/data/EscapeSequences'; -import { CHARSETS, DEFAULT_CHARSET } from 'core/data/Charsets'; +import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { wcwidth } from './common/CharWidth'; -import { EscapeSequenceParser } from 'core/parser/EscapeSequenceParser'; +import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { IDisposable } from 'xterm'; import { Disposable } from 'common/Lifecycle'; import { concat } from 'common/TypedArrayUtils'; -import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'core/input/TextDecoder'; -import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from 'core/buffer/BufferLine'; +import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder'; +import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; -import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'core/parser/Types'; +import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types'; /** * Map collect to glevel. Used in `selectCharset`. diff --git a/src/Linkifier.test.ts b/src/Linkifier.test.ts index e02105d8..8b7e71a0 100644 --- a/src/Linkifier.test.ts +++ b/src/Linkifier.test.ts @@ -5,11 +5,11 @@ import { assert } from 'chai'; import { IMouseZoneManager, IMouseZone, ILinkMatcher, ITerminal } from './Types'; -import { IBufferLine } from 'core/Types'; +import { IBufferLine } from 'common/Types'; import { Linkifier } from './Linkifier'; import { MockBuffer, MockTerminal, TestTerminal } from './TestUtils.test'; import { CircularList } from 'common/CircularList'; -import { BufferLine, CellData } from 'core/buffer/BufferLine'; +import { BufferLine, CellData } from 'common/buffer/BufferLine'; class TestLinkifier extends Linkifier { constructor(terminal: ITerminal) { diff --git a/src/MouseHelper.test.ts b/src/MouseHelper.test.ts index 946b3cb8..a0669ec0 100644 --- a/src/MouseHelper.test.ts +++ b/src/MouseHelper.test.ts @@ -3,6 +3,7 @@ * @license MIT */ +import jsdom = require('jsdom'); import { assert } from 'chai'; import { MouseHelper } from './MouseHelper'; import { MockRenderer, MockCharSizeService } from './TestUtils.test'; @@ -11,9 +12,11 @@ const CHAR_WIDTH = 10; const CHAR_HEIGHT = 20; describe('MouseHelper.getCoords', () => { + let document: Document; let mouseHelper: MouseHelper; beforeEach(() => { + document = new jsdom.JSDOM('').window.document; const renderer = new MockRenderer(); renderer.dimensions = { actualCellWidth: CHAR_WIDTH, diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 538abc91..90d70d79 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -8,9 +8,9 @@ import { SelectionManager, SelectionMode } from './SelectionManager'; import { SelectionModel } from './SelectionModel'; import { BufferSet } from './BufferSet'; import { ITerminal, IBuffer } from './Types'; -import { IBufferLine } from 'core/Types'; +import { IBufferLine } from 'common/Types'; import { MockTerminal, MockCharSizeService } from './TestUtils.test'; -import { BufferLine, CellData } from 'core/buffer/BufferLine'; +import { BufferLine, CellData } from 'common/buffer/BufferLine'; class TestMockTerminal extends MockTerminal { emit(event: string, data: any): void {} diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 0b939562..33a087e2 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -4,12 +4,12 @@ */ import { ITerminal, ISelectionManager, IBuffer, ISelectionRedrawRequestEvent } from './Types'; -import { IBufferLine } from 'core/Types'; +import { IBufferLine } from 'common/Types'; import { MouseHelper } from './MouseHelper'; import * as Browser from 'common/Platform'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; -import { CellData } from 'core/buffer/BufferLine'; +import { CellData } from 'common/buffer/BufferLine'; import { IDisposable } from 'xterm'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { ICharSizeService } from 'ui/services/Services'; diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index beb9e921..db474269 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -6,7 +6,7 @@ import { assert, expect } from 'chai'; import { Terminal } from './Terminal'; import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test'; -import { CellData, DEFAULT_ATTR_DATA } from 'core/buffer/BufferLine'; +import { CellData, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; const INIT_COLS = 80; const INIT_ROWS = 24; diff --git a/src/Terminal.ts b/src/Terminal.ts index 0ea8a335..5bc6c6a6 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -45,10 +45,10 @@ import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from 'common/Types'; -import { evaluateKeyboardEvent } from 'core/input/Keyboard'; -import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'core/Types'; +import { evaluateKeyboardEvent } from 'common/input/Keyboard'; +import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'common/Types'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; -import { Attributes, DEFAULT_ATTR_DATA } from 'core/buffer/BufferLine'; +import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from 'ui/ColorManager'; import { RenderCoordinator } from './renderer/RenderCoordinator'; diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index 3e85347b..0fc2f461 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -12,7 +12,7 @@ import * as path from 'path'; import * as pty from 'node-pty'; import { Terminal } from './Terminal'; import { IViewport } from './Types'; -import { CellData, WHITESPACE_CELL_CHAR } from 'core/buffer/BufferLine'; +import { CellData, WHITESPACE_CELL_CHAR } from 'common/buffer/BufferLine'; class TestTerminal extends Terminal { innerWrite(): void { this._innerWrite(); } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 05e7fa61..eb2f43fc 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -5,13 +5,13 @@ import { IRenderer, IRenderDimensions } from './renderer/Types'; import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types'; -import { IBufferLine, ICellData, IAttributeData } from 'core/Types'; +import { IBufferLine, ICellData, IAttributeData } from 'common/Types'; import { ICircularList, XtermListener } from 'common/Types'; import { Buffer } from './Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; -import { AttributeData } from 'core/buffer/BufferLine'; +import { AttributeData } from 'common/buffer/BufferLine'; import { IColorManager, IColorSet } from 'ui/Types'; import { IOptionsService } from 'common/services/Services'; import { ICharSizeService } from 'ui/services/Services'; diff --git a/src/Types.ts b/src/Types.ts index 3eca3ac4..c0ce984a 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -4,7 +4,7 @@ */ import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; -import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from 'core/Types'; +import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from 'common/Types'; import { ICircularList } from 'common/Types'; import { IEvent } from 'common/EventEmitter2'; import { IColorSet } from 'ui/Types'; diff --git a/src/WindowsMode.ts b/src/WindowsMode.ts index 3bfa2a16..cee31de1 100644 --- a/src/WindowsMode.ts +++ b/src/WindowsMode.ts @@ -5,7 +5,7 @@ import { IDisposable } from 'xterm'; import { ITerminal } from './Types'; -import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'core/buffer/BufferLine'; +import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/BufferLine'; export function applyWindowsMode(terminal: ITerminal): IDisposable { // Winpty does not support wraparound mode which means that lines will never diff --git a/src/common/Types.ts b/src/common/Types.ts index e0a1e612..67d449e5 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -56,3 +56,105 @@ export interface ICircularList { trimStart(count: number): void; shiftElements(start: number, count: number, offset: number): void; } + +export const enum KeyboardResultType { + SEND_KEY, + SELECT_ALL, + PAGE_UP, + PAGE_DOWN +} + +export interface IKeyboardResult { + type: KeyboardResultType; + cancel: boolean; + key: string | undefined; +} + +export interface ICharset { + [key: string]: string; +} + +export type CharData = [number, string, number, number]; +export type IColorRGB = [number, number, number]; + +/** Attribute data */ +export interface IAttributeData { + fg: number; + bg: number; + + clone(): IAttributeData; + + // flags + isInverse(): number; + isBold(): number; + isUnderline(): number; + isBlink(): number; + isInvisible(): number; + isItalic(): number; + isDim(): number; + + // color modes + getFgColorMode(): number; + getBgColorMode(): number; + isFgRGB(): boolean; + isBgRGB(): boolean; + isFgPalette(): boolean; + isBgPalette(): boolean; + isFgDefault(): boolean; + isBgDefault(): boolean; + + // colors + getFgColor(): number; + getBgColor(): number; +} + +/** Cell data */ +export interface ICellData extends IAttributeData { + content: number; + combinedData: string; + isCombined(): number; + getWidth(): number; + getChars(): string; + getCode(): number; + setFromCharData(value: CharData): void; + getAsCharData(): CharData; +} + +/** + * Interface for a line in the terminal buffer. + */ +export interface IBufferLine { + length: number; + isWrapped: boolean; + get(index: number): CharData; + set(index: number, value: CharData): void; + loadCell(index: number, cell: ICellData): ICellData; + setCell(index: number, cell: ICellData): void; + setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; + addCodepointToCell(index: number, codePoint: number): void; + insertCells(pos: number, n: number, ch: ICellData): void; + deleteCells(pos: number, n: number, fill: ICellData): void; + replaceCells(start: number, end: number, fill: ICellData): void; + resize(cols: number, fill: ICellData): void; + fill(fillCellData: ICellData): void; + copyFrom(line: IBufferLine): void; + clone(): IBufferLine; + getTrimmedLength(): number; + translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string; + + /* direct access to cell attrs */ + getWidth(index: number): number; + hasWidth(index: number): number; + getFg(index: number): number; + getBg(index: number): number; + hasContent(index: number): number; + getCodePoint(index: number): number; + isCombined(index: number): number; + getString(index: number): string; +} + +export interface IMarker extends IDisposable { + readonly id: number; + readonly isDisposed: boolean; + readonly line: number; +} diff --git a/src/core/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts similarity index 100% rename from src/core/buffer/BufferLine.test.ts rename to src/common/buffer/BufferLine.test.ts diff --git a/src/core/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts similarity index 99% rename from src/core/buffer/BufferLine.ts rename to src/common/buffer/BufferLine.ts index 91c3a8d1..eb0a5e99 100644 --- a/src/core/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -2,9 +2,8 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ -import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from 'core/Types'; -import { stringFromCodePoint } from 'core/input/TextDecoder'; -import { DEFAULT_COLOR } from 'common/Types'; +import { DEFAULT_COLOR, CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from 'common/Types'; +import { stringFromCodePoint } from 'common/input/TextDecoder'; export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0); diff --git a/src/core/buffer/BufferReflow.test.ts b/src/common/buffer/BufferReflow.test.ts similarity index 97% rename from src/core/buffer/BufferReflow.test.ts rename to src/common/buffer/BufferReflow.test.ts index f7d2de73..af908572 100644 --- a/src/core/buffer/BufferReflow.test.ts +++ b/src/common/buffer/BufferReflow.test.ts @@ -3,8 +3,8 @@ * @license MIT */ import { assert } from 'chai'; -import { BufferLine, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from 'core/buffer/BufferLine'; -import { reflowSmallerGetNewLineLengths } from 'core/buffer/BufferReflow'; +import { BufferLine, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from 'common/buffer/BufferLine'; +import { reflowSmallerGetNewLineLengths } from 'common/buffer/BufferReflow'; describe('BufferReflow', () => { describe('reflowSmallerGetNewLineLengths', () => { diff --git a/src/core/buffer/BufferReflow.ts b/src/common/buffer/BufferReflow.ts similarity index 98% rename from src/core/buffer/BufferReflow.ts rename to src/common/buffer/BufferReflow.ts index d75941e3..ece9a96e 100644 --- a/src/core/buffer/BufferReflow.ts +++ b/src/common/buffer/BufferReflow.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { BufferLine } from 'core/buffer/BufferLine'; +import { BufferLine } from 'common/buffer/BufferLine'; import { CircularList } from 'common/CircularList'; -import { IBufferLine, ICellData } from 'core/Types'; +import { IBufferLine, ICellData } from 'common/Types'; export interface INewLayoutResult { layout: number[]; diff --git a/src/core/buffer/Marker.ts b/src/common/buffer/Marker.ts similarity index 95% rename from src/core/buffer/Marker.ts rename to src/common/buffer/Marker.ts index 8d207166..51c5d7f8 100644 --- a/src/core/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -5,7 +5,7 @@ import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { Disposable } from 'common/Lifecycle'; -import { IMarker } from 'core/Types'; +import { IMarker } from 'common/Types'; export class Marker extends Disposable implements IMarker { private static _nextId = 1; diff --git a/src/core/data/Charsets.ts b/src/common/data/Charsets.ts similarity index 99% rename from src/core/data/Charsets.ts rename to src/common/data/Charsets.ts index f6ee7297..56ca6799 100644 --- a/src/core/data/Charsets.ts +++ b/src/common/data/Charsets.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICharset } from 'core/Types'; +import { ICharset } from 'common/Types'; /** * The character sets supported by the terminal. These enable several languages diff --git a/src/core/input/Keyboard.test.ts b/src/common/input/Keyboard.test.ts similarity index 99% rename from src/core/input/Keyboard.test.ts rename to src/common/input/Keyboard.test.ts index 8a5f105b..409a3192 100644 --- a/src/core/input/Keyboard.test.ts +++ b/src/common/input/Keyboard.test.ts @@ -1,8 +1,7 @@ import { assert } from 'chai'; -import { evaluateKeyboardEvent } from 'core/input/Keyboard'; -import { IKeyboardResult } from 'core/Types'; -import { IKeyboardEvent } from 'common/Types'; +import { evaluateKeyboardEvent } from 'common/input/Keyboard'; +import { IKeyboardResult, IKeyboardEvent } from 'common/Types'; /** * A helper function for testing which allows passing in a partial event and defaults will be filled diff --git a/src/core/input/Keyboard.ts b/src/common/input/Keyboard.ts similarity index 98% rename from src/core/input/Keyboard.ts rename to src/common/input/Keyboard.ts index c78328da..3cc0eb76 100644 --- a/src/core/input/Keyboard.ts +++ b/src/common/input/Keyboard.ts @@ -4,8 +4,7 @@ * @license MIT */ -import { IKeyboardEvent } from 'common/Types'; -import { IKeyboardResult, KeyboardResultType } from 'core/Types'; +import { IKeyboardEvent, IKeyboardResult, KeyboardResultType } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; // reg + shift key mappings for digits and special chars diff --git a/src/core/input/TextDecoder.test.ts b/src/common/input/TextDecoder.test.ts similarity index 99% rename from src/core/input/TextDecoder.test.ts rename to src/common/input/TextDecoder.test.ts index ba1c04fa..cda74a18 100644 --- a/src/core/input/TextDecoder.test.ts +++ b/src/common/input/TextDecoder.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32, utf32ToString } from 'core/input/TextDecoder'; +import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32, utf32ToString } from 'common/input/TextDecoder'; import { encode } from 'utf8'; // convert UTF32 codepoints to string diff --git a/src/core/input/TextDecoder.ts b/src/common/input/TextDecoder.ts similarity index 100% rename from src/core/input/TextDecoder.ts rename to src/common/input/TextDecoder.ts diff --git a/src/core/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts similarity index 99% rename from src/core/parser/EscapeSequenceParser.test.ts rename to src/common/parser/EscapeSequenceParser.test.ts index 01436e5e..18d5c6cf 100644 --- a/src/core/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { ParserState, IDcsHandler, IParsingState } from 'core/parser/Types'; -import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'core/parser/EscapeSequenceParser'; +import { ParserState, IDcsHandler, IParsingState } from 'common/parser/Types'; +import { EscapeSequenceParser, TransitionTable, VT500_TRANSITION_TABLE } from 'common/parser/EscapeSequenceParser'; import * as chai from 'chai'; -import { StringToUtf32, stringFromCodePoint } from 'core/input/TextDecoder'; +import { StringToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; function r(a: number, b: number): string[] { let c = b - a; diff --git a/src/core/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts similarity index 99% rename from src/core/parser/EscapeSequenceParser.ts rename to src/common/parser/EscapeSequenceParser.ts index 5ecf4909..c8b631d0 100644 --- a/src/core/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from 'core/parser/Types'; +import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types'; import { Disposable } from 'common/Lifecycle'; -import { utf32ToString } from 'core/input/TextDecoder'; +import { utf32ToString } from 'common/input/TextDecoder'; import { IDisposable } from 'common/Types'; import { fill } from 'common/TypedArrayUtils'; diff --git a/src/core/parser/Types.ts b/src/common/parser/Types.ts similarity index 100% rename from src/core/parser/Types.ts rename to src/common/parser/Types.ts diff --git a/src/core/Types.ts b/src/core/Types.ts deleted file mode 100644 index 39cb172e..00000000 --- a/src/core/Types.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from 'common/Types'; - -export const enum KeyboardResultType { - SEND_KEY, - SELECT_ALL, - PAGE_UP, - PAGE_DOWN -} - -export interface IKeyboardResult { - type: KeyboardResultType; - cancel: boolean; - key: string | undefined; -} - -export interface ICharset { - [key: string]: string; -} - -export type CharData = [number, string, number, number]; -export type IColorRGB = [number, number, number]; - -/** Attribute data */ -export interface IAttributeData { - fg: number; - bg: number; - - clone(): IAttributeData; - - // flags - isInverse(): number; - isBold(): number; - isUnderline(): number; - isBlink(): number; - isInvisible(): number; - isItalic(): number; - isDim(): number; - - // color modes - getFgColorMode(): number; - getBgColorMode(): number; - isFgRGB(): boolean; - isBgRGB(): boolean; - isFgPalette(): boolean; - isBgPalette(): boolean; - isFgDefault(): boolean; - isBgDefault(): boolean; - - // colors - getFgColor(): number; - getBgColor(): number; -} - -/** Cell data */ -export interface ICellData extends IAttributeData { - content: number; - combinedData: string; - isCombined(): number; - getWidth(): number; - getChars(): string; - getCode(): number; - setFromCharData(value: CharData): void; - getAsCharData(): CharData; -} - -/** - * Interface for a line in the terminal buffer. - */ -export interface IBufferLine { - length: number; - isWrapped: boolean; - get(index: number): CharData; - set(index: number, value: CharData): void; - loadCell(index: number, cell: ICellData): ICellData; - setCell(index: number, cell: ICellData): void; - setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void; - addCodepointToCell(index: number, codePoint: number): void; - insertCells(pos: number, n: number, ch: ICellData): void; - deleteCells(pos: number, n: number, fill: ICellData): void; - replaceCells(start: number, end: number, fill: ICellData): void; - resize(cols: number, fill: ICellData): void; - fill(fillCellData: ICellData): void; - copyFrom(line: IBufferLine): void; - clone(): IBufferLine; - getTrimmedLength(): number; - translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string; - - /* direct access to cell attrs */ - getWidth(index: number): number; - hasWidth(index: number): number; - getFg(index: number): number; - getBg(index: number): number; - hasContent(index: number): number; - getCodePoint(index: number): number; - isCombined(index: number): number; - getString(index: number): string; -} - -export interface IMarker extends IDisposable { - readonly id: number; - readonly isDisposed: boolean; - readonly line: number; -} diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json deleted file mode 100644 index 5eeac7bc..00000000 --- a/src/core/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../tsconfig-library-base", - "compilerOptions": { - "outDir": "../../out", - "types": [ - "../../node_modules/@types/mocha" - ], - "baseUrl": "..", - "paths": { - "common/*": [ "./common/*" ] - } - }, - "include": [ "./**/*" ], - "references": [ - { "path": "../common" } - ] -} diff --git a/src/handlers/AltClickHandler.ts b/src/handlers/AltClickHandler.ts index eebabd7d..506e6ee1 100644 --- a/src/handlers/AltClickHandler.ts +++ b/src/handlers/AltClickHandler.ts @@ -4,8 +4,7 @@ */ import { ITerminal } from '../Types'; -import { IBufferLine } from 'core/Types'; -import { ICircularList } from 'common/Types'; +import { IBufferLine, ICircularList } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; const enum Direction { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index e6bd5923..f2524381 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -5,7 +5,7 @@ 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 { IBufferLine } from 'core/Types'; +import { IBufferLine } from 'common/Types'; import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; import { IEvent } from 'common/EventEmitter2'; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index f61df580..4b31e270 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -5,12 +5,12 @@ import { IRenderLayer, IRenderDimensions } from './Types'; import { ITerminal } from '../Types'; -import { ICellData } from 'core/Types'; +import { ICellData } from 'common/Types'; import { DEFAULT_COLOR } from 'common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; import { BaseCharAtlas } from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; -import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'core/buffer/BufferLine'; +import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/BufferLine'; import { IColorSet } from 'ui/Types'; export abstract class BaseRenderLayer implements IRenderLayer { diff --git a/src/renderer/CharacterJoinerRegistry.test.ts b/src/renderer/CharacterJoinerRegistry.test.ts index 1ce02b76..e5ab08ea 100644 --- a/src/renderer/CharacterJoinerRegistry.test.ts +++ b/src/renderer/CharacterJoinerRegistry.test.ts @@ -10,8 +10,8 @@ import { CircularList } from 'common/CircularList'; import { ICharacterJoinerRegistry } from './Types'; import { CharacterJoinerRegistry } from './CharacterJoinerRegistry'; -import { BufferLine, CellData } from 'core/buffer/BufferLine'; -import { IBufferLine } from 'core/Types'; +import { BufferLine, CellData } from 'common/buffer/BufferLine'; +import { IBufferLine } from 'common/Types'; describe('CharacterJoinerRegistry', () => { let registry: ICharacterJoinerRegistry; diff --git a/src/renderer/CharacterJoinerRegistry.ts b/src/renderer/CharacterJoinerRegistry.ts index 1432e805..b707d863 100644 --- a/src/renderer/CharacterJoinerRegistry.ts +++ b/src/renderer/CharacterJoinerRegistry.ts @@ -4,9 +4,9 @@ */ import { ITerminal } from '../Types'; -import { IBufferLine, ICellData, CharData } from 'core/Types'; +import { IBufferLine, ICellData, CharData } from 'common/Types'; import { ICharacterJoinerRegistry, ICharacterJoiner } from './Types'; -import { CellData, Content, AttributeData, WHITESPACE_CELL_CHAR } from 'core/buffer/BufferLine'; +import { CellData, Content, AttributeData, WHITESPACE_CELL_CHAR } from 'common/buffer/BufferLine'; export class JoinedCellData extends AttributeData implements ICellData { private _width: number; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index 4534603e..dbf11d55 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -6,8 +6,8 @@ import { IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ITerminal } from '../Types'; -import { ICellData } from 'core/Types'; -import { CellData } from 'core/buffer/BufferLine'; +import { ICellData } from 'common/Types'; +import { CellData } from 'common/buffer/BufferLine'; import { IColorSet } from 'ui/Types'; interface ICursorState { diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 52d8b0f8..4594a2c8 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -5,10 +5,10 @@ import { IRenderDimensions, ICharacterJoinerRegistry } from './Types'; import { ITerminal } from '../Types'; -import { CharData, ICellData } from 'core/Types'; +import { CharData, ICellData } from 'common/Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { CellData, AttributeData, Content, NULL_CELL_CODE } from 'core/buffer/BufferLine'; +import { CellData, AttributeData, Content, NULL_CELL_CODE } from 'common/buffer/BufferLine'; import { JoinedCellData } from './CharacterJoinerRegistry'; import { IColorSet } from 'ui/Types'; diff --git a/src/renderer/dom/DomRendererRowFactory.test.ts b/src/renderer/dom/DomRendererRowFactory.test.ts index e3b26e5d..28ee0a7e 100644 --- a/src/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/renderer/dom/DomRendererRowFactory.test.ts @@ -6,9 +6,9 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { DomRendererRowFactory } from './DomRendererRowFactory'; -import { BufferLine, CellData, FgFlags, BgFlags, Attributes, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, DEFAULT_ATTR_DATA } from 'core/buffer/BufferLine'; +import { BufferLine, CellData, FgFlags, BgFlags, Attributes, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ITerminalOptions } from '../../Types'; -import { IBufferLine } from 'core/Types'; +import { IBufferLine } from 'common/Types'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; diff --git a/src/renderer/dom/DomRendererRowFactory.ts b/src/renderer/dom/DomRendererRowFactory.ts index d3685923..8c31aa44 100644 --- a/src/renderer/dom/DomRendererRowFactory.ts +++ b/src/renderer/dom/DomRendererRowFactory.ts @@ -4,9 +4,9 @@ */ import { ITerminalOptions } from '../../Types'; -import { IBufferLine } from '../../core/Types'; +import { IBufferLine } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { CellData, AttributeData, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from '../../core/buffer/BufferLine'; +import { CellData, AttributeData, NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/BufferLine'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; diff --git a/src/tsconfig.json b/src/tsconfig.json index a42c8338..c99f2b19 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -14,7 +14,6 @@ "baseUrl": ".", "paths": { "common/*": [ "./common/*" ], - "core/*": [ "./core/*" ], "ui/*": [ "./ui/*" ] }, @@ -30,7 +29,6 @@ ], "references": [ { "path": "./common" }, - { "path": "./core" }, { "path": "./ui" } ] } From 98cda5ca868b57950ad40f09cc60ce0ae898145b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 16:12:20 -0700 Subject: [PATCH 13/39] Remove duplicate imports --- src/Terminal.ts | 3 +-- src/TestUtils.test.ts | 3 +-- src/Types.ts | 3 +-- src/renderer/BaseRenderLayer.ts | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 5bc6c6a6..3608b3bf 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -44,9 +44,8 @@ import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; -import { IKeyboardEvent } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'common/Types'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index eb2f43fc..914477ca 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -5,8 +5,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 { IBufferLine, ICellData, IAttributeData } from 'common/Types'; -import { ICircularList, XtermListener } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener } from 'common/Types'; import { Buffer } from './Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; diff --git a/src/Types.ts b/src/Types.ts index c0ce984a..b5c2bd29 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -4,8 +4,7 @@ */ import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; -import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from 'common/Types'; -import { ICircularList } from 'common/Types'; +import { ICharset, IAttributeData, ICellData, IBufferLine, CharData, ICircularList } from 'common/Types'; import { IEvent } from 'common/EventEmitter2'; import { IColorSet } from 'ui/Types'; import { IOptionsService } from 'common/services/Services'; diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 4b31e270..b5836ed9 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -5,8 +5,7 @@ import { IRenderLayer, IRenderDimensions } from './Types'; import { ITerminal } from '../Types'; -import { ICellData } from 'common/Types'; -import { DEFAULT_COLOR } from 'common/Types'; +import { ICellData, DEFAULT_COLOR } from 'common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; import { BaseCharAtlas } from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; From 6e91875bc4c77f4075c906b91cfeb91765f16840 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 16:14:29 -0700 Subject: [PATCH 14/39] Rename ui to browser --- demo/start.js | 3 +-- src/AccessibilityManager.ts | 6 +++--- src/CompositionHelper.ts | 2 +- src/MouseHelper.ts | 2 +- src/MouseZoneManager.ts | 2 +- src/SelectionManager.ts | 2 +- src/Terminal.ts | 8 ++++---- src/TestUtils.test.ts | 4 ++-- src/Types.ts | 2 +- src/Viewport.ts | 6 +++--- src/{ui => browser}/ColorManager.test.ts | 2 +- src/{ui => browser}/ColorManager.ts | 2 +- src/{ui => browser}/Lifecycle.ts | 0 src/{ui => browser}/RenderDebouncer.ts | 0 src/{ui => browser}/ScreenDprMonitor.ts | 0 src/{ui => browser}/Types.ts | 0 src/{ui => browser}/services/CharSizeService.ts | 2 +- src/{ui => browser}/services/Services.d.ts | 0 src/{ui => browser}/tsconfig.json | 0 src/renderer/BaseRenderLayer.ts | 2 +- src/renderer/CursorRenderLayer.ts | 2 +- src/renderer/LinkRenderLayer.ts | 2 +- src/renderer/RenderCoordinator.ts | 10 +++++----- src/renderer/Renderer.ts | 4 ++-- src/renderer/SelectionRenderLayer.ts | 2 +- src/renderer/TextRenderLayer.ts | 2 +- src/renderer/Types.ts | 2 +- src/renderer/atlas/CharAtlasCache.ts | 2 +- src/renderer/atlas/CharAtlasUtils.ts | 2 +- src/renderer/atlas/DynamicCharAtlas.ts | 4 ++-- src/renderer/atlas/Types.ts | 2 +- src/renderer/dom/DomRenderer.ts | 4 ++-- src/tsconfig.json | 4 ++-- webpack.config.js | 3 +-- 34 files changed, 44 insertions(+), 46 deletions(-) rename src/{ui => browser}/ColorManager.test.ts (99%) rename src/{ui => browser}/ColorManager.ts (98%) rename src/{ui => browser}/Lifecycle.ts (100%) rename src/{ui => browser}/RenderDebouncer.ts (100%) rename src/{ui => browser}/ScreenDprMonitor.ts (100%) rename src/{ui => browser}/Types.ts (100%) rename src/{ui => browser}/services/CharSizeService.ts (97%) rename src/{ui => browser}/services/Services.d.ts (100%) rename src/{ui => browser}/tsconfig.json (100%) diff --git a/demo/start.js b/demo/start.js index 20195215..a054e939 100644 --- a/demo/start.js +++ b/demo/start.js @@ -51,8 +51,7 @@ const clientConfig = { extensions: [ '.tsx', '.ts', '.js' ], alias: { common: path.resolve('./out/common'), - core: path.resolve('./out/core'), - ui: path.resolve('./out/ui') + browser: path.resolve('./out/browser') } }, output: { diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index a2a6209c..4ff359d0 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -6,10 +6,10 @@ import * as Strings from './Strings'; import { ITerminal, IBuffer } from './Types'; import { isMac } from 'common/Platform'; -import { RenderDebouncer } from 'ui/RenderDebouncer'; -import { addDisposableDomListener } from 'ui/Lifecycle'; +import { RenderDebouncer } from 'browser/RenderDebouncer'; +import { addDisposableDomListener } from 'browser/Lifecycle'; import { Disposable } from 'common/Lifecycle'; -import { ScreenDprMonitor } from 'ui/ScreenDprMonitor'; +import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { IRenderDimensions } from './renderer/Types'; const MAX_ROWS_TO_READ = 20; diff --git a/src/CompositionHelper.ts b/src/CompositionHelper.ts index e1179fed..2b2d3042 100644 --- a/src/CompositionHelper.ts +++ b/src/CompositionHelper.ts @@ -4,7 +4,7 @@ */ import { ITerminal } from './Types'; -import { ICharSizeService } from 'ui/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; interface IPosition { start: number; diff --git a/src/MouseHelper.ts b/src/MouseHelper.ts index d5662ab0..cf4811a5 100644 --- a/src/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -5,7 +5,7 @@ import { IMouseHelper } from './Types'; import { RenderCoordinator } from './renderer/RenderCoordinator'; -import { ICharSizeService } from 'ui/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; export class MouseHelper implements IMouseHelper { constructor( diff --git a/src/MouseZoneManager.ts b/src/MouseZoneManager.ts index 109bc517..b2ee9b14 100644 --- a/src/MouseZoneManager.ts +++ b/src/MouseZoneManager.ts @@ -5,7 +5,7 @@ import { ITerminal, IMouseZoneManager, IMouseZone } from './Types'; import { Disposable } from 'common/Lifecycle'; -import { addDisposableDomListener } from 'ui/Lifecycle'; +import { addDisposableDomListener } from 'browser/Lifecycle'; const HOVER_DURATION = 500; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 33a087e2..498b4abe 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -12,7 +12,7 @@ import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from 'common/buffer/BufferLine'; import { IDisposable } from 'xterm'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; -import { ICharSizeService } from 'ui/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; /** * The number of pixels the mouse needs to be above or below the viewport in diff --git a/src/Terminal.ts b/src/Terminal.ts index 3608b3bf..a5348568 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -35,7 +35,7 @@ import { Renderer } from './renderer/Renderer'; import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; import * as Browser from 'common/Platform'; -import { addDisposableDomListener } from 'ui/Lifecycle'; +import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from './Strings'; import { MouseHelper } from './MouseHelper'; import { SoundManager } from './SoundManager'; @@ -49,12 +49,12 @@ import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; -import { ColorManager } from 'ui/ColorManager'; +import { ColorManager } from 'browser/ColorManager'; import { RenderCoordinator } from './renderer/RenderCoordinator'; import { IOptionsService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; -import { ICharSizeService } from 'ui/services/Services'; -import { CharSizeService } from 'ui/services/CharSizeService'; +import { ICharSizeService } from 'browser/services/Services'; +import { CharSizeService } from 'browser/services/CharSizeService'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 914477ca..e8fdeeba 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -11,9 +11,9 @@ import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; -import { IColorManager, IColorSet } from 'ui/Types'; +import { IColorManager, IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; -import { ICharSizeService } from 'ui/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; export class TestTerminal extends Terminal { writeSync(data: string): void { diff --git a/src/Types.ts b/src/Types.ts index b5c2bd29..cb6f4178 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -6,7 +6,7 @@ import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { ICharset, IAttributeData, ICellData, IBufferLine, CharData, ICircularList } from 'common/Types'; import { IEvent } from 'common/EventEmitter2'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; diff --git a/src/Viewport.ts b/src/Viewport.ts index 90565a34..6fa51bf8 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -5,10 +5,10 @@ import { ITerminal, IViewport } from './Types'; import { Disposable } from 'common/Lifecycle'; -import { addDisposableDomListener } from 'ui/Lifecycle'; -import { IColorSet } from 'ui/Types'; +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from './renderer/Types'; -import { ICharSizeService } from 'ui/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; const FALLBACK_SCROLL_BAR_WIDTH = 15; diff --git a/src/ui/ColorManager.test.ts b/src/browser/ColorManager.test.ts similarity index 99% rename from src/ui/ColorManager.test.ts rename to src/browser/ColorManager.test.ts index e4d79092..a213c616 100644 --- a/src/ui/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -5,7 +5,7 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; -import { ColorManager } from 'ui/ColorManager'; +import { ColorManager } from 'browser/ColorManager'; describe('ColorManager', () => { let cm: ColorManager; diff --git a/src/ui/ColorManager.ts b/src/browser/ColorManager.ts similarity index 98% rename from src/ui/ColorManager.ts rename to src/browser/ColorManager.ts index f1fe5842..9a574e4a 100644 --- a/src/ui/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IColorManager, IColor, IColorSet, ITheme } from 'ui/Types'; +import { IColorManager, IColor, IColorSet, ITheme } from 'browser/Types'; const DEFAULT_FOREGROUND = fromHex('#ffffff'); const DEFAULT_BACKGROUND = fromHex('#000000'); diff --git a/src/ui/Lifecycle.ts b/src/browser/Lifecycle.ts similarity index 100% rename from src/ui/Lifecycle.ts rename to src/browser/Lifecycle.ts diff --git a/src/ui/RenderDebouncer.ts b/src/browser/RenderDebouncer.ts similarity index 100% rename from src/ui/RenderDebouncer.ts rename to src/browser/RenderDebouncer.ts diff --git a/src/ui/ScreenDprMonitor.ts b/src/browser/ScreenDprMonitor.ts similarity index 100% rename from src/ui/ScreenDprMonitor.ts rename to src/browser/ScreenDprMonitor.ts diff --git a/src/ui/Types.ts b/src/browser/Types.ts similarity index 100% rename from src/ui/Types.ts rename to src/browser/Types.ts diff --git a/src/ui/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts similarity index 97% rename from src/ui/services/CharSizeService.ts rename to src/browser/services/CharSizeService.ts index 61405909..60ce685e 100644 --- a/src/ui/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -5,7 +5,7 @@ import { IOptionsService } from 'common/services/Services'; import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; -import { ICharSizeService } from 'ui/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; export class CharSizeService implements ICharSizeService { public width: number = 0; diff --git a/src/ui/services/Services.d.ts b/src/browser/services/Services.d.ts similarity index 100% rename from src/ui/services/Services.d.ts rename to src/browser/services/Services.d.ts diff --git a/src/ui/tsconfig.json b/src/browser/tsconfig.json similarity index 100% rename from src/ui/tsconfig.json rename to src/browser/tsconfig.json diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index b5836ed9..8a06b1c4 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -10,7 +10,7 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/T import { BaseCharAtlas } from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/BufferLine'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index dbf11d55..b5dfcd0f 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { ITerminal } from '../Types'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/BufferLine'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; interface ICursorState { x: number; diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 38dcf4b1..63cf0335 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -8,7 +8,7 @@ import { IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent = null; diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderCoordinator.ts index dc6e0694..f5916b1d 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderCoordinator.ts @@ -4,15 +4,15 @@ */ import { IRenderer, IRenderDimensions } from './Types'; -import { RenderDebouncer } from 'ui/RenderDebouncer'; +import { RenderDebouncer } from 'browser/RenderDebouncer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { Disposable } from 'common/Lifecycle'; -import { ScreenDprMonitor } from 'ui/ScreenDprMonitor'; -import { addDisposableDomListener } from 'ui/Lifecycle'; -import { IColorSet } from 'ui/Types'; +import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { IColorSet } from 'browser/Types'; import { CharacterJoinerHandler } from '../Types'; import { IOptionsService } from 'common/services/Services'; -import { ICharSizeService } from 'ui/services/Services'; +import { ICharSizeService } from 'browser/services/Services'; export class RenderCoordinator extends Disposable { private _renderDebouncer: RenderDebouncer; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 19adddbf..c56327df 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -11,8 +11,8 @@ import { ITerminal, CharacterJoinerHandler } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; import { Disposable } from 'common/Lifecycle'; -import { IColorSet } from 'ui/Types'; -import { ICharSizeService } from 'ui/services/Services'; +import { IColorSet } from 'browser/Types'; +import { ICharSizeService } from 'browser/services/Services'; export class Renderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index 96bf6ec3..b555ac98 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -6,7 +6,7 @@ import { ITerminal } from '../Types'; import { IRenderDimensions } from './Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; interface ISelectionState { start: [number, number]; diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 4594a2c8..3aeb96c8 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -10,7 +10,7 @@ import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; import { CellData, AttributeData, Content, NULL_CELL_CODE } from 'common/buffer/BufferLine'; import { JoinedCellData } from './CharacterJoinerRegistry'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; /** * This CharData looks like a null character, which will forc a clear and render diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index d8f637eb..017285d8 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -5,7 +5,7 @@ import { ITerminal, CharacterJoinerHandler } from '../Types'; import { IDisposable } from 'xterm'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; /** * Flags used to render terminal text properly. diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index 80c05d6f..43b5eb83 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -8,7 +8,7 @@ import { generateConfig, configEquals } from './CharAtlasUtils'; import { BaseCharAtlas } from './BaseCharAtlas'; import { DynamicCharAtlas } from './DynamicCharAtlas'; import { ICharAtlasConfig } from './Types'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index ab0f3240..b68793f6 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -6,7 +6,7 @@ import { ITerminal } from '../../Types'; import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from 'common/Types'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index 695162c1..c8ccefcb 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -5,10 +5,10 @@ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; -import { DEFAULT_ANSI_COLORS } from 'ui/ColorManager'; +import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; -import { IColor } from 'ui/Types'; +import { IColor } from 'browser/Types'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. diff --git a/src/renderer/atlas/Types.ts b/src/renderer/atlas/Types.ts index 5bfd83bf..2cb1db40 100644 --- a/src/renderer/atlas/Types.ts +++ b/src/renderer/atlas/Types.ts @@ -4,7 +4,7 @@ */ import { FontWeight } from 'xterm'; -import { IColorSet } from 'ui/Types'; +import { IColorSet } from 'browser/Types'; export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 77cfcba8..7c41f028 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -8,8 +8,8 @@ import { ILinkifierEvent, ITerminal, CharacterJoinerHandler } from '../../Types' import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { Disposable } from 'common/Lifecycle'; -import { IColorSet } from 'ui/Types'; -import { ICharSizeService } from 'ui/services/Services'; +import { IColorSet } from 'browser/Types'; +import { ICharSizeService } from 'browser/services/Services'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; diff --git a/src/tsconfig.json b/src/tsconfig.json index c99f2b19..97576668 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -14,7 +14,7 @@ "baseUrl": ".", "paths": { "common/*": [ "./common/*" ], - "ui/*": [ "./ui/*" ] + "browser/*": [ "./browser/*" ] }, "noUnusedLocals": true, @@ -29,6 +29,6 @@ ], "references": [ { "path": "./common" }, - { "path": "./ui" } + { "path": "./browser" } ] } diff --git a/webpack.config.js b/webpack.config.js index 5e0a2754..3f55dda3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -29,8 +29,7 @@ module.exports = { extensions: [ '.js' ], alias: { common: path.resolve('./out/common'), - core: path.resolve('./out/core'), - ui: path.resolve('./out/ui') + browser: path.resolve('./out/browser') } }, output: { From e2cea7578bb02616f1864b880e00b92209e5919e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 16:24:36 -0700 Subject: [PATCH 15/39] 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 16/39] 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 17/39] 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 18/39] 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 19/39] 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 20/39] 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 21/39] 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 22/39] 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 23/39] 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 24/39] 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 25/39] 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 26/39] 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 27/39] 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'; From 9ad01080f326753e0cdf8bd920447d72f8a187a1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 19:24:48 -0700 Subject: [PATCH 28/39] Introduce RenderService Used to be RenderCoordinator --- src/AccessibilityManager.ts | 2 +- src/MouseHelper.ts | 4 +- src/Terminal.ts | 10 ++-- src/TestUtils.test.ts | 4 +- src/Types.ts | 2 - src/Viewport.ts | 2 +- src/browser/ColorManager.ts | 3 +- src/browser/Types.ts | 24 ---------- src/browser/renderer/Types.ts | 47 +++++++++++++++++++ src/browser/services/Services.d.ts | 27 +++++++++++ src/renderer/BaseRenderLayer.ts | 3 +- src/renderer/CursorRenderLayer.ts | 2 +- src/renderer/LinkRenderLayer.ts | 2 +- ...{RenderCoordinator.ts => RenderService.ts} | 5 +- src/renderer/Renderer.ts | 5 +- src/renderer/SelectionRenderLayer.ts | 2 +- src/renderer/TextRenderLayer.ts | 3 +- src/renderer/Types.ts | 41 +--------------- src/renderer/dom/DomRenderer.ts | 4 +- 19 files changed, 103 insertions(+), 89 deletions(-) create mode 100644 src/browser/renderer/Types.ts rename src/renderer/{RenderCoordinator.ts => RenderService.ts} (97%) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 6d47a75f..884db050 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -11,7 +11,7 @@ import { RenderDebouncer } from 'browser/RenderDebouncer'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; -import { IRenderDimensions } from './renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; const MAX_ROWS_TO_READ = 20; diff --git a/src/MouseHelper.ts b/src/MouseHelper.ts index cf4811a5..8ce6543a 100644 --- a/src/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -4,12 +4,12 @@ */ import { IMouseHelper } from './Types'; -import { RenderCoordinator } from './renderer/RenderCoordinator'; +import { RenderService } from './renderer/RenderService'; import { ICharSizeService } from 'browser/services/Services'; export class MouseHelper implements IMouseHelper { constructor( - private _renderCoordinator: RenderCoordinator, + private _renderCoordinator: RenderService, private _charSizeService: ICharSizeService ) { } diff --git a/src/Terminal.ts b/src/Terminal.ts index 851f4d6e..2741ba09 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,8 +21,8 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IMouseZoneManager } from './Types'; -import { IRenderer } from './renderer/Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, IMouseZoneManager } from './Types'; +import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; import { Buffer } from 'common/buffer/Buffer'; import { CompositionHelper } from './CompositionHelper'; @@ -50,7 +50,7 @@ import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from 'browser/ColorManager'; -import { RenderCoordinator } from './renderer/RenderCoordinator'; +import { RenderService } from './renderer/RenderService'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService } from 'browser/services/Services'; @@ -171,7 +171,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _inputHandler: InputHandler; public soundManager: SoundManager; - private _renderCoordinator: RenderCoordinator; + private _renderCoordinator: RenderService; public selectionManager: SelectionManager; public linkifier: ILinkifier; public buffers: BufferSet; @@ -629,7 +629,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._colorManager.setTheme(this._theme); const renderer = this._createRenderer(); - this._renderCoordinator = new RenderCoordinator(renderer, this.rows, this.screenElement, this.optionsService, this._charSizeService); + this._renderCoordinator = new RenderService(renderer, this.rows, this.screenElement, this.optionsService, this._charSizeService); this._renderCoordinator.onRender(e => this._onRender.fire(e)); this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows)); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 4697eefb..a206ec10 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { IRenderer, IRenderDimensions } from './renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler } from './Types'; +import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions } 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'; diff --git a/src/Types.ts b/src/Types.ts index 9dafbf79..cdd693f4 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -17,8 +17,6 @@ export type LineData = CharData[]; export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void; export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; -export type CharacterJoinerHandler = (text: string) => [number, number][]; - /** * This interface encapsulates everything needed from the Terminal by the * InputHandler. This cleanly separates the large amount of methods needed by diff --git a/src/Viewport.ts b/src/Viewport.ts index 6fa51bf8..cd5a282c 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -7,7 +7,7 @@ import { ITerminal, IViewport } from './Types'; import { Disposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from './renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; import { ICharSizeService } from 'browser/services/Services'; const FALLBACK_SCROLL_BAR_WIDTH = 15; diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 9a574e4a..70d21a7a 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { IColorManager, IColor, IColorSet, ITheme } from 'browser/Types'; +import { IColorManager, IColor, IColorSet } from 'browser/Types'; +import { ITheme } from 'common/services/Services'; const DEFAULT_FOREGROUND = fromHex('#ffffff'); const DEFAULT_BACKGROUND = fromHex('#000000'); diff --git a/src/browser/Types.ts b/src/browser/Types.ts index b7b0ac21..ef725ba6 100644 --- a/src/browser/Types.ts +++ b/src/browser/Types.ts @@ -20,27 +20,3 @@ export interface IColorSet { selection: IColor; ansi: IColor[]; } - -export interface ITheme { - foreground?: string; - background?: string; - cursor?: string; - cursorAccent?: string; - selection?: string; - black?: string; - red?: string; - green?: string; - yellow?: string; - blue?: string; - magenta?: string; - cyan?: string; - white?: string; - brightBlack?: string; - brightRed?: string; - brightGreen?: string; - brightYellow?: string; - brightBlue?: string; - brightMagenta?: string; - brightCyan?: string; - brightWhite?: string; -} diff --git a/src/browser/renderer/Types.ts b/src/browser/renderer/Types.ts new file mode 100644 index 00000000..e580ff8d --- /dev/null +++ b/src/browser/renderer/Types.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from 'common/Types'; +import { IColorSet } from 'browser/Types'; + +export type CharacterJoinerHandler = (text: string) => [number, number][]; + +export interface IRenderDimensions { + scaledCharWidth: number; + scaledCharHeight: number; + scaledCellWidth: number; + scaledCellHeight: number; + scaledCharLeft: number; + scaledCharTop: number; + scaledCanvasWidth: number; + scaledCanvasHeight: number; + canvasWidth: number; + canvasHeight: number; + actualCellWidth: number; + actualCellHeight: number; +} + +/** + * Note that IRenderer implementations should emit the refresh event after + * rendering rows to the screen. + */ +export interface IRenderer extends IDisposable { + readonly dimensions: IRenderDimensions; + + dispose(): void; + setColors(colors: IColorSet): void; + onDevicePixelRatioChange(): void; + onResize(cols: number, rows: number): void; + onCharSizeChanged(): void; + onBlur(): void; + onFocus(): void; + onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; + onCursorMove(): void; + onOptionsChanged(): void; + clear(): void; + renderRows(start: number, end: number): void; + registerCharacterJoiner(handler: CharacterJoinerHandler): number; + deregisterCharacterJoiner(joinerId: number): boolean; +} diff --git a/src/browser/services/Services.d.ts b/src/browser/services/Services.d.ts index b7a94ea1..6bdf383a 100644 --- a/src/browser/services/Services.d.ts +++ b/src/browser/services/Services.d.ts @@ -4,6 +4,8 @@ */ import { IEvent } from 'common/EventEmitter2'; +import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { IColorSet } from 'browser/Types'; export interface ICharSizeService { readonly width: number; @@ -14,3 +16,28 @@ export interface ICharSizeService { measure(): void; } + +export interface IRenderService { + onDimensionsChange: IEvent; + onRender: IEvent<{ start: number, end: number }>; + onRefreshRequest: IEvent<{ start: number, end: number }>; + + dimensions: IRenderDimensions; + + refreshRows(start: number, end: number): void; + resize(cols: number, rows: number): void; + changeOptions(): void; + setRenderer(renderer: IRenderer): void; + setColors(colors: IColorSet): void; + onDevicePixelRatioChange(): void; + onResize(cols: number, rows: number): void; + // TODO: Is this useful when we have onResize? + onCharSizeChanged(): void; + onBlur(): void; + onFocus(): void; + onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; + onCursorMove(): void; + clear(): void; + registerCharacterJoiner(handler: CharacterJoinerHandler): number; + deregisterCharacterJoiner(joinerId: number): boolean; +} diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 8a06b1c4..8335f76d 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { IRenderLayer, IRenderDimensions } from './Types'; +import { IRenderLayer } from './Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; import { ITerminal } from '../Types'; import { ICellData, DEFAULT_COLOR } from 'common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types'; diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index b5dfcd0f..9626f772 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderDimensions } from './Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ITerminal } from '../Types'; import { ICellData } from 'common/Types'; diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index 63cf0335..32db3db9 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -4,7 +4,7 @@ */ import { ILinkifierEvent, ITerminal, ILinkifierAccessor } from '../Types'; -import { IRenderDimensions } from './Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; diff --git a/src/renderer/RenderCoordinator.ts b/src/renderer/RenderService.ts similarity index 97% rename from src/renderer/RenderCoordinator.ts rename to src/renderer/RenderService.ts index f5916b1d..409dd84d 100644 --- a/src/renderer/RenderCoordinator.ts +++ b/src/renderer/RenderService.ts @@ -3,18 +3,17 @@ * @license MIT */ -import { IRenderer, IRenderDimensions } from './Types'; +import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet } from 'browser/Types'; -import { CharacterJoinerHandler } from '../Types'; import { IOptionsService } from 'common/services/Services'; import { ICharSizeService } from 'browser/services/Services'; -export class RenderCoordinator extends Disposable { +export class RenderService extends Disposable { private _renderDebouncer: RenderDebouncer; private _screenDprMonitor: ScreenDprMonitor; diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index c56327df..56e12b57 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -6,8 +6,9 @@ import { TextRenderLayer } from './TextRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; -import { IRenderLayer, IRenderer, IRenderDimensions, ICharacterJoinerRegistry } from './Types'; -import { ITerminal, CharacterJoinerHandler } from '../Types'; +import { IRenderLayer, ICharacterJoinerRegistry } from './Types'; +import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { ITerminal } from '../Types'; import { LinkRenderLayer } from './LinkRenderLayer'; import { CharacterJoinerRegistry } from '../renderer/CharacterJoinerRegistry'; import { Disposable } from 'common/Lifecycle'; diff --git a/src/renderer/SelectionRenderLayer.ts b/src/renderer/SelectionRenderLayer.ts index b555ac98..7c297fd8 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/renderer/SelectionRenderLayer.ts @@ -4,7 +4,7 @@ */ import { ITerminal } from '../Types'; -import { IRenderDimensions } from './Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { IColorSet } from 'browser/Types'; diff --git a/src/renderer/TextRenderLayer.ts b/src/renderer/TextRenderLayer.ts index 3aeb96c8..39fcb7fc 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/renderer/TextRenderLayer.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { IRenderDimensions, ICharacterJoinerRegistry } from './Types'; +import { ICharacterJoinerRegistry } from './Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; import { ITerminal } from '../Types'; import { CharData, ICellData } from 'common/Types'; import { GridCache } from './GridCache'; diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index 017285d8..153b68e2 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -3,9 +3,10 @@ * @license MIT */ -import { ITerminal, CharacterJoinerHandler } from '../Types'; +import { ITerminal } from '../Types'; import { IDisposable } from 'xterm'; import { IColorSet } from 'browser/Types'; +import { IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; /** * Flags used to render terminal text properly. @@ -20,44 +21,6 @@ export const enum FLAGS { ITALIC = 64 } -/** - * Note that IRenderer implementations should emit the refresh event after - * rendering rows to the screen. - */ -export interface IRenderer extends IDisposable { - readonly dimensions: IRenderDimensions; - - dispose(): void; - setColors(colors: IColorSet): void; - onDevicePixelRatioChange(): void; - onResize(cols: number, rows: number): void; - onCharSizeChanged(): void; - onBlur(): void; - onFocus(): void; - onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; - onCursorMove(): void; - onOptionsChanged(): void; - clear(): void; - renderRows(start: number, end: number): void; - registerCharacterJoiner(handler: CharacterJoinerHandler): number; - deregisterCharacterJoiner(joinerId: number): boolean; -} - -export interface IRenderDimensions { - scaledCharWidth: number; - scaledCharHeight: number; - scaledCellWidth: number; - scaledCellHeight: number; - scaledCharLeft: number; - scaledCharTop: number; - scaledCanvasWidth: number; - scaledCanvasHeight: number; - canvasWidth: number; - canvasHeight: number; - actualCellWidth: number; - actualCellHeight: number; -} - export interface IRenderLayer extends IDisposable { /** * Called when the terminal loses focus. diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 7c41f028..8d370336 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { IRenderer, IRenderDimensions } from '../Types'; -import { ILinkifierEvent, ITerminal, CharacterJoinerHandler } from '../../Types'; +import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; +import { ILinkifierEvent, ITerminal } from '../../Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from './DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { Disposable } from 'common/Lifecycle'; From 54d7bb0b38b09c107b9b91fceb3fef2c34f7ed5c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 19:26:31 -0700 Subject: [PATCH 29/39] Move RenderService into browser --- src/MouseHelper.ts | 2 +- src/Terminal.ts | 2 +- src/{renderer => browser/services}/RenderService.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/{renderer => browser/services}/RenderService.ts (97%) diff --git a/src/MouseHelper.ts b/src/MouseHelper.ts index 8ce6543a..78968108 100644 --- a/src/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -4,7 +4,7 @@ */ import { IMouseHelper } from './Types'; -import { RenderService } from './renderer/RenderService'; +import { RenderService } from 'browser/services/RenderService'; import { ICharSizeService } from 'browser/services/Services'; export class MouseHelper implements IMouseHelper { diff --git a/src/Terminal.ts b/src/Terminal.ts index 2741ba09..5929aace 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -50,7 +50,7 @@ import { EventEmitter2, IEvent } from 'common/EventEmitter2'; import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from 'browser/ColorManager'; -import { RenderService } from './renderer/RenderService'; +import { RenderService } from 'browser/services/RenderService'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { OptionsService } from 'common/services/OptionsService'; import { ICharSizeService } from 'browser/services/Services'; diff --git a/src/renderer/RenderService.ts b/src/browser/services/RenderService.ts similarity index 97% rename from src/renderer/RenderService.ts rename to src/browser/services/RenderService.ts index 409dd84d..5930569d 100644 --- a/src/renderer/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -11,9 +11,9 @@ import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; -import { ICharSizeService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService } from 'browser/services/Services'; -export class RenderService extends Disposable { +export class RenderService extends Disposable implements IRenderService { private _renderDebouncer: RenderDebouncer; private _screenDprMonitor: ScreenDprMonitor; From cbfbe232d00fd3ff6acb622b3b5469d7f882f731 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 19:28:43 -0700 Subject: [PATCH 30/39] Rename coordinator to service --- addons/xterm-addon-fit/src/FitAddon.ts | 6 +-- demo/client.ts | 4 +- src/MouseHelper.ts | 6 +-- src/Terminal.ts | 58 +++++++++++++------------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/addons/xterm-addon-fit/src/FitAddon.ts b/addons/xterm-addon-fit/src/FitAddon.ts index f2c37141..f23bd161 100644 --- a/addons/xterm-addon-fit/src/FitAddon.ts +++ b/addons/xterm-addon-fit/src/FitAddon.ts @@ -39,7 +39,7 @@ export class FitAddon implements ITerminalAddon { // Force a full render if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) { - core._renderCoordinator.clear(); + core._renderService.clear(); this._terminal.resize(dims.cols, dims.rows); } } @@ -71,8 +71,8 @@ export class FitAddon implements ITerminalAddon { const availableHeight = parentElementHeight - elementPaddingVer; const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth; const geometry = { - cols: Math.floor(availableWidth / core._renderCoordinator.dimensions.actualCellWidth), - rows: Math.floor(availableHeight / core._renderCoordinator.dimensions.actualCellHeight) + cols: Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth), + rows: Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight) }; return geometry; } diff --git a/demo/client.ts b/demo/client.ts index 1841e991..f1626c4f 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -307,8 +307,8 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a function updateTerminalSize(): void { const cols = parseInt((document.getElementById(`opt-cols`)).value, 10); const rows = parseInt((document.getElementById(`opt-rows`)).value, 10); - const width = (cols * term._core._renderCoordinator.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px'; - const height = (rows * term._core._renderCoordinator.dimensions.actualCellHeight).toString() + 'px'; + const width = (cols * term._core._renderService.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px'; + const height = (rows * term._core._renderService.dimensions.actualCellHeight).toString() + 'px'; terminalContainer.style.width = width; terminalContainer.style.height = height; fitAddon.fit(); diff --git a/src/MouseHelper.ts b/src/MouseHelper.ts index 78968108..12e8cf0e 100644 --- a/src/MouseHelper.ts +++ b/src/MouseHelper.ts @@ -9,7 +9,7 @@ import { ICharSizeService } from 'browser/services/Services'; export class MouseHelper implements IMouseHelper { constructor( - private _renderCoordinator: RenderService, + private _renderService: RenderService, private _charSizeService: ICharSizeService ) { } @@ -42,8 +42,8 @@ export class MouseHelper implements IMouseHelper { return null; } - coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderCoordinator.dimensions.actualCellWidth / 2 : 0)) / this._renderCoordinator.dimensions.actualCellWidth); - coords[1] = Math.ceil(coords[1] / this._renderCoordinator.dimensions.actualCellHeight); + coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderService.dimensions.actualCellWidth / 2 : 0)) / this._renderService.dimensions.actualCellWidth); + coords[1] = Math.ceil(coords[1] / this._renderService.dimensions.actualCellHeight); // Ensure coordinates are within the terminal viewport. Note that selections // need an addition point of precision to cover the end point (as characters diff --git a/src/Terminal.ts b/src/Terminal.ts index 5929aace..a48f895c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -111,6 +111,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // browser services private _charSizeService: ICharSizeService; + private _renderService: RenderService; // modes public applicationKeypad: boolean; @@ -171,7 +172,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _inputHandler: InputHandler; public soundManager: SoundManager; - private _renderCoordinator: RenderService; public selectionManager: SelectionManager; public linkifier: ILinkifier; public buffers: BufferSet; @@ -360,8 +360,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II case 'fontFamily': case 'fontSize': // When the font changes the size of the cells may change which requires a renderer clear - if (this._renderCoordinator) { - this._renderCoordinator.clear(); + if (this._renderService) { + this._renderService.clear(); } if (this._charSizeService) { this._charSizeService.measure(); @@ -373,15 +373,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II case 'fontWeight': case 'fontWeightBold': // When the font changes the size of the cells may change which requires a renderer clear - if (this._renderCoordinator) { - this._renderCoordinator.clear(); - this._renderCoordinator.onResize(this.cols, this.rows); + if (this._renderService) { + this._renderService.clear(); + this._renderService.onResize(this.cols, this.rows); this.refresh(0, this.rows - 1); } break; case 'rendererType': - if (this._renderCoordinator) { - this._renderCoordinator.setRenderer(this._createRenderer()); + if (this._renderService) { + this._renderService.setRenderer(this._createRenderer()); } break; case 'scrollback': @@ -392,8 +392,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II break; case 'screenReaderMode': if (this.optionsService.options.screenReaderMode) { - if (!this._accessibilityManager && this._renderCoordinator) { - this._accessibilityManager = new AccessibilityManager(this, this._renderCoordinator.dimensions); + if (!this._accessibilityManager && this._renderService) { + this._accessibilityManager = new AccessibilityManager(this, this._renderService.dimensions); } } else { if (this._accessibilityManager) { @@ -629,24 +629,24 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._colorManager.setTheme(this._theme); const renderer = this._createRenderer(); - this._renderCoordinator = new RenderService(renderer, this.rows, this.screenElement, this.optionsService, this._charSizeService); - this._renderCoordinator.onRender(e => this._onRender.fire(e)); - this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows)); + this._renderService = new RenderService(renderer, this.rows, this.screenElement, this.optionsService, this._charSizeService); + this._renderService.onRender(e => this._onRender.fire(e)); + this.onResize(e => this._renderService.resize(e.cols, e.rows)); - this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderCoordinator.dimensions, this._charSizeService); + this.viewport = new Viewport(this, this._viewportElement, this._viewportScrollArea, this._renderService.dimensions, this._charSizeService); this.viewport.onThemeChange(this._colorManager.colors); this.register(this.viewport); - this.register(this.onCursorMove(() => this._renderCoordinator.onCursorMove())); - this.register(this.onResize(() => this._renderCoordinator.onResize(this.cols, this.rows))); - this.register(this.addDisposableListener('blur', () => this._renderCoordinator.onBlur())); - this.register(this.addDisposableListener('focus', () => this._renderCoordinator.onFocus())); - this.register(this._renderCoordinator.onDimensionsChange(() => this.viewport.syncScrollArea())); + this.register(this.onCursorMove(() => this._renderService.onCursorMove())); + this.register(this.onResize(() => this._renderService.onResize(this.cols, this.rows))); + this.register(this.addDisposableListener('blur', () => this._renderService.onBlur())); + this.register(this.addDisposableListener('focus', () => this._renderService.onFocus())); + this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea())); 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))); + this.register(this.selectionManager.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode))); this.register(this.selectionManager.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it // in order to register it as a selection on the OS. This event is fired @@ -661,7 +661,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this.selectionManager.refresh())); - this.mouseHelper = new MouseHelper(this._renderCoordinator, this._charSizeService); + this.mouseHelper = new MouseHelper(this._renderService, this._charSizeService); // apply mouse event classes set by escape codes before terminal was attached this.element.classList.toggle('enable-mouse-events', this.mouseEvents); if (this.mouseEvents) { @@ -673,8 +673,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II if (this.options.screenReaderMode) { // Note that this must be done *after* the renderer is created in order to // ensure the correct order of the dprchange event - this._accessibilityManager = new AccessibilityManager(this, this._renderCoordinator.dimensions); - this._accessibilityManager.register(this._renderCoordinator.onDimensionsChange(e => this._accessibilityManager.setDimensions(e))); + this._accessibilityManager = new AccessibilityManager(this, this._renderService.dimensions); + this._accessibilityManager.register(this._renderService.onDimensionsChange(e => this._accessibilityManager.setDimensions(e))); } // Measure the character size @@ -707,8 +707,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _setTheme(theme: ITheme): void { this._theme = theme; this._colorManager.setTheme(theme); - if (this._renderCoordinator) { - this._renderCoordinator.setColors(this._colorManager.colors); + if (this._renderService) { + this._renderService.setColors(this._colorManager.colors); } if (this.viewport) { this.viewport.onThemeChange(this._colorManager.colors); @@ -1064,8 +1064,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param end The row to end at (between start and this.rows - 1). */ public refresh(start: number, end: number): void { - if (this._renderCoordinator) { - this._renderCoordinator.refreshRows(start, end); + if (this._renderService) { + this._renderService.refreshRows(start, end); } } @@ -1460,13 +1460,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { - const joinerId = this._renderCoordinator.registerCharacterJoiner(handler); + const joinerId = this._renderService.registerCharacterJoiner(handler); this.refresh(0, this.rows - 1); return joinerId; } public deregisterCharacterJoiner(joinerId: number): void { - if (this._renderCoordinator.deregisterCharacterJoiner(joinerId)) { + if (this._renderService.deregisterCharacterJoiner(joinerId)) { this.refresh(0, this.rows - 1); } } From 5dbab76bed4d67a4c6487aa29e4544cebccab5a1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 19:34:38 -0700 Subject: [PATCH 31/39] Move MouseHelper into browser --- src/MouseHelper.test.ts | 2 +- src/SelectionManager.ts | 2 +- src/Terminal.ts | 2 +- src/TestUtils.test.ts | 4 ++-- src/Types.ts | 7 +------ src/browser/Types.ts | 5 +++++ src/{ => browser/input}/MouseHelper.ts | 16 +++++++--------- 7 files changed, 18 insertions(+), 20 deletions(-) rename src/{ => browser/input}/MouseHelper.ts (89%) diff --git a/src/MouseHelper.test.ts b/src/MouseHelper.test.ts index a0669ec0..6f886d75 100644 --- a/src/MouseHelper.test.ts +++ b/src/MouseHelper.test.ts @@ -5,7 +5,7 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; -import { MouseHelper } from './MouseHelper'; +import { MouseHelper } from './browser/input/MouseHelper'; import { MockRenderer, MockCharSizeService } from './TestUtils.test'; const CHAR_WIDTH = 10; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index fe36a348..1a8d6b6c 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -6,7 +6,7 @@ import { ITerminal, ISelectionManager, ISelectionRedrawRequestEvent } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; -import { MouseHelper } from './MouseHelper'; +import { MouseHelper } from './browser/input/MouseHelper'; import * as Browser from 'common/Platform'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; diff --git a/src/Terminal.ts b/src/Terminal.ts index a48f895c..a18bb209 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -37,7 +37,7 @@ import { SelectionManager } from './SelectionManager'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from './Strings'; -import { MouseHelper } from './MouseHelper'; +import { MouseHelper } from './browser/input/MouseHelper'; import { SoundManager } from './SoundManager'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index a206ec10..f26708a6 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; -import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions } from './Types'; +import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBrowser, ISelectionManager, ITerminalOptions, ILinkifier, ILinkMatcherOptions } 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'; @@ -12,7 +12,7 @@ import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm'; import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; -import { IColorManager, IColorSet } from 'browser/Types'; +import { IColorManager, IColorSet, IMouseHelper } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { ICharSizeService } from 'browser/services/Services'; diff --git a/src/Types.ts b/src/Types.ts index cdd693f4..71987bbe 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -6,7 +6,7 @@ import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { ICharset, IAttributeData, CharData } from 'common/Types'; import { IEvent } from 'common/EventEmitter2'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IMouseHelper } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; @@ -277,11 +277,6 @@ export interface ILinkifierAccessor { linkifier: ILinkifier; } -export interface IMouseHelper { - getCoords(event: { clientX: number, clientY: number }, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number]; - getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number }; -} - // TODO: The options that are not in the public API should be reviewed export interface ITerminalOptions extends IPublicTerminalOptions { [key: string]: any; diff --git a/src/browser/Types.ts b/src/browser/Types.ts index ef725ba6..a1ea662c 100644 --- a/src/browser/Types.ts +++ b/src/browser/Types.ts @@ -20,3 +20,8 @@ export interface IColorSet { selection: IColor; ansi: IColor[]; } + +export interface IMouseHelper { + getCoords(event: { clientX: number, clientY: number }, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined; + getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number | undefined, y: number | undefined }; +} diff --git a/src/MouseHelper.ts b/src/browser/input/MouseHelper.ts similarity index 89% rename from src/MouseHelper.ts rename to src/browser/input/MouseHelper.ts index 12e8cf0e..b99757db 100644 --- a/src/MouseHelper.ts +++ b/src/browser/input/MouseHelper.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IMouseHelper } from './Types'; +import { IMouseHelper } from 'browser/Types'; import { RenderService } from 'browser/services/RenderService'; import { ICharSizeService } from 'browser/services/Services'; @@ -31,15 +31,15 @@ export class MouseHelper implements IMouseHelper { * apply an offset to the x value such that the left half of the cell will * select that cell and the right half will select the next cell. */ - public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] { + public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined { // Coordinates cannot be measured if there are no valid if (!this._charSizeService.hasValidSize) { - return null; + return undefined; } const coords = MouseHelper.getCoordsRelativeToElement(event, element); if (!coords) { - return null; + return undefined; } coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderService.dimensions.actualCellWidth / 2 : 0)) / this._renderService.dimensions.actualCellWidth); @@ -63,14 +63,12 @@ export class MouseHelper implements IMouseHelper { * @param colCount The number of columns in the terminal. * @param rowCount The number of rows in the terminal. */ - public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } { + public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number | undefined, y: number | undefined } { const coords = this.getCoords(event, element, colCount, rowCount); - let x = coords[0]; - let y = coords[1]; // xterm sends raw bytes and starts at 32 (SP) for each. - x += 32; - y += 32; + const x = coords ? coords[0] + 32 : undefined; + const y = coords ? coords[1] + 32 : undefined; return { x, y }; } From e21826f6cbf7a05b2adb592d2e02f8428dc08a0a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 19:40:18 -0700 Subject: [PATCH 32/39] Move MouseHelper.test into browser --- src/CompositionHelper.test.ts | 2 +- src/SelectionManager.test.ts | 3 ++- src/TestUtils.test.ts | 8 -------- src/browser/TestUtils.test.ts | 14 ++++++++++++++ src/{ => browser/input}/MouseHelper.test.ts | 19 ++++++++++--------- 5 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 src/browser/TestUtils.test.ts rename src/{ => browser/input}/MouseHelper.test.ts (78%) diff --git a/src/CompositionHelper.test.ts b/src/CompositionHelper.test.ts index 156f5a44..2d28f55d 100644 --- a/src/CompositionHelper.test.ts +++ b/src/CompositionHelper.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { CompositionHelper } from './CompositionHelper'; import { ITerminal } from './Types'; -import { MockCharSizeService } from 'TestUtils.test'; +import { MockCharSizeService } from 'browser/TestUtils.test'; describe('CompositionHelper', () => { let terminal: ITerminal; diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 87f7386a..5dc692af 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -10,10 +10,11 @@ import { BufferSet } from 'common/buffer/BufferSet'; import { ITerminal } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; -import { MockTerminal, MockCharSizeService } from './TestUtils.test'; +import { MockTerminal } from './TestUtils.test'; import { MockOptionsService, MockBufferService } from 'common/TestUtils.test'; import { BufferLine, CellData } from 'common/buffer/BufferLine'; import { IBufferService } from 'common/services/Services'; +import { MockCharSizeService } from 'browser/TestUtils.test'; class TestMockTerminal extends MockTerminal { emit(event: string, data: any): void {} diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index f26708a6..bbe09508 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -14,7 +14,6 @@ import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; import { IColorManager, IColorSet, IMouseHelper } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; -import { ICharSizeService } from 'browser/services/Services'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -428,10 +427,3 @@ export class MockCompositionHelper implements ICompositionHelper { return true; } } - -export class MockCharSizeService implements ICharSizeService { - get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - onCharSizeChange: IEvent; - constructor(public width: number, public height: number) {} - measure(): void {} -} diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts new file mode 100644 index 00000000..7c965c8d --- /dev/null +++ b/src/browser/TestUtils.test.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; +import { ICharSizeService } from 'browser/services/Services'; + +export class MockCharSizeService implements ICharSizeService { + get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } + onCharSizeChange: IEvent = new EventEmitter2().event; + constructor(public width: number, public height: number) {} + measure(): void {} +} diff --git a/src/MouseHelper.test.ts b/src/browser/input/MouseHelper.test.ts similarity index 78% rename from src/MouseHelper.test.ts rename to src/browser/input/MouseHelper.test.ts index 6f886d75..5d4b567c 100644 --- a/src/MouseHelper.test.ts +++ b/src/browser/input/MouseHelper.test.ts @@ -5,8 +5,8 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; -import { MouseHelper } from './browser/input/MouseHelper'; -import { MockRenderer, MockCharSizeService } from './TestUtils.test'; +import { MouseHelper } from 'browser/input/MouseHelper'; +import { MockCharSizeService } from 'browser/TestUtils.test'; const CHAR_WIDTH = 10; const CHAR_HEIGHT = 20; @@ -17,16 +17,17 @@ describe('MouseHelper.getCoords', () => { beforeEach(() => { document = new jsdom.JSDOM('').window.document; - const renderer = new MockRenderer(); - renderer.dimensions = { - actualCellWidth: CHAR_WIDTH, - actualCellHeight: CHAR_HEIGHT + const mockRenderService = { + dimensions: { + actualCellWidth: CHAR_WIDTH, + actualCellHeight: CHAR_HEIGHT + } }; - mouseHelper = new MouseHelper(renderer as any, new MockCharSizeService(CHAR_WIDTH, CHAR_HEIGHT)); + mouseHelper = new MouseHelper(mockRenderService as any, new MockCharSizeService(CHAR_WIDTH, CHAR_HEIGHT)); }); it('should return the cell that was clicked', () => { - let coords: [number, number]; + let coords: [number, number] | undefined; coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10); assert.deepEqual(coords, [1, 1]); coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10); @@ -38,7 +39,7 @@ describe('MouseHelper.getCoords', () => { }); it('should ensure the coordinates are returned within the terminal bounds', () => { - let coords: [number, number]; + let coords: [number, number] | undefined; coords = mouseHelper.getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10); assert.deepEqual(coords, [1, 1]); // Event are double the cols/rows From 09b0bbed32e782d8ec2ea5bec0621828217147c9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 20:21:29 -0700 Subject: [PATCH 33/39] Move buffer/buffers ownership to BufferService --- src/Terminal.ts | 12 ++++++------ src/TestUtils.test.ts | 4 ++++ src/common/TestUtils.test.ts | 3 +++ src/common/buffer/Types.ts | 6 +++++- src/common/services/BufferService.ts | 8 +++++++- src/common/services/Services.d.ts | 3 +++ 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index a18bb209..35b97bf1 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -23,8 +23,6 @@ import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, IMouseZoneManager } from './Types'; import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; -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'; @@ -56,6 +54,7 @@ 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'; +import { IBufferSet, IBuffer } from '../out/common/buffer/Types'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -174,7 +173,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II public soundManager: SoundManager; public selectionManager: SelectionManager; public linkifier: ILinkifier; - public buffers: BufferSet; public viewport: IViewport; private _compositionHelper: ICompositionHelper; private _mouseZoneManager: IMouseZoneManager; @@ -312,8 +310,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this._mouseZoneManager = this._mouseZoneManager || null; this.soundManager = this.soundManager || new SoundManager(this); - // Create the terminal's buffers and set the current buffer - this.buffers = new BufferSet(this.optionsService, this._bufferService); if (this.selectionManager) { this.selectionManager.clearSelection(); this.selectionManager.initBuffersListeners(); @@ -327,10 +323,14 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II /** * Convenience property to active buffer. */ - public get buffer(): Buffer { + public get buffer(): IBuffer { return this.buffers.active; } + public get buffers(): IBufferSet { + return this._bufferService.buffers; + } + /** * back_color_erase feature for xterm. */ diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index bbe09508..79e36a6d 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -305,6 +305,10 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { } export class MockBuffer implements IBuffer { + markers: IMarker[]; + addMarker(y: number): IMarker { + throw new Error('Method not implemented.'); + } isCursorInViewport: boolean; lines: ICircularList; ydisp: number; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 0a0cb6a4..358fdb17 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -7,8 +7,11 @@ import { IBufferService, IOptionsService, ITerminalOptions, IPartialTerminalOpti import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; +import { IBufferSet, IBuffer } from './buffer/Types'; export class MockBufferService implements IBufferService { + public buffer: IBuffer = {} as any; + public buffers: IBufferSet = {} as any; constructor( public cols: number, public rows: number diff --git a/src/common/buffer/Types.ts b/src/common/buffer/Types.ts index 37ce2b7e..19794c38 100644 --- a/src/common/buffer/Types.ts +++ b/src/common/buffer/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IAttributeData, ICircularList, IBufferLine, ICellData } from 'common/Types'; +import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker } from 'common/Types'; import { IEvent } from 'common/EventEmitter2'; // BufferIndex denotes a position in the buffer: [rowIndex, colIndex] @@ -33,6 +33,7 @@ export interface IBuffer { savedX: number; savedCurAttrData: IAttributeData; isCursorInViewport: boolean; + markers: IMarker[]; translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; getWrappedRangeForLine(y: number): { first: number, last: number }; nextStop(x?: number): number; @@ -42,6 +43,7 @@ export interface IBuffer { iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator; getNullCell(attr?: IAttributeData): ICellData; getWhitespaceCell(attr?: IAttributeData): ICellData; + addMarker(y: number): IMarker; } export interface IBufferSet { @@ -53,4 +55,6 @@ export interface IBufferSet { activateNormalBuffer(): void; activateAltBuffer(fillAttr?: IAttributeData): void; + resize(newCols: number, newRows: number): void; + setupTabStops(i?: number): void; } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 542166c1..bbac7803 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -3,7 +3,9 @@ * @license MIT */ -import { IBufferService, IOptionsService } from './Services'; +import { IBufferService, IOptionsService } from 'common/services/Services'; +import { BufferSet } from 'common/buffer/BufferSet'; +import { IBufferSet, IBuffer } from 'common/buffer/Types'; export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars export const MINIMUM_ROWS = 1; @@ -11,12 +13,16 @@ export const MINIMUM_ROWS = 1; export class BufferService implements IBufferService { public cols: number; public rows: number; + public buffers: IBufferSet; + + public get buffer(): IBuffer { return this.buffers.active; } constructor( optionsService: IOptionsService ) { this.cols = Math.max(optionsService.options.cols, MINIMUM_COLS); this.rows = Math.max(optionsService.options.rows, MINIMUM_ROWS); + this.buffers = new BufferSet(optionsService, this); } public resize(cols: number, rows: number): void { diff --git a/src/common/services/Services.d.ts b/src/common/services/Services.d.ts index 8ebc0308..0da78fc7 100644 --- a/src/common/services/Services.d.ts +++ b/src/common/services/Services.d.ts @@ -4,10 +4,13 @@ */ import { IEvent } from 'common/EventEmitter2'; +import { IBuffer, IBufferSet } from 'common/buffer/Types'; export interface IBufferService { readonly cols: number; readonly rows: number; + readonly buffer: IBuffer; + readonly buffers: IBufferSet; // TODO: Move resize event here From f49daf02a399cad1794b66d8a2a07a98c86ed1d8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 20:51:39 -0700 Subject: [PATCH 34/39] Fix tests and imports --- src/SelectionManager.ts | 2 +- src/Terminal.ts | 11 ++++------- src/common/TestUtils.test.ts | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1a8d6b6c..138e861d 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -6,7 +6,7 @@ import { ITerminal, ISelectionManager, ISelectionRedrawRequestEvent } from './Types'; import { IBuffer } from 'common/buffer/Types'; import { IBufferLine } from 'common/Types'; -import { MouseHelper } from './browser/input/MouseHelper'; +import { MouseHelper } from 'browser/input/MouseHelper'; import * as Browser from 'common/Platform'; import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; diff --git a/src/Terminal.ts b/src/Terminal.ts index 35b97bf1..087fb379 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -35,7 +35,7 @@ import { SelectionManager } from './SelectionManager'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from './Strings'; -import { MouseHelper } from './browser/input/MouseHelper'; +import { MouseHelper } from 'browser/input/MouseHelper'; import { SoundManager } from './SoundManager'; import { MouseZoneManager } from './MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; @@ -54,7 +54,7 @@ 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'; -import { IBufferSet, IBuffer } from '../out/common/buffer/Types'; +import { IBufferSet, IBuffer } from 'common/buffer/Types'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -224,14 +224,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II ) { super(); - // Initialize common services + // Setup and initialize common services this.optionsService = new OptionsService(options); - this._bufferService = new BufferService(this.optionsService); - this._setupOptionsListeners(); - - // this.options = clone(options); this._setup(); + this._bufferService = new BufferService(this.optionsService); // TODO: Remove these in v4 // Fire old style events from new emitters diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 358fdb17..2d699cf8 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IBufferService, IOptionsService, ITerminalOptions, IPartialTerminalOpti import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; -import { IBufferSet, IBuffer } from './buffer/Types'; +import { IBufferSet, IBuffer } from 'common/buffer/Types'; export class MockBufferService implements IBufferService { public buffer: IBuffer = {} as any; From 447fc4f3fa0a63a25b70ce7cf9235e433a8bd0e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 21:28:03 -0700 Subject: [PATCH 35/39] Remove old event emitter Fixes #2029 --- src/AccessibilityManager.ts | 6 +- src/Clipboard.ts | 1 - src/InputHandler.ts | 6 +- src/Terminal.test.ts | 176 +++----------------------------- src/Terminal.ts | 42 +++----- src/TestUtils.test.ts | 7 ++ src/Types.ts | 16 ++- src/common/EventEmitter.test.ts | 77 -------------- src/common/EventEmitter.ts | 98 ------------------ src/public/Terminal.ts | 21 ---- typings/xterm.d.ts | 99 +----------------- 11 files changed, 53 insertions(+), 496 deletions(-) delete mode 100644 src/common/EventEmitter.test.ts delete mode 100644 src/common/EventEmitter.ts diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 884db050..58949cba 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -85,11 +85,11 @@ export class AccessibilityManager extends Disposable { this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end))); this.register(this._terminal.onScroll(() => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run - this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); + this.register(this._terminal.onA11yChar(char => this._onChar(char))); this.register(this._terminal.onLineFeed(() => this._onChar('\n'))); - this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount))); + this.register(this._terminal.onA11yTab(spaceCount => this._onTab(spaceCount))); this.register(this._terminal.onKey(e => this._onKey(e.key))); - this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion())); + this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this._screenDprMonitor = new ScreenDprMonitor(); this.register(this._screenDprMonitor); diff --git a/src/Clipboard.ts b/src/Clipboard.ts index 7a32badc..cbb0935e 100644 --- a/src/Clipboard.ts +++ b/src/Clipboard.ts @@ -63,7 +63,6 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void { text = bracketTextForPaste(text, term.bracketedPasteMode); term.handler(text); term.textarea.value = ''; - term.emit('paste', text); term.cancel(ev); }; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index b4689cd5..2b592aa7 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -342,7 +342,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer = this._terminal.buffer; if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) { - this._terminal.emit('cursormove'); + this._onCursorMove.fire(); } } @@ -377,7 +377,7 @@ export class InputHandler extends Disposable implements IInputHandler { } if (screenReaderMode) { - this._terminal.emit('a11y.char', stringFromCodePoint(code)); + this._terminal.onA11yCharEmitter.fire(stringFromCodePoint(code)); } // insert combining char at last cursor position @@ -542,7 +542,7 @@ export class InputHandler extends Disposable implements IInputHandler { const originalX = this._terminal.buffer.x; this._terminal.buffer.x = this._terminal.buffer.nextStop(); if (this._terminal.options.screenReaderMode) { - this._terminal.emit('a11y.tab', this._terminal.buffer.x - originalX); + this._terminal.onA11yTabEmitter.fire(this._terminal.buffer.x - originalX); } } diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index a3cff41f..36886da1 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -57,20 +57,20 @@ describe('Terminal', () => { term.handler('fake'); }); it('should fire the onCursorMove event', (done) => { - term.on('cursormove', () => done()); + term.onCursorMove(() => done()); term.write('foo'); }); it('should fire the onLineFeed event', (done) => { - term.on('linefeed', () => done()); + term.onLineFeed(() => done()); term.write('\n'); }); it('should fire a scroll event when scrollback is created', (done) => { - term.on('scroll', () => done()); + term.onScroll(() => done()); term.write('\n'.repeat(INIT_ROWS)); }); it('should fire a scroll event when scrollback is cleared', (done) => { term.write('\n'.repeat(INIT_ROWS)); - term.on('scroll', () => done()); + term.onScroll(() => done()); term.clear(); }); it('should fire a key event after a keypress DOM event', (done) => { @@ -126,158 +126,6 @@ describe('Terminal', () => { }); }); - describe('on', () => { - beforeEach(() => { - term.on('key', () => { }); - term.on('keypress', () => { }); - term.on('keydown', () => { }); - }); - - describe('data', () => { - it('should emit a data event', (done) => { - term.on('data', () => { - done(); - }); - - term.handler('fake'); - }); - }); - - describe('cursormove', () => { - it('should emit a cursormove event', (done) => { - term.on('cursormove', () => { - done(); - }); - term.write('foo'); - }); - }); - - describe('linefeed', () => { - it('should emit a linefeed event', (done) => { - term.on('linefeed', () => { - done(); - }); - term.write('\n'); - }); - }); - - describe('scroll', () => { - it('should emit a scroll event when scrollback is created', (done) => { - term.on('scroll', () => { - done(); - }); - term.write('\n'.repeat(INIT_ROWS)); - }); - it('should emit a scroll event when scrollback is cleared', (done) => { - term.write('\n'.repeat(INIT_ROWS)); - term.on('scroll', () => { - done(); - }); - term.clear(); - }); - }); - - describe(`keypress (including 'key' event)`, () => { - it('should receive a string and event object', (done) => { - let steps = 0; - - const finish = () => { - if ((++steps) === 2) { - done(); - } - }; - - const evKeyPress = { - preventDefault: () => { }, - stopPropagation: () => { }, - type: 'keypress', - keyCode: 13 - }; - - term.on('keypress', (key, event) => { - assert.equal(typeof key, 'string'); - expect(event).to.be.an.instanceof(Object); - finish(); - }); - - term.on('key', (key, event) => { - assert.equal(typeof key, 'string'); - expect(event).to.be.an.instanceof(Object); - finish(); - }); - - term.keyPress(evKeyPress); - }); - }); - - describe(`keydown (including 'key' event)`, () => { - it(`should receive an event object for 'keydown' and a string and event object for 'key'`, (done) => { - let steps = 0; - - const finish = () => { - if ((++steps) === 2) { - done(); - } - }; - - const evKeyDown = { - preventDefault: () => { }, - stopPropagation: () => { }, - type: 'keydown', - keyCode: 13 - }; - - term.on('keydown', (event) => { - expect(event).to.be.an.instanceof(Object); - finish(); - }); - - term.on('key', (key, event) => { - assert.equal(typeof key, 'string'); - expect(event).to.be.an.instanceof(Object); - finish(); - }); - - term.keyDown(evKeyDown); - }); - }); - - describe('resize', () => { - it('should receive an object: {cols: number, rows: number}', (done) => { - term.on('resize', (data) => { - expect(data).to.have.keys(['cols', 'rows']); - assert.equal(typeof data.cols, 'number'); - assert.equal(typeof data.rows, 'number'); - done(); - }); - - term.resize(1, 1); - }); - }); - - describe('scroll', () => { - it('should receive a number', (done) => { - term.on('scroll', (ydisp) => { - assert.equal(typeof ydisp, 'number'); - done(); - }); - - term.scroll(); - }); - }); - - describe('title', () => { - it('should receive a string', (done) => { - term.on('title', (title) => { - assert.equal(typeof title, 'string'); - done(); - }); - - term.handleTitle('title'); - }); - }); - }); - describe('attachCustomKeyEventHandler', () => { const evKeyDown = { preventDefault: () => { }, @@ -741,10 +589,10 @@ describe('Terminal', () => { it('should emit key with alt + key on keyPress', (done) => { const keys = ['@', '@', '\\', '\\', '|', '|']; - term.on('keypress', (key) => { - if (key) { - const index = keys.indexOf(key); - assert(index !== -1, 'Emitted wrong key: ' + key); + term.onKey(e => { + if (e.key) { + const index = keys.indexOf(e.key); + assert(index !== -1, 'Emitted wrong key: ' + e.key); keys.splice(index, 1); } if (keys.length === 0) done(); @@ -807,10 +655,10 @@ describe('Terminal', () => { it('should emit key with alt + ctrl + key on keyPress', (done) => { const keys = ['@', '@', '\\', '\\', '|', '|']; - term.on('keypress', (key) => { - if (key) { - const index = keys.indexOf(key); - assert(index !== -1, 'Emitted wrong key: ' + key); + term.onKey(e => { + if (e.key) { + const index = keys.indexOf(e.key); + assert(index !== -1, 'Emitted wrong key: ' + e.key); keys.splice(index, 1); } if (keys.length === 0) done(); diff --git a/src/Terminal.ts b/src/Terminal.ts index a18bb209..f99b6a9d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -26,7 +26,6 @@ import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; 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'; import { rightClickHandler, moveTextAreaUnderMouseCursor, pasteHandler, copyHandler } from './Clipboard'; import { C0 } from 'common/data/EscapeSequences'; @@ -56,6 +55,7 @@ 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'; +import { Disposable } from 'common/Lifecycle'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -76,7 +76,7 @@ const WRITE_BUFFER_PAUSE_THRESHOLD = 5; const WRITE_TIMEOUT_MS = 12; const WRITE_BUFFER_LENGTH_THRESHOLD = 50; -export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { +export class Terminal extends Disposable implements ITerminal, IDisposable, IInputHandlingTerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; public screenElement: HTMLElement; @@ -209,6 +209,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II private _onTitleChange = new EventEmitter2(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } + private _onFocus = new EventEmitter2(); + public get onFocus(): IEvent { return this._onFocus.event; } + private _onBlur = new EventEmitter2(); + public get onBlur(): IEvent { return this._onBlur.event; } + public onA11yCharEmitter = new EventEmitter2(); + public get onA11yChar(): IEvent { return this.onA11yCharEmitter.event; } + public onA11yTabEmitter = new EventEmitter2(); + public get onA11yTab(): IEvent { return this.onA11yTabEmitter.event; } + /** * Creates a new `Terminal` object. * @@ -234,18 +243,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // this.options = clone(options); this._setup(); - - // TODO: Remove these in v4 - // Fire old style events from new emitters - this.onCursorMove(() => this.emit('cursormove')); - this.onData(e => this.emit('data', e)); - this.onKey(e => this.emit('key', e.key, e.domEvent)); - this.onLineFeed(() => this.emit('linefeed')); - this.onRender(e => this.emit('refresh', e)); - this.onResize(e => this.emit('resize', e)); - this.onSelectionChange(() => this.emit('selection')); - this.onScroll(e => this.emit('scroll', e)); - this.onTitleChange(e => this.emit('title', e)); } public dispose(): void { @@ -444,7 +441,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.updateCursorStyle(ev); this.element.classList.add('focus'); this.showCursor(); - this.emit('focus'); + this._onFocus.fire(); } /** @@ -467,7 +464,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.handler(C0.ESC + '[O'); } this.element.classList.remove('focus'); - this.emit('blur'); + this._onBlur.fire(); } /** @@ -639,8 +636,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.register(this.onCursorMove(() => this._renderService.onCursorMove())); this.register(this.onResize(() => this._renderService.onResize(this.cols, this.rows))); - this.register(this.addDisposableListener('blur', () => this._renderService.onBlur())); - this.register(this.addDisposableListener('focus', () => this._renderService.onFocus())); + this.register(this.onBlur(() => this._renderService.onBlur())); + this.register(this.onFocus(() => this._renderService.onFocus())); this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea())); this.selectionManager = new SelectionManager(this, this._charSizeService, this._bufferService); @@ -803,12 +800,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r // locator: CSI P e ; P b ; P r ; P c ; P p & w function sendEvent(button: number, pos: {x: number, y: number}): void { - // self.emit('mouse', { - // x: pos.x - 32, - // y: pos.x - 32, - // button: button - // }); - if (self._vt300Mouse) { // NOTE: Unstable. // http://www.vt100.net/docs/vt3xx-gp/chapter15.html @@ -1597,7 +1588,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II return true; } - this.emit('keydown', event); this._onKey.fire({ key: result.key, domEvent: event }); this.showCursor(); this.handler(result.key); @@ -1676,7 +1666,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II key = String.fromCharCode(key); - this.emit('keypress', key, ev); this._onKey.fire({ key, domEvent: ev }); this.showCursor(); this.handler(key); @@ -1689,7 +1678,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * Note: We could do sweet things with webaudio here */ public bell(): void { - this.emit('bell'); if (this._soundBell()) { this.soundManager.playBellSound(); } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index bbe09508..4f293eed 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -14,6 +14,7 @@ import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; import { IColorManager, IColorSet, IMouseHelper } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; +import { EventEmitter2 } from 'common/EventEmitter2'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -23,6 +24,10 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { + onBlur: IEvent; + onFocus: IEvent; + onA11yChar: IEvent; + onA11yTab: IEvent; onCursorMove: IEvent; onLineFeed: IEvent; onSelectionChange: IEvent; @@ -181,6 +186,8 @@ export class MockTerminal implements ITerminal { } export class MockInputHandlingTerminal implements IInputHandlingTerminal { + onA11yCharEmitter: EventEmitter2; + onA11yTabEmitter: EventEmitter2; element: HTMLElement; options: ITerminalOptions = {}; cols: number; diff --git a/src/Types.ts b/src/Types.ts index 71987bbe..536f7cca 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm'; +import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { ICharset, IAttributeData, CharData } from 'common/Types'; -import { IEvent } from 'common/EventEmitter2'; +import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; import { IColorSet, IMouseHelper } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; @@ -22,7 +22,7 @@ export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: bo * InputHandler. This cleanly separates the large amount of methods needed by * InputHandler cleanly from the ITerminal interface. */ -export interface IInputHandlingTerminal extends IEventEmitter { +export interface IInputHandlingTerminal { element: HTMLElement; options: ITerminalOptions; cols: number; @@ -54,6 +54,9 @@ export interface IInputHandlingTerminal extends IEventEmitter { viewport: IViewport; selectionManager: ISelectionManager; + onA11yCharEmitter: EventEmitter2; + onA11yTabEmitter: EventEmitter2; + bell(): void; focus(): void; updateRange(y: number): void; @@ -209,6 +212,11 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc // TODO: We should remove options once components adopt optionsService options: ITerminalOptions; + onBlur: IEvent; + onFocus: IEvent; + onA11yChar: IEvent; + onA11yTab: IEvent; + handler(data: string): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; cancel(ev: Event, force?: boolean): boolean | void; @@ -217,7 +225,7 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc } // Portions of the public API that are required by the internal Terminal -export interface IPublicTerminal extends IDisposable, IEventEmitter { +export interface IPublicTerminal extends IDisposable { textarea: HTMLTextAreaElement; rows: number; cols: number; diff --git a/src/common/EventEmitter.test.ts b/src/common/EventEmitter.test.ts deleted file mode 100644 index c7f75ba0..00000000 --- a/src/common/EventEmitter.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { EventEmitter } from 'common/EventEmitter'; - -describe('EventEmitter', () => { - let eventEmitter: EventEmitter; - - beforeEach(() => { - eventEmitter = new EventEmitter(); - }); - - describe('emit', () => { - it('should emit events to listeners', () => { - let count1 = 0; - let count2 = 0; - const listener1 = () => count1++; - const listener2 = () => count2++; - eventEmitter.on('test', listener1); - eventEmitter.on('test', listener2); - eventEmitter.emit('test'); - assert.equal(count1, 1); - assert.equal(count2, 1); - eventEmitter.emit('test'); - assert.equal(count1, 2); - assert.equal(count2, 2); - }); - - it('should manage multiple listener types', () => { - let count1 = 0; - let count2 = 0; - const listener1 = () => count1++; - const listener2 = () => count2++; - eventEmitter.on('test', listener1); - eventEmitter.on('foo', listener2); - eventEmitter.emit('test'); - assert.equal(count1, 1); - assert.equal(count2, 0); - eventEmitter.emit('foo'); - assert.equal(count1, 1); - assert.equal(count2, 1); - }); - }); - - describe('listeners', () => { - it('should return listeners for the type requested', () => { - assert.equal(eventEmitter.listeners('test').length, 0); - const listener = () => {}; - eventEmitter.on('test', listener); - assert.deepEqual(eventEmitter.listeners('test'), [listener]); - }); - }); - - describe('off', () => { - it('should remove the specific listener', () => { - const listener1 = () => {}; - const listener2 = () => {}; - eventEmitter.on('foo', listener1); - eventEmitter.on('foo', listener2); - assert.equal(eventEmitter.listeners('foo').length, 2); - eventEmitter.off('foo', listener1); - assert.deepEqual(eventEmitter.listeners('foo'), [listener2]); - }); - }); - - describe('removeAllListeners', () => { - it('should clear all listeners', () => { - eventEmitter.on('foo', () => {}); - assert.equal(eventEmitter.listeners('foo').length, 1); - eventEmitter.removeAllListeners('foo'); - assert.equal(eventEmitter.listeners('foo').length, 0); - }); - }); -}); diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts deleted file mode 100644 index 266216a4..00000000 --- a/src/common/EventEmitter.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable, IEventEmitter, XtermListener } from 'common/Types'; -import { Disposable } from 'common/Lifecycle'; - -export class EventEmitter extends Disposable implements IEventEmitter, IDisposable { - private _events: {[type: string]: XtermListener[]}; - - constructor() { - super(); - // Restore the previous events if available, this will happen if the - // constructor is called multiple times on the same object (terminal reset). - this._events = (this)._events || {}; - } - - public on(type: string, listener: XtermListener): void { - this._events[type] = this._events[type] || []; - this._events[type].push(listener); - } - - /** - * Adds a disposable listener to the EventEmitter, returning the disposable. - * @param type The event type. - * @param handler The handler for the listener. - */ - public addDisposableListener(type: string, handler: XtermListener): IDisposable { - // TODO: Rename addDisposableEventListener to more easily disambiguate from Dom listener - this.on(type, handler); - let disposed = false; - return { - dispose: () => { - if (disposed) { - // Already disposed - return; - } - this.off(type, handler); - disposed = true; - } - }; - } - - public off(type: string, listener: XtermListener): void { - if (!this._events[type]) { - return; - } - - const obj = this._events[type]; - let i = obj.length; - - while (i--) { - if (obj[i] === listener) { - obj.splice(i, 1); - return; - } - } - } - - public removeAllListeners(type: string): void { - if (this._events[type]) { - delete this._events[type]; - } - } - - public emit(type: string, ...args: any[]): void { - if (!this._events[type]) { - return; - } - const obj = this._events[type]; - for (let i = 0; i < obj.length; i++) { - obj[i].apply(this, args); - } - } - - public emitMayRemoveListeners(type: string, ...args: any[]): void { - if (!this._events[type]) { - return; - } - const obj = this._events[type]; - let length = obj.length; - for (let i = 0; i < obj.length; i++) { - obj[i].apply(this, args); - i -= length - obj.length; - length = obj.length; - } - } - - public listeners(type: string): XtermListener[] { - return this._events[type] || []; - } - - public dispose(): void { - super.dispose(); - this._events = {}; - } -} diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 5c7b4b99..2ccbdef6 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -43,27 +43,6 @@ export class Terminal implements ITerminalApi { public focus(): void { this._core.focus(); } - public on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void; - public on(type: 'data', listener: (...args: any[]) => void): void; - public on(type: 'key', listener: (key?: string, event?: KeyboardEvent) => void): void; - public on(type: 'keypress' | 'keydown', listener: (event?: KeyboardEvent) => void): void; - public on(type: 'refresh', listener: (data?: { start: number; end: number; }) => void): void; - public on(type: 'resize', listener: (data?: { cols: number; rows: number; }) => void): void; - public on(type: 'scroll', listener: (ydisp?: number) => void): void; - public on(type: 'title', listener: (title?: string) => void): void; - public on(type: string, listener: (...args: any[]) => void): void; - public on(type: any, listener: any): void { - this._core.on(type, listener); - } - public off(type: string, listener: (...args: any[]) => void): void { - this._core.off(type, listener); - } - public emit(type: string, data?: any): void { - this._core.emit(type, data); - } - public addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable { - return this._core.addDisposableListener(type, handler); - } public resize(columns: number, rows: number): void { this._core.resize(columns, rows); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 22446e36..3dc08782 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -268,13 +268,6 @@ declare module 'xterm' { willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } - export interface IEventEmitter { - on(type: string, listener: (...args: any[]) => void): void; - off(type: string, listener: (...args: any[]) => void): void; - emit(type: string, data?: any): void; - addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable; - } - /** * An object that can be disposed via a dispose function. */ @@ -305,7 +298,7 @@ declare module 'xterm' { /** * The class that represents an xterm.js terminal. */ - export class Terminal implements IEventEmitter, IDisposable { + export class Terminal implements IDisposable { /** * The element containing the terminal. */ @@ -429,96 +422,6 @@ declare module 'xterm' { */ focus(): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: 'data', listener: (...args: any[]) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: 'key', listener: (key: string, event: KeyboardEvent) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: 'keypress' | 'keydown', listener: (event: KeyboardEvent) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: 'refresh', listener: (data: {start: number, end: number}) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: 'resize', listener: (data: {cols: number, rows: number}) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: 'scroll', listener: (ydisp: number) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: 'title', listener: (title: string) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - on(type: string, listener: (...args: any[]) => void): void; - - /** - * Deregisters an event listener. - * @param type The type of the event. - * @param listener The listener. - * @deprecated use `Terminal.onEvent(listener).dispose()` instead. - */ - off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; - - /** - * Emits an event on the terminal. - * @param type The type of event - * @param data data associated with the event. - * @deprecated This is being removed from the API with no replacement, see - * issue #1505. - */ - emit(type: string, data?: any): void; - - /** - * Adds an event listener to the Terminal, returning an IDisposable that can - * be used to conveniently remove the event listener. - * @param type The type of event. - * @param handler The event handler. - * @deprecated use `Terminal.onEvent(listener)` instead. - */ - addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable; - /** * Resizes the terminal. It's best practice to debounce calls to resize, * this will help ensure that the pty can respond to the resize event From 80ce33f9ef1c3c8eceea79a5cff3c9938e5b6ec2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 21:33:02 -0700 Subject: [PATCH 36/39] Rename EventEmitter2 to EventEmitter Fixes #2029 --- src/InputHandler.ts | 10 +++---- src/Linkifier.ts | 8 +++--- src/SelectionManager.ts | 8 +++--- src/Terminal.ts | 28 +++++++++---------- src/TestUtils.test.ts | 6 ++-- src/Types.ts | 6 ++-- src/browser/TestUtils.test.ts | 4 +-- src/browser/services/CharSizeService.ts | 4 +-- src/browser/services/RenderService.ts | 8 +++--- src/browser/services/Services.d.ts | 2 +- src/common/CircularList.ts | 8 +++--- ...tEmitter2.test.ts => EventEmitter.test.ts} | 8 +++--- .../{EventEmitter2.ts => EventEmitter.ts} | 2 +- src/common/TestUtils.test.ts | 4 +-- src/common/Types.ts | 8 +++--- src/common/buffer/BufferSet.ts | 4 +-- src/common/buffer/Marker.ts | 4 +-- src/common/buffer/Types.ts | 2 +- src/common/services/OptionsService.ts | 4 +-- src/common/services/Services.d.ts | 2 +- src/public/Terminal.ts | 2 +- 21 files changed, 66 insertions(+), 66 deletions(-) rename src/common/{EventEmitter2.test.ts => EventEmitter.test.ts} (81%) rename src/common/{EventEmitter2.ts => EventEmitter.ts} (97%) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2b592aa7..5ea20caf 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -14,7 +14,7 @@ import { Disposable } from 'common/Lifecycle'; import { concat } from 'common/TypedArrayUtils'; import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IParsingState, IDcsHandler, IEscapeSequenceParser } from 'common/parser/Types'; /** @@ -108,13 +108,13 @@ export class InputHandler extends Disposable implements IInputHandler { private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32(); private _workCell: CellData = new CellData(); - private _onCursorMove = new EventEmitter2(); + private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } - private _onData = new EventEmitter2(); + private _onData = new EventEmitter(); public get onData(): IEvent { return this._onData.event; } - private _onLineFeed = new EventEmitter2(); + private _onLineFeed = new EventEmitter(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } - private _onScroll = new EventEmitter2(); + private _onScroll = new EventEmitter(); public get onScroll(): IEvent { return this._onScroll.event; } constructor( diff --git a/src/Linkifier.ts b/src/Linkifier.ts index 50794a58..c11849de 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -7,7 +7,7 @@ import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, import { IBufferStringIteratorResult } from 'common/buffer/Types'; import { MouseZone } from './MouseZoneManager'; import { getStringCellWidth } from 'common/CharWidth'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; /** * The Linkifier applies links to rows shortly after they have been refreshed. @@ -34,11 +34,11 @@ export class Linkifier implements ILinkifier { private _nextLinkMatcherId = 0; private _rowsToLinkify: { start: number, end: number }; - private _onLinkHover = new EventEmitter2(); + private _onLinkHover = new EventEmitter(); public get onLinkHover(): IEvent { return this._onLinkHover.event; } - private _onLinkLeave = new EventEmitter2(); + private _onLinkLeave = new EventEmitter(); public get onLinkLeave(): IEvent { return this._onLinkLeave.event; } - private _onLinkTooltip = new EventEmitter2(); + private _onLinkTooltip = new EventEmitter(); public get onLinkTooltip(): IEvent { return this._onLinkTooltip.event; } constructor( diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1a8d6b6c..ef16f5c5 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -12,7 +12,7 @@ import { SelectionModel } from './SelectionModel'; import { AltClickHandler } from './handlers/AltClickHandler'; import { CellData } from 'common/buffer/BufferLine'; import { IDisposable } from 'xterm'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; import { IBufferService } from 'common/services/Services'; @@ -110,11 +110,11 @@ export class SelectionManager implements ISelectionManager { private _mouseDownTimeStamp: number; - private _onLinuxMouseSelection = new EventEmitter2(); + private _onLinuxMouseSelection = new EventEmitter(); public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } - private _onRedrawRequest = new EventEmitter2(); + private _onRedrawRequest = new EventEmitter(); public get onRedrawRequest(): IEvent { return this._onRedrawRequest.event; } - private _onSelectionChange = new EventEmitter2(); + private _onSelectionChange = new EventEmitter(); public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } constructor( diff --git a/src/Terminal.ts b/src/Terminal.ts index f99b6a9d..9d27e0f5 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -45,7 +45,7 @@ import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Attributes, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { applyWindowsMode } from './WindowsMode'; import { ColorManager } from 'browser/ColorManager'; @@ -190,32 +190,32 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } - private _onCursorMove = new EventEmitter2(); + private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } - private _onData = new EventEmitter2(); + private _onData = new EventEmitter(); public get onData(): IEvent { return this._onData.event; } - private _onKey = new EventEmitter2<{ key: string, domEvent: KeyboardEvent }>(); + private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } - private _onLineFeed = new EventEmitter2(); + private _onLineFeed = new EventEmitter(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } - private _onRender = new EventEmitter2<{ start: number, end: number }>(); + private _onRender = new EventEmitter<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - private _onResize = new EventEmitter2<{ cols: number, rows: number }>(); + private _onResize = new EventEmitter<{ cols: number, rows: number }>(); public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } - private _onScroll = new EventEmitter2(); + private _onScroll = new EventEmitter(); public get onScroll(): IEvent { return this._onScroll.event; } - private _onSelectionChange = new EventEmitter2(); + private _onSelectionChange = new EventEmitter(); public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } - private _onTitleChange = new EventEmitter2(); + private _onTitleChange = new EventEmitter(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } - private _onFocus = new EventEmitter2(); + private _onFocus = new EventEmitter(); public get onFocus(): IEvent { return this._onFocus.event; } - private _onBlur = new EventEmitter2(); + private _onBlur = new EventEmitter(); public get onBlur(): IEvent { return this._onBlur.event; } - public onA11yCharEmitter = new EventEmitter2(); + public onA11yCharEmitter = new EventEmitter(); public get onA11yChar(): IEvent { return this.onA11yCharEmitter.event; } - public onA11yTabEmitter = new EventEmitter2(); + public onA11yTabEmitter = new EventEmitter(); public get onA11yTab(): IEvent { return this.onA11yTabEmitter.event; } /** diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 4f293eed..6afd018d 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -14,7 +14,7 @@ import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/BufferLine'; import { IColorManager, IColorSet, IMouseHelper } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; -import { EventEmitter2 } from 'common/EventEmitter2'; +import { EventEmitter } from 'common/EventEmitter'; export class TestTerminal extends Terminal { writeSync(data: string): void { @@ -186,8 +186,8 @@ export class MockTerminal implements ITerminal { } export class MockInputHandlingTerminal implements IInputHandlingTerminal { - onA11yCharEmitter: EventEmitter2; - onA11yTabEmitter: EventEmitter2; + onA11yCharEmitter: EventEmitter; + onA11yTabEmitter: EventEmitter; element: HTMLElement; options: ITerminalOptions = {}; cols: number; diff --git a/src/Types.ts b/src/Types.ts index 536f7cca..a3435614 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -5,7 +5,7 @@ import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { ICharset, IAttributeData, CharData } from 'common/Types'; -import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { IColorSet, IMouseHelper } from 'browser/Types'; import { IOptionsService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; @@ -54,8 +54,8 @@ export interface IInputHandlingTerminal { viewport: IViewport; selectionManager: ISelectionManager; - onA11yCharEmitter: EventEmitter2; - onA11yTabEmitter: EventEmitter2; + onA11yCharEmitter: EventEmitter; + onA11yTabEmitter: EventEmitter; bell(): void; focus(): void; diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 7c965c8d..b286295b 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; export class MockCharSizeService implements ICharSizeService { get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - onCharSizeChange: IEvent = new EventEmitter2().event; + onCharSizeChange: IEvent = new EventEmitter().event; constructor(public width: number, public height: number) {} measure(): void {} } diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 9312e383..42920bfa 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -4,7 +4,7 @@ */ import { IOptionsService } from 'common/services/Services'; -import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; export class CharSizeService implements ICharSizeService { @@ -14,7 +14,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - private _onCharSizeChange = new EventEmitter2(); + private _onCharSizeChange = new EventEmitter(); public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; } constructor( diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 5930569d..5035ed3d 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -5,7 +5,7 @@ import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -22,11 +22,11 @@ export class RenderService extends Disposable implements IRenderService { private _canvasWidth: number = 0; private _canvasHeight: number = 0; - private _onDimensionsChange = new EventEmitter2(); + private _onDimensionsChange = new EventEmitter(); public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } - private _onRender = new EventEmitter2<{ start: number, end: number }>(); + private _onRender = new EventEmitter<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - private _onRefreshRequest = new EventEmitter2<{ start: number, end: number }>(); + private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; } public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } diff --git a/src/browser/services/Services.d.ts b/src/browser/services/Services.d.ts index 6bdf383a..1916572c 100644 --- a/src/browser/services/Services.d.ts +++ b/src/browser/services/Services.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent } from 'common/EventEmitter2'; +import { IEvent } from 'common/EventEmitter'; import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 88e714b8..d4ad3fce 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -4,7 +4,7 @@ */ import { ICircularList } from 'common/Types'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; export interface IInsertEvent { index: number; @@ -25,11 +25,11 @@ export class CircularList implements ICircularList { private _startIndex: number; private _length: number; - public onDeleteEmitter = new EventEmitter2(); + public onDeleteEmitter = new EventEmitter(); public get onDelete(): IEvent { return this.onDeleteEmitter.event; } - public onInsertEmitter = new EventEmitter2(); + public onInsertEmitter = new EventEmitter(); public get onInsert(): IEvent { return this.onInsertEmitter.event; } - public onTrimEmitter = new EventEmitter2(); + public onTrimEmitter = new EventEmitter(); public get onTrim(): IEvent { return this.onTrimEmitter.event; } constructor( diff --git a/src/common/EventEmitter2.test.ts b/src/common/EventEmitter.test.ts similarity index 81% rename from src/common/EventEmitter2.test.ts rename to src/common/EventEmitter.test.ts index bd8b5dea..e9013a99 100644 --- a/src/common/EventEmitter2.test.ts +++ b/src/common/EventEmitter.test.ts @@ -4,12 +4,12 @@ */ import { assert } from 'chai'; -import { EventEmitter2 } from 'common/EventEmitter2'; +import { EventEmitter } from 'common/EventEmitter'; -describe('EventEmitter2', () => { +describe('EventEmitter', () => { it('should fire listeners multiple times', () => { const order: string[] = []; - const emitter = new EventEmitter2(); + const emitter = new EventEmitter(); emitter.event(data => order.push(data + 'a')); emitter.event(data => order.push(data + 'b')); emitter.fire(1); @@ -19,7 +19,7 @@ describe('EventEmitter2', () => { it('should not fire listeners once disposed', () => { const order: string[] = []; - const emitter = new EventEmitter2(); + const emitter = new EventEmitter(); emitter.event(data => order.push(data + 'a')); const disposeB = emitter.event(data => order.push(data + 'b')); emitter.event(data => order.push(data + 'c')); diff --git a/src/common/EventEmitter2.ts b/src/common/EventEmitter.ts similarity index 97% rename from src/common/EventEmitter2.ts rename to src/common/EventEmitter.ts index fcbef969..efc101ce 100644 --- a/src/common/EventEmitter2.ts +++ b/src/common/EventEmitter.ts @@ -13,7 +13,7 @@ export interface IEvent { (listener: (e: T) => any): IDisposable; } -export class EventEmitter2 { +export class EventEmitter { private _listeners: IListener[] = []; private _event?: IEvent; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 0a0cb6a4..0bb56ee3 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IBufferService, IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services'; -import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -21,7 +21,7 @@ export class MockBufferService implements IBufferService { export class MockOptionsService implements IOptionsService { options: ITerminalOptions = clone(DEFAULT_OPTIONS); - onOptionChange: IEvent = new EventEmitter2().event; + onOptionChange: IEvent = new EventEmitter().event; constructor(testOptions: IPartialTerminalOptions) { Object.keys(testOptions).forEach(key => this.options[key] = (testOptions)[key]); } diff --git a/src/common/Types.ts b/src/common/Types.ts index 67d449e5..b29515d4 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent, EventEmitter2 } from 'common/EventEmitter2'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; export const DEFAULT_COLOR = 256; @@ -40,11 +40,11 @@ export interface ICircularList { maxLength: number; isFull: boolean; - onDeleteEmitter: EventEmitter2; + onDeleteEmitter: EventEmitter; onDelete: IEvent; - onInsertEmitter: EventEmitter2; + onInsertEmitter: EventEmitter; onInsert: IEvent; - onTrimEmitter: EventEmitter2; + onTrimEmitter: EventEmitter; onTrim: IEvent; get(index: number): T | undefined; diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index cc8fe737..50e6e505 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -6,7 +6,7 @@ import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IOptionsService, IBufferService } from 'common/services/Services'; /** @@ -19,7 +19,7 @@ export class BufferSet implements IBufferSet { private _activeBuffer: Buffer; - private _onBufferActivate = new EventEmitter2<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>(); + private _onBufferActivate = new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>(); public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; } /** diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 51c5d7f8..7f52ac99 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IMarker } from 'common/Types'; @@ -15,7 +15,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } - private _onDispose = new EventEmitter2(); + private _onDispose = new EventEmitter(); public get onDispose(): IEvent { return this._onDispose.event; } constructor( diff --git a/src/common/buffer/Types.ts b/src/common/buffer/Types.ts index 37ce2b7e..4b4bb85f 100644 --- a/src/common/buffer/Types.ts +++ b/src/common/buffer/Types.ts @@ -4,7 +4,7 @@ */ import { IAttributeData, ICircularList, IBufferLine, ICellData } from 'common/Types'; -import { IEvent } from 'common/EventEmitter2'; +import { IEvent } from 'common/EventEmitter'; // BufferIndex denotes a position in the buffer: [rowIndex, colIndex] export type BufferIndex = [number, number]; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d518aae6..2d2a08f0 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -4,7 +4,7 @@ */ import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/services/Services'; -import { EventEmitter2, IEvent } from 'common/EventEmitter2'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { clone } from 'common/Clone'; @@ -57,7 +57,7 @@ const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; export class OptionsService implements IOptionsService { public options: ITerminalOptions; - private _onOptionChange = new EventEmitter2(); + private _onOptionChange = new EventEmitter(); public get onOptionChange(): IEvent { return this._onOptionChange.event; } constructor(options: IPartialTerminalOptions) { diff --git a/src/common/services/Services.d.ts b/src/common/services/Services.d.ts index 8ebc0308..0c7f92bd 100644 --- a/src/common/services/Services.d.ts +++ b/src/common/services/Services.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent } from 'common/EventEmitter2'; +import { IEvent } from 'common/EventEmitter'; export interface IBufferService { readonly cols: number; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 2ccbdef6..1c019b16 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -9,7 +9,7 @@ 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'; +import { IEvent } from 'common/EventEmitter'; import { AddonManager } from './AddonManager'; export class Terminal implements ITerminalApi { From 1b07a03b2f0bb539ac8fa2d620845c5b04896e9c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 21:39:10 -0700 Subject: [PATCH 37/39] Properly reset buffer after Terminal.reset --- src/Terminal.ts | 1 + src/common/TestUtils.test.ts | 1 + src/common/services/BufferService.ts | 12 ++++++++---- src/common/services/Services.d.ts | 1 + 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 087fb379..c7706008 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1893,6 +1893,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II const userScrolling = this._userScrolling; this._setup(); + this._bufferService.reset(); // reattach this._customKeyEventHandler = customKeyEventHandler; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 2d699cf8..c982df25 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -20,6 +20,7 @@ export class MockBufferService implements IBufferService { this.cols = cols; this.rows = rows; } + reset(): void {} } export class MockOptionsService implements IOptionsService { diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index bbac7803..6ff08061 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -18,15 +18,19 @@ export class BufferService implements IBufferService { public get buffer(): IBuffer { return this.buffers.active; } constructor( - optionsService: IOptionsService + private _optionsService: IOptionsService ) { - this.cols = Math.max(optionsService.options.cols, MINIMUM_COLS); - this.rows = Math.max(optionsService.options.rows, MINIMUM_ROWS); - this.buffers = new BufferSet(optionsService, this); + this.cols = Math.max(_optionsService.options.cols, MINIMUM_COLS); + this.rows = Math.max(_optionsService.options.rows, MINIMUM_ROWS); + this.buffers = new BufferSet(_optionsService, this); } public resize(cols: number, rows: number): void { this.cols = cols; this.rows = rows; } + + public reset(): void { + this.buffers = new BufferSet(this._optionsService, this); + } } diff --git a/src/common/services/Services.d.ts b/src/common/services/Services.d.ts index 0da78fc7..efc273d2 100644 --- a/src/common/services/Services.d.ts +++ b/src/common/services/Services.d.ts @@ -15,6 +15,7 @@ export interface IBufferService { // TODO: Move resize event here resize(cols: number, rows: number): void; + reset(): void; } export interface IOptionsService { From 73a08f785075977513c6dd2edde9908e8e6c87d3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 21:46:13 -0700 Subject: [PATCH 38/39] Consolidate service init --- src/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index a1c70d3b..835ff420 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -235,9 +235,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Setup and initialize common services this.optionsService = new OptionsService(options); + this._bufferService = new BufferService(this.optionsService); this._setupOptionsListeners(); this._setup(); - this._bufferService = new BufferService(this.optionsService); } public dispose(): void { From 07cef1ec9eaa3db423398c2654705f468114340e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 8 Jun 2019 21:51:24 -0700 Subject: [PATCH 39/39] Increase attach addon timeouts --- addons/xterm-addon-attach/src/AttachAddon.api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.api.ts b/addons/xterm-addon-attach/src/AttachAddon.api.ts index 39a6f73a..945824a2 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.api.ts @@ -17,7 +17,7 @@ const height = 600; describe('AttachAddon', () => { before(async function(): Promise { - this.timeout(10000); + this.timeout(20000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, slowMo: 80, @@ -32,7 +32,7 @@ describe('AttachAddon', () => { }); beforeEach(async function(): Promise { - this.timeout(5000); + this.timeout(20000); await page.goto(APP); });