Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions src/BufferLine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,11 @@ export class BufferLine implements IBufferLine {
}
}

public copyFrom(line: IBufferLine): void {
this._data = [];
for (let i = 0; i < line.length; ++i) {
this._push(line.get(i));
}
public copyFrom(line: BufferLine): IBufferLine {
this._data = line._data.slice(0);
this.length = line.length;
this.isWrapped = line.isWrapped;
return this;
}

public clone(): IBufferLine {
Expand Down Expand Up @@ -251,7 +249,7 @@ export class BufferLineTypedArray implements IBufferLine {
}

/** alter to a full copy of line */
public copyFrom(line: BufferLineTypedArray): void {
public copyFrom(line: BufferLineTypedArray): IBufferLine {
if (this.length !== line.length) {
this._data = new Uint32Array(line._data);
} else {
Expand All @@ -264,6 +262,7 @@ export class BufferLineTypedArray implements IBufferLine {
this._combined[el] = line._combined[el];
}
this.isWrapped = line.isWrapped;
return this;
}

/** create a new clone */
Expand Down
41 changes: 34 additions & 7 deletions src/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
* http://linux.die.net/man/7/urxvt
*/

import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler } from './Types';
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, CharacterJoinerHandler, IBufferLine } from './Types';
import { IMouseZoneManager } from './ui/Types';
import { IRenderer } from './renderer/Types';
import { BufferSet } from './BufferSet';
import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer';
import { Buffer, MAX_BUFFER_SIZE, DEFAULT_ATTR, NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer';
import { CompositionHelper } from './CompositionHelper';
import { EventEmitter } from './common/EventEmitter';
import { Viewport } from './Viewport';
Expand Down Expand Up @@ -106,7 +106,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
theme: null,
rightClickSelectsWord: Browser.isMac,
rendererType: 'canvas',
experimentalBufferLineImpl: 'JsArray'
experimentalBufferLineImpl: 'JsArray',
experimentalPushRecycling: false
};

export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal {
Expand Down Expand Up @@ -208,6 +209,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
private _screenDprMonitor: ScreenDprMonitor;
private _theme: ITheme;

// bufferline to clone/copy from for new blank lines
private _blankLine: IBufferLine = null;

public cols: number;
public rows: number;

Expand Down Expand Up @@ -496,6 +500,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
case 'experimentalBufferLineImpl':
this.buffers.normal.setBufferLineFactory(value);
this.buffers.alt.setBufferLineFactory(value);
this._blankLine = null;
break;
}
// Inform renderer of changes
Expand Down Expand Up @@ -1174,7 +1179,19 @@ 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 = this.buffer.getBlankLine(this.eraseAttr(), isWrapped);
let newLine: IBufferLine;
const useRecycling = this.options.experimentalPushRecycling;
if (useRecycling) {
newLine = this._blankLine;
if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) {
newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped);
this._blankLine = newLine;
}
newLine.isWrapped = !!(isWrapped);
} else {
newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped);
}

const topRow = this.buffer.ybase + this.buffer.scrollTop;
const bottomRow = this.buffer.ybase + this.buffer.scrollBottom;

Expand All @@ -1184,9 +1201,19 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II

// Insert the line using the fastest method
if (bottomRow === this.buffer.lines.length - 1) {
this.buffer.lines.push(newLine);
if (useRecycling) {
if (willBufferBeTrimmed) {
// Warning: Never call .trimAndRecycle() without the
// willBufferBeTrimmed guard!
this.buffer.lines.trimAndRecycle().copyFrom(newLine);
} else {
this.buffer.lines.push(newLine.clone());
}
} else {
this.buffer.lines.push(newLine);
}
} else {
this.buffer.lines.splice(bottomRow + 1, 0, newLine);
this.buffer.lines.splice(bottomRow + 1, 0, (useRecycling) ? newLine.clone() : newLine);
}

