diff --git a/demo/main.js b/demo/main.js index 70544e38..e0fb5aa4 100644 --- a/demo/main.js +++ b/demo/main.js @@ -184,7 +184,8 @@ function initOptions(term) { fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - rendererType: ['dom', 'canvas'] + rendererType: ['dom', 'canvas'], + bufferLineConstructor: ['JsArray', 'TypedArray'] }; var options = Object.keys(term._core.options); var booleanOptions = []; diff --git a/src/Buffer.ts b/src/Buffer.ts index 4ae168b4..3f0445bc 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -4,10 +4,10 @@ */ import { CircularList } from './common/CircularList'; -import { CharData, ITerminal, IBuffer, IBufferLine } from './Types'; +import { CharData, ITerminal, IBuffer, IBufferLine, IBufferLineConstructor } from './Types'; import { EventEmitter } from './EventEmitter'; import { IMarker } from 'xterm'; -import { BufferLine } from './BufferLine'; +import { BufferLine, BufferLineJsArray, BufferLineTypedArray } from './BufferLine'; export const DEFAULT_ATTR = (0 << 18) | (257 << 9) | (256 << 0); export const CHAR_DATA_ATTR_INDEX = 0; @@ -39,6 +39,7 @@ export class Buffer implements IBuffer { public savedY: number; public savedX: number; public markers: Marker[] = []; + private _bufferLineConstructor: IBufferLineConstructor; /** * Create a new Buffer. @@ -53,6 +54,40 @@ export class Buffer implements IBuffer { this.clear(); } + public setBufferLineFactory(type: string): void { + if (type === 'JsArray') { + if (this._bufferLineConstructor === BufferLineJsArray) { + return; + } + this._bufferLineConstructor = BufferLineJsArray; + this._recreateLines(); + } else if (type === 'TypedArray') { + if (this._bufferLineConstructor === BufferLineTypedArray) { + return; + } + this._bufferLineConstructor = BufferLineTypedArray; + this._recreateLines(); + } else { + this._bufferLineConstructor = BufferLine; + } + } + + private _recreateLines(): void { + if (!this.lines) return; + for (let i = 0; i < this.lines.length; ++i) { + const oldLine = this.lines.get(i); + const newLine = new this._bufferLineConstructor(oldLine.length); + for (let j = 0; j < oldLine.length; ++j) { + newLine.set(j, oldLine.get(j)); + } + this.lines.set(i, newLine); + } + } + + public getBlankLine(cols: number, attr: number, isWrapped?: boolean): IBufferLine { + return this._bufferLineConstructor.blankLine(cols, attr, isWrapped); + } + public get hasScrollback(): boolean { return this._hasScrollback && this.lines.maxLength > this._terminal.rows; } @@ -94,6 +129,7 @@ export class Buffer implements IBuffer { * Clears the buffer to it's initial state, discarding all previous data. */ public clear(): void { + this.setBufferLineFactory(this._terminal.options.bufferLineConstructor); this.ydisp = 0; this.ybase = 0; this.y = 0; diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 1fa5ca61..aa2a2181 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -2,16 +2,16 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ -import { CharData, IBufferLine } from './Types'; +import { CharData, IBufferLine, IBufferLineConstructor } from './Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer'; /** * Class representing a terminal line. */ -export class BufferLineOld implements IBufferLine { +export class BufferLineJsArray implements IBufferLine { static blankLine(cols: number, attr: number, isWrapped?: boolean): IBufferLine { const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - return new BufferLineOld(cols, ch, isWrapped); + return new BufferLineJsArray(cols, ch, isWrapped); } protected _data: CharData[]; public isWrapped = false; @@ -108,7 +108,7 @@ export class BufferLineOld implements IBufferLine { } public clone(): IBufferLine { - const newLine = new BufferLineOld(0); + const newLine = new BufferLineJsArray(0); newLine.makeCopyOf(this); return newLine; } @@ -133,10 +133,10 @@ const enum Cell { * TODO: * - provide getData/setData to directly access the data */ -export class BufferLine implements IBufferLine { +export class BufferLineTypedArray implements IBufferLine { static blankLine(cols: number, attr: number, isWrapped?: boolean): IBufferLine { const ch: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; - return new BufferLine(cols, ch, isWrapped); + return new BufferLineTypedArray(cols, ch, isWrapped); } protected _data: Uint32Array | null = null; protected _combined: {[index: number]: string} = {}; @@ -241,10 +241,6 @@ export class BufferLine implements IBufferLine { this.length = cols; } - /** - * new methods... - */ - /** fill a line with fillCharData */ public fill(fillCharData: CharData): void { this._combined = {}; @@ -254,7 +250,7 @@ export class BufferLine implements IBufferLine { } /** alter to a full copy of line */ - public makeCopyOf(line: BufferLine): void { + public makeCopyOf(line: BufferLineTypedArray): void { if (this.length !== line.length) { this._data = new Uint32Array(line._data); } else { @@ -271,7 +267,7 @@ export class BufferLine implements IBufferLine { /** create a new clone */ public clone(): IBufferLine { - const newLine = new BufferLine(0); + const newLine = new BufferLineTypedArray(0); // creation of new typed array from another is actually pretty slow :( // still faster than copying values one by one newLine._data = new Uint32Array(this._data); @@ -283,3 +279,12 @@ export class BufferLine implements IBufferLine { return newLine; } } + +/** + * implementation switch + * needed to test the different implementation throughout the + * whole code base and tests + * FIXME: remove once we are settled with one + */ +export const BufferLine = BufferLineJsArray; +// export const BufferLine: IBufferLineConstructor = BufferLineTypedArray; diff --git a/src/Terminal.ts b/src/Terminal.ts index 3927d8b5..7104ba21 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,7 +52,7 @@ import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; -import { BufferLine } from './BufferLine'; +import { BufferLine, BufferLineJsArray } from './BufferLine'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -106,7 +106,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = { tabStopWidth: 8, theme: null, rightClickSelectsWord: Browser.isMac, - rendererType: 'canvas' + rendererType: 'canvas', + bufferLineConstructor: (BufferLine === BufferLineJsArray) ? 'JsArray' : 'TypedArray' }; export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal { @@ -493,6 +494,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } break; case 'tabStopWidth': this.buffers.setupTabStops(); break; + case 'bufferLineConstructor': + this.buffers.normal.setBufferLineFactory(value); + this.buffers.alt.setBufferLineFactory(value); + break; } // Inform renderer of changes if (this.renderer) { @@ -1170,7 +1175,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(isWrapped?: boolean): void { - const newLine = BufferLine.blankLine(this.cols, DEFAULT_ATTR, isWrapped); + const newLine = this.buffer.getBlankLine(this.cols, DEFAULT_ATTR, isWrapped); const topRow = this.buffer.ybase + this.buffer.scrollTop; const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; @@ -1722,7 +1727,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.buffer.ybase = 0; this.buffer.y = 0; for (let i = 1; i < this.rows; i++) { - this.buffer.lines.push(BufferLine.blankLine(this.cols, DEFAULT_ATTR)); + this.buffer.lines.push(this.buffer.getBlankLine(this.cols, DEFAULT_ATTR)); } this.refresh(0, this.rows - 1); this.emit('scroll', this.buffer.ydisp); @@ -1814,7 +1819,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II // blankLine(true) is xterm/linux behavior const scrollRegionHeight = this.buffer.scrollBottom - this.buffer.scrollTop; this.buffer.lines.shiftElements(this.buffer.y + this.buffer.ybase, scrollRegionHeight, 1); - this.buffer.lines.set(this.buffer.y + this.buffer.ybase, BufferLine.blankLine(this.cols, this.eraseAttr())); + this.buffer.lines.set(this.buffer.y + this.buffer.ybase, this.buffer.getBlankLine(this.cols, this.eraseAttr())); this.updateRange(this.buffer.scrollTop); this.updateRange(this.buffer.scrollBottom); } else { diff --git a/src/Types.ts b/src/Types.ts index b852b09f..5cc8cf19 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -264,6 +264,7 @@ export interface ITerminalOptions extends IPublicTerminalOptions { screenKeys?: boolean; termName?: string; useFlowControl?: boolean; + bufferLineConstructor?: string; } export interface IBuffer { @@ -523,3 +524,8 @@ export interface IBufferLine { makeCopyOf(line: IBufferLine): void; clone(): IBufferLine; } + +export interface IBufferLineConstructor { + new(cols: number, fillCharData?: CharData, isWrapped?: boolean): IBufferLine; + blankLine(cols: number, attr: number, isWrapped?: boolean): IBufferLine; +}