// Only adjust ybase and ydisp when the buffer is not trimmed
Expand All @@ -1208,7 +1235,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
// scrollback, instead we can just shift them in-place.
const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;
this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
this.buffer.lines.set(bottomRow, newLine);
this.buffer.lines.set(bottomRow, (useRecycling) ? newLine.clone() : newLine);
}

// Move the viewport to the bottom of the buffer unless the user is
Expand Down
2 changes: 1 addition & 1 deletion src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ export interface IBufferLine {
replaceCells(start: number, end: number, fill: CharData): void;
resize(cols: number, fill: CharData, shrink?: boolean): void;
fill(fillCharData: CharData): void;
copyFrom(line: IBufferLine): void;
copyFrom(line: IBufferLine): IBufferLine;
clone(): IBufferLine;
}

Expand Down
16 changes: 16 additions & 0 deletions src/common/CircularList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,20 @@ describe('CircularList', () => {
assert.equal(list.get(3), 4);
});
});
describe('trimAndRecycle', function(): void {
it('should return correct element', function(): void {
const list = new CircularList<number[]>(5);
list.push([0]);
list.push([1]);
list.push([2]);
list.push([3]);
list.push([4]);
assert.equal(list.trimAndRecycle()[0], 0);
assert.equal(list.trimAndRecycle()[0], 1);
assert.equal(list.trimAndRecycle()[0], 2);
assert.equal(list.trimAndRecycle()[0], 3);
assert.equal(list.trimAndRecycle()[0], 4);
assert.equal(list.trimAndRecycle()[0], 0);
});
});
});
25 changes: 20 additions & 5 deletions src/common/CircularList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
}
}

/**
* Recycling trim.
* This is used to recycle buffer lines in Terminal.scroll when
* the list is at maxLength as a push replacement.
* Returns the old line as new one to be recycled.
* Note: There are no bound checks for performance reasons,
* the method is a special optimization for Terminal.scroll,
* do not use it anywhere else.
*/
public trimAndRecycle(): T | undefined {
this._startIndex = ++this._startIndex % this._maxLength;
this.emit('trim', 1);
return this._array[this._getCyclicIndex(this._length - 1)];
}

/**
* Removes and returns the last value on the list.
* @return The popped value.
Expand Down Expand Up @@ -136,10 +151,10 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
}

// Adjust length as needed
if (this._length + items.length > this.maxLength) {
const countToTrim = (this._length + items.length) - this.maxLength;
if (this._length + items.length > this._maxLength) {
const countToTrim = (this._length + items.length) - this._maxLength;
this._startIndex += countToTrim;
this._length = this.maxLength;
this._length = this._maxLength;
this.emit('trim', countToTrim);
} else {
this._length += items.length;
Expand Down Expand Up @@ -178,7 +193,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
const expandListBy = (start + count + offset) - this._length;
if (expandListBy > 0) {
this._length += expandListBy;
while (this._length > this.maxLength) {
while (this._length > this._maxLength) {
this._length--;
this._startIndex++;
this.emit('trim', 1);
Expand All @@ -198,6 +213,6 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
* @returns The cyclic index.
*/
private _getCyclicIndex(index: number): number {
return (this._startIndex + index) % this.maxLength;
return (this._startIndex + index) % this._maxLength;
}
}
1 change: 1 addition & 0 deletions src/common/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface ICircularList<T> extends IEventEmitter {
get(index: number): T | undefined;
set(index: number, value: T): void;
push(value: T): void;
trimAndRecycle(): T | undefined;
pop(): T | undefined;
splice(start: number, deleteCount: number, ...items: T[]): void;
trimStart(count: number): void;
Expand Down
2 changes: 2 additions & 0 deletions typings/xterm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ declare module 'xterm' {
*/
experimentalBufferLineImpl?: 'JsArray' | 'TypedArray';

experimentalPushRecycling?: boolean;

/**
* The font size used to render text.
*/
Expand Down