From db7b792d87b72bd53cc063c58612d606c36d0008 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Sat, 22 Apr 2017 10:21:40 +0100 Subject: [PATCH 01/17] Add logic for reflowing lines Connects to #622 --- src/InputHandler.ts | 19 ++++-- src/Interfaces.ts | 6 +- src/utils/CircularList.ts | 17 +++-- src/utils/WrappableList.ts | 127 +++++++++++++++++++++++++++++++++++++ src/xterm.js | 32 +++++++--- 5 files changed, 180 insertions(+), 21 deletions(-) create mode 100644 src/utils/WrappableList.ts diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a702308372..4cac5cc6cb 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -53,6 +53,8 @@ export class InputHandler implements IInputHandler { if (this._terminal.x + ch_width - 1 >= this._terminal.cols) { // autowrap - DECAWM if (this._terminal.wraparoundMode) { + // Mark this row as being wrapped. + this._terminal.lines.wrappedLines.push(row); this._terminal.x = 0; this._terminal.y++; if (this._terminal.y > this._terminal.scrollBottom) { @@ -76,10 +78,10 @@ export class InputHandler implements IInputHandler { if (removed[2] === 0 && this._terminal.lines.get(row)[this._terminal.cols - 2] && this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) - this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; + this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, null, 1]; // insert empty cell at cursor - this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, ' ', 1]); + this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, null, 1]); } } @@ -92,6 +94,11 @@ export class InputHandler implements IInputHandler { this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, '', 0]; this._terminal.x++; } + + let wi = this._terminal.lines.wrappedLines.indexOf(row); + if (wi > -1) { + this._terminal.lines.wrappedLines.splice(wi, 1); + } } } @@ -185,7 +192,7 @@ export class InputHandler implements IInputHandler { row = this._terminal.y + this._terminal.ybase; j = this._terminal.x; - ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + ch = [this._terminal.eraseAttr(), null, 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.lines.get(row).splice(j++, 0, ch); @@ -504,7 +511,7 @@ export class InputHandler implements IInputHandler { } row = this._terminal.y + this._terminal.ybase; - ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + ch = [this._terminal.eraseAttr(), null, 1]; // xterm while (param--) { this._terminal.lines.get(row).splice(this._terminal.x, 1); @@ -554,7 +561,7 @@ export class InputHandler implements IInputHandler { row = this._terminal.y + this._terminal.ybase; j = this._terminal.x; - ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm + ch = [this._terminal.eraseAttr(), null, 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.lines.get(row)[j++] = ch; @@ -608,7 +615,7 @@ export class InputHandler implements IInputHandler { public repeatPrecedingCharacter(params: number[]): void { let param = params[0] || 1 , line = this._terminal.lines.get(this._terminal.ybase + this._terminal.y) - , ch = line[this._terminal.x - 1] || [this._terminal.defAttr, ' ', 1]; + , ch = line[this._terminal.x - 1] || [this._terminal.defAttr, null, 1]; while (param--) { line[this._terminal.x++] = ch; diff --git a/src/Interfaces.ts b/src/Interfaces.ts index ca228ce01b..11bd62866d 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -23,7 +23,7 @@ export interface ITerminal { textarea: HTMLTextAreaElement; ybase: number; ydisp: number; - lines: ICircularList; + lines: IWrappableList; rows: number; cols: number; browser: IBrowser; @@ -74,6 +74,10 @@ interface ICircularList { shiftElements(start: number, count: number, offset: number): void; } +export interface IWrappableList extends ICircularList { + transform(width: number): void; +} + export interface LinkMatcherOptions { /** * The index of the link from the regex.match(text) call. This defaults to 0 diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index b72c667fe1..8a4f13cf0f 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -5,9 +5,9 @@ * @license MIT */ export class CircularList { - private _array: T[]; - private _startIndex: number; - private _length: number; + protected _array: T[]; + protected _startIndex: number; + protected _length: number; constructor(maxLength: number) { this._array = new Array(maxLength); @@ -43,8 +43,13 @@ export class CircularList { this._length = newLength; } - public get forEach(): (callbackfn: (value: T, index: number, array: T[]) => void) => void { - return this._array.forEach; + public forEach(callbackfn: (value: T, index?: number, array?: T[]) => void): void { + let get = this.get.bind(this); + let i = 0; + let len = this.maxLength; + for (i; i < len; i++) { + callbackfn(get(i), i); + } } /** @@ -177,7 +182,7 @@ export class CircularList { * @param index The regular index. * @returns The cyclic index. */ - private _getCyclicIndex(index: number): number { + protected _getCyclicIndex(index: number): number { return (this._startIndex + index) % this.maxLength; } } diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts new file mode 100644 index 0000000000..4fcd84f4d5 --- /dev/null +++ b/src/utils/WrappableList.ts @@ -0,0 +1,127 @@ +import { CircularList } from './CircularList'; + +// Much faster than native filter +// https://jsperf.com/function-loops/4 +function standardFilter(array, fn) { + let results = []; + let item; + let i; + let len; + for (i = 0, len = array.length; i < len; i++) { + item = array[i]; + if (fn(item)) results.push(item); + } + return results; +} + +function trimmedLength(line, min) { + let i = line.length - 1; + for (i; i >= 0; i--) { + if (line[i] && line[i][1] !== null) { + break; + } + } + + if (i < min) { + // i = min; + } else { + // 2 extra blank chars allows for cursor and ensures at least one element is in array (in case + // of intentional blank rows) + i++; + } + + return i; +} + +function chunkArray(chunkSize, array) { + let temparray = []; + let i = 0; + let j = array.length; + for (i; i < j; i += chunkSize) { + temparray.push(array.slice(i, i + chunkSize)); + } + + return temparray; +} + +function notNull(value) { + return value !== null; +} + +export class WrappableList extends CircularList { + public wrappedLines: number[] = []; + + constructor(maxLength: number) { + super(maxLength); + } + + // Need to make sure wrappedlines move when CircularList wraps around + public push(value: any[]): void { + if (this._length + 1 === this.maxLength) { + this.wrappedLines = this.wrappedLines.map(x => x - 1); + } + this._array[this._getCyclicIndex(this._length)] = value; + if (this._length === this.maxLength) { + this._startIndex++; + if (this._startIndex === this.maxLength) { + this._startIndex = 0; + } + } else { + this._length++; + } + } + + public transform(width) { + let wrappedLines = this.wrappedLines; + + let temp = []; + let tempWrapped = []; + let skip = []; + + const previouslyWrapped = (i) => wrappedLines.indexOf(i) > -1; + + const concatWrapped = (line, index) => { + line = standardFilter(line, notNull).concat(this._array[index + 1]); + skip.push(index + 1); + if (previouslyWrapped(index + 1)) { + return concatWrapped(line, index + 1); + } else { + return line; + } + }; + + this.forEach((line, index) => { + if (!line) { + return; + } + if (skip.indexOf(index) > -1) { + return; + } + + if (previouslyWrapped(index)) { + line = concatWrapped(line, index); + } + + const trim = trimmedLength(line, width); + if (trim > width) { + chunkArray(width, line.slice(0, trim)).forEach((line, i, chunks) => { + temp.push(line); + if (i < chunks.length - 1) { + tempWrapped.push(temp.length - 1); + } + }); + } else { + temp.push(line.slice(0, width)); + } + }); + + let cachedStartIndex = this._startIndex; + const scrollback = temp.length > this.maxLength ? temp.length : this.maxLength; + this.maxLength = scrollback; + this._length = temp.length; + this._array = temp; + this._array.length = scrollback; + this.wrappedLines = tempWrapped; + this._startIndex = 0; + } +} diff --git a/src/xterm.js b/src/xterm.js index 777e65f9ca..47da3c37bb 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -14,7 +14,7 @@ import { CompositionHelper } from './CompositionHelper'; import { EventEmitter } from './EventEmitter'; import { Viewport } from './Viewport'; import { rightClickHandler, pasteHandler, copyHandler } from './handlers/Clipboard'; -import { CircularList } from './utils/CircularList'; +import { WrappableList } from './utils/WrappableList'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; import { Parser } from './Parser'; @@ -243,7 +243,7 @@ function Terminal(options) { * An array of all lines in the entire buffer, including the prompt. The lines are array of * characters which are 2-length arrays where [0] is an attribute and [1] is the character. */ - this.lines = new CircularList(this.scrollback); + this.lines = new WrappableList(this.scrollback); var i = this.rows; while (i--) { this.lines.push(this.blankLine()); @@ -1847,8 +1847,23 @@ Terminal.prototype.resize = function(x, y) { // resize cols j = this.cols; + + let cachedLines = this.lines.length; + this.lines.transform(x); + + let ymove = this.lines.length - cachedLines; + + if (ymove) { + if (this.ydisp === 0) { + this.y += ymove; + } else { + this.ydisp += ymove; + this.ybase += ymove; + } + } + if (j < x) { - ch = [this.defAttr, ' ', 1]; // does xterm use the default attr? + ch = [this.defAttr, null, 1]; // does xterm use the default attr? i = this.lines.length; while (i--) { while (this.lines.get(i).length < x) { @@ -1857,6 +1872,7 @@ Terminal.prototype.resize = function(x, y) { } } + this.cols = x; this.setupStops(this.cols); @@ -2014,7 +2030,7 @@ Terminal.prototype.eraseRight = function(x, y) { if (!line) { return; } - var ch = [this.eraseAttr(), ' ', 1]; // xterm + var ch = [this.eraseAttr(), null, 1]; // xterm for (; x < this.cols; x++) { line[x] = ch; } @@ -2033,7 +2049,7 @@ Terminal.prototype.eraseLeft = function(x, y) { if (!line) { return; } - var ch = [this.eraseAttr(), ' ', 1]; // xterm + var ch = [this.eraseAttr(), null, 1]; // xterm x++; while (x--) { line[x] = ch; @@ -2079,7 +2095,7 @@ Terminal.prototype.blankLine = function(cur) { ? this.eraseAttr() : this.defAttr; - var ch = [attr, ' ', 1] // width defaults to 1 halfwidth character + var ch = [attr, null, 1] // width defaults to 1 halfwidth character , line = [] , i = 0; @@ -2097,8 +2113,8 @@ Terminal.prototype.blankLine = function(cur) { */ Terminal.prototype.ch = function(cur) { return cur - ? [this.eraseAttr(), ' ', 1] - : [this.defAttr, ' ', 1]; + ? [this.eraseAttr(), null, 1] + : [this.defAttr, null, 1]; }; From 5bdef740283beb0ef07a28bbc89b0783154ad116 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Tue, 25 Apr 2017 11:28:33 +0100 Subject: [PATCH 02/17] Fixed bug caused by unwrapping lines. --- src/xterm.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/xterm.js b/src/xterm.js index 47da3c37bb..90f4dce70c 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -1853,6 +1853,12 @@ Terminal.prototype.resize = function(x, y) { let ymove = this.lines.length - cachedLines; + // It's possible that the transform process has reduced the number of available lines. + // Make sure we have a line for each row. + while (this.lines.length < y) { + this.lines.push(this.blankLine()); + } + if (ymove) { if (this.ydisp === 0) { this.y += ymove; From d6f41ca58ebbb902035a8914d48603c928dd63c2 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Tue, 25 Apr 2017 13:34:59 +0100 Subject: [PATCH 03/17] Fixed some issues releated to running in an alt screen buffer and writing lines to an unscrollable area. --- src/InputHandler.ts | 4 ++++ src/xterm.js | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 4cac5cc6cb..e85603a80e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1138,6 +1138,10 @@ export class InputHandler implements IInputHandler { // this.x = this.savedX; // this.y = this.savedY; // } + + // Run the resize handler incase the viewport has been resized since we switched buffers + this._terminal.resize(this._terminal.cols, this._terminal.rows, true); + this._terminal.refresh(0, this._terminal.rows - 1); this._terminal.viewport.syncScrollArea(); this._terminal.showCursor(); diff --git a/src/xterm.js b/src/xterm.js index 90f4dce70c..c8c5925718 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -1826,7 +1826,7 @@ Terminal.prototype.error = function() { * @param {number} x The number of columns to resize to. * @param {number} y The number of rows to resize to. */ -Terminal.prototype.resize = function(x, y) { +Terminal.prototype.resize = function(x, y, force) { if (isNaN(x) || isNaN(y)) { return; } @@ -1838,7 +1838,7 @@ Terminal.prototype.resize = function(x, y) { , ch , addToY; - if (x === this.cols && y === this.rows) { + if (x === this.cols && y === this.rows && !force) { return; } @@ -1849,6 +1849,7 @@ Terminal.prototype.resize = function(x, y) { j = this.cols; let cachedLines = this.lines.length; + this.lines.transform(x); let ymove = this.lines.length - cachedLines; @@ -1942,6 +1943,11 @@ Terminal.prototype.resize = function(x, y) { this.x = x - 1; } + // If we're in a "no scroll" situation (e.g. vim, top) trim the number of lines + if (this.ybase === 0 && this.ydisp === 0 && this.lines.length > this.rows) { + this.lines.splice(this.rows, this.lines.length - this.rows); + } + this.scrollTop = 0; this.scrollBottom = y - 1; @@ -1949,7 +1955,7 @@ Terminal.prototype.resize = function(x, y) { this.refresh(0, this.rows - 1); - this.normal = null; + // this.normal = null; this.geometry = [this.cols, this.rows]; this.emit('resize', {terminal: this, cols: x, rows: y}); From b9449a18b2e7cebd07c2e5c2e719c4c6de9b8123 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Wed, 26 Apr 2017 09:56:57 +0100 Subject: [PATCH 04/17] Addressing PR comments --- src/InputHandler.ts | 9 ++---- src/Interfaces.ts | 5 ++- src/Types.ts | 4 +++ src/utils/WrappableList.ts | 64 ++++++++++++++++++++++++++------------ src/xterm.js | 4 +-- 5 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index e85603a80e..476af4a221 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -54,7 +54,7 @@ export class InputHandler implements IInputHandler { // autowrap - DECAWM if (this._terminal.wraparoundMode) { // Mark this row as being wrapped. - this._terminal.lines.wrappedLines.push(row); + this._terminal.lines.addWrappedLine(row); this._terminal.x = 0; this._terminal.y++; if (this._terminal.y > this._terminal.scrollBottom) { @@ -94,11 +94,6 @@ export class InputHandler implements IInputHandler { this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, '', 0]; this._terminal.x++; } - - let wi = this._terminal.lines.wrappedLines.indexOf(row); - if (wi > -1) { - this._terminal.lines.wrappedLines.splice(wi, 1); - } } } @@ -1139,7 +1134,7 @@ export class InputHandler implements IInputHandler { // this.y = this.savedY; // } - // Run the resize handler incase the viewport has been resized since we switched buffers + // Run the resize handler in case the viewport has been resized since we switched buffers this._terminal.resize(this._terminal.cols, this._terminal.rows, true); this._terminal.refresh(0, this._terminal.rows - 1); diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 11bd62866d..a028cd1146 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -75,7 +75,10 @@ interface ICircularList { } export interface IWrappableList extends ICircularList { - transform(width: number): void; + /** + * Reflows lines in this list to a new maxwidth. + */ + reflow(width: number): void; } export interface LinkMatcherOptions { diff --git a/src/Types.ts b/src/Types.ts index 896b729a81..3092ee39f1 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -12,3 +12,7 @@ export type LinkMatcher = { }; export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => void; + +export type CharData = [number, string | null, number]; + +export type RowData = CharData[]; diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index 4fcd84f4d5..89648a0dd2 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -1,4 +1,5 @@ import { CircularList } from './CircularList'; +import { RowData } from '../Types'; // Much faster than native filter // https://jsperf.com/function-loops/4 @@ -14,6 +15,14 @@ function standardFilter(array, fn) { return results; } +function fastForeach(array, fn) { + let i = 0; + let len = array.length; + for (i; i < len; i++) { + fn(array[i]); + } +} + function trimmedLength(line, min) { let i = line.length - 1; for (i; i >= 0; i--) { @@ -48,40 +57,56 @@ function notNull(value) { return value !== null; } -export class WrappableList extends CircularList { +export class WrappableList extends CircularList { + private _wrappedLineIncrement: number[] = []; public wrappedLines: number[] = []; constructor(maxLength: number) { super(maxLength); } - // Need to make sure wrappedlines move when CircularList wraps around - public push(value: any[]): void { + public push(value: RowData): void { + // Need to make sure wrappedlines move when CircularList wraps around, but without increasing + // the time complexity of `push`. We push the number of `wrappedLines` that should be + // incremented so that it can be calculated later. if (this._length + 1 === this.maxLength) { - this.wrappedLines = this.wrappedLines.map(x => x - 1); + this._wrappedLineIncrement.push(this.wrappedLines.length); } - this._array[this._getCyclicIndex(this._length)] = value; - if (this._length === this.maxLength) { - this._startIndex++; - if (this._startIndex === this.maxLength) { - this._startIndex = 0; + super.push(value); + } + + public addWrappedLine(row: number): void { + this.wrappedLines.push(row); + } + + // Adjusts `wrappedLines` using `_wrappedLineIncrement` + private _adjustWrappedLines(): void { + fastForeach(this._wrappedLineIncrement, (end) => { + let i = 0; + for (i; i < end; i++) { + this.wrappedLines[i] -= 1; } - } else { - this._length++; - } + }); + this._wrappedLineIncrement = []; } - public transform(width) { - let wrappedLines = this.wrappedLines; + /** + * Reflow lines to a new max width. + * A record of which lines are wrapped is stored in `wrappedLines` and is used to join and split + * lines correctly. + */ + public reflow(width: number): void { + this._adjustWrappedLines(); + const wrappedLines = this.wrappedLines; - let temp = []; - let tempWrapped = []; - let skip = []; + const temp = []; + const tempWrapped = []; + const skip = []; const previouslyWrapped = (i) => wrappedLines.indexOf(i) > -1; const concatWrapped = (line, index) => { - line = standardFilter(line, notNull).concat(this._array[index + 1]); + line = standardFilter(line, notNull).concat(this.get(index + 1)); skip.push(index + 1); if (previouslyWrapped(index + 1)) { return concatWrapped(line, index + 1); @@ -115,9 +140,8 @@ export class WrappableList extends CircularList { } }); - let cachedStartIndex = this._startIndex; + // Reset the list internals using the reflowed lines const scrollback = temp.length > this.maxLength ? temp.length : this.maxLength; - this.maxLength = scrollback; this._length = temp.length; this._array = temp; this._array.length = scrollback; diff --git a/src/xterm.js b/src/xterm.js index c8c5925718..4f4fb097e5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -1850,7 +1850,7 @@ Terminal.prototype.resize = function(x, y, force) { let cachedLines = this.lines.length; - this.lines.transform(x); + this.lines.reflow(x); let ymove = this.lines.length - cachedLines; @@ -1955,8 +1955,6 @@ Terminal.prototype.resize = function(x, y, force) { this.refresh(0, this.rows - 1); - // this.normal = null; - this.geometry = [this.cols, this.rows]; this.emit('resize', {terminal: this, cols: x, rows: y}); }; From 893289cb27543ecaee50016e938759844b02a1e9 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Thu, 27 Apr 2017 08:59:27 +0100 Subject: [PATCH 05/17] Remove use of `.bind` and switch to `.length` in forEach call --- src/utils/CircularList.ts | 5 ++--- src/utils/WrappableList.ts | 6 +----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts index 8a4f13cf0f..d8f6623405 100644 --- a/src/utils/CircularList.ts +++ b/src/utils/CircularList.ts @@ -44,11 +44,10 @@ export class CircularList { } public forEach(callbackfn: (value: T, index?: number, array?: T[]) => void): void { - let get = this.get.bind(this); let i = 0; - let len = this.maxLength; + let len = this.length; for (i; i < len; i++) { - callbackfn(get(i), i); + callbackfn(this.get(i), i); } } diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index 89648a0dd2..818452cf68 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -31,11 +31,7 @@ function trimmedLength(line, min) { } } - if (i < min) { - // i = min; - } else { - // 2 extra blank chars allows for cursor and ensures at least one element is in array (in case - // of intentional blank rows) + if (i >= min) { i++; } From d1fb763ce74344f5ea304c72775e77ad49e7f4dc Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Thu, 27 Apr 2017 10:14:25 +0100 Subject: [PATCH 06/17] Performance improvements and added throttle to resize function. --- src/utils/Throttle.js | 38 +++++++++++++++++++++ src/utils/WrappableList.ts | 68 +++++++++++++++++++++----------------- src/xterm.js | 7 ++-- 3 files changed, 80 insertions(+), 33 deletions(-) create mode 100644 src/utils/Throttle.js diff --git a/src/utils/Throttle.js b/src/utils/Throttle.js new file mode 100644 index 0000000000..9f9678e9de --- /dev/null +++ b/src/utils/Throttle.js @@ -0,0 +1,38 @@ +// Returns a function, that, when invoked, will only be triggered at most once +// during a given window of time. Normally, the throttled function will run +// as much as it can, without ever going more than once per `wait` duration; +// but if you'd like to disable the execution on the leading edge, pass +// `{leading: false}`. To disable execution on the trailing edge, ditto. +const throttle = function(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + if (!options) options = {}; + var later = function() { + previous = options.leading === false ? 0 : Date.now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + return function() { + var now = Date.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; +}; + +export default throttle; diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index 818452cf68..a81b373dd3 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -19,7 +19,7 @@ function fastForeach(array, fn) { let i = 0; let len = array.length; for (i; i < len; i++) { - fn(array[i]); + fn(array[i], i, array); } } @@ -86,55 +86,61 @@ export class WrappableList extends CircularList { this._wrappedLineIncrement = []; } + private _numArrayToObject(array: number[]) { + let i = 0; + let len = array.length; + let returnObject = {}; + for (i; i < len; i++) { + returnObject[array[i]] = null; + } + return returnObject; + } + /** * Reflow lines to a new max width. * A record of which lines are wrapped is stored in `wrappedLines` and is used to join and split * lines correctly. */ public reflow(width: number): void { - this._adjustWrappedLines(); - const wrappedLines = this.wrappedLines; - const temp = []; const tempWrapped = []; const skip = []; - const previouslyWrapped = (i) => wrappedLines.indexOf(i) > -1; + this._adjustWrappedLines(); + // Using in index accessor is much quicker when we need to calculate previouslyWrapped many times + const wrappedLinesObject = this._numArrayToObject(this.wrappedLines); + const previouslyWrapped = (i) => wrappedLinesObject[i] !== undefined; const concatWrapped = (line, index) => { - line = standardFilter(line, notNull).concat(this.get(index + 1)); - skip.push(index + 1); - if (previouslyWrapped(index + 1)) { - return concatWrapped(line, index + 1); - } else { - return line; + let next = index; + while (previouslyWrapped(next)) { + next++; + skip.push(next); + line = line.concat(this.get(next)); } + return next === index ? line : standardFilter(line, notNull); }; - this.forEach((line, index) => { - if (!line) { - return; - } - if (skip.indexOf(index) > -1) { - return; - } + const reflowLine = (line, index) => { + if (line && skip.indexOf(index) === -1) { - if (previouslyWrapped(index)) { line = concatWrapped(line, index); - } - const trim = trimmedLength(line, width); - if (trim > width) { - chunkArray(width, line.slice(0, trim)).forEach((line, i, chunks) => { - temp.push(line); - if (i < chunks.length - 1) { - tempWrapped.push(temp.length - 1); - } - }); - } else { - temp.push(line.slice(0, width)); + const trim = trimmedLength(line, width); + if (trim > width) { + fastForeach(chunkArray(width, line.slice(0, trim)), (chunk, i, chunks) => { + temp.push(chunk); + if (i < chunks.length - 1) { + tempWrapped.push(temp.length - 1); + } + }); + } else { + temp.push(line.slice(0, width)); + } } - }); + }; + + this.forEach(reflowLine); // Reset the list internals using the reflowed lines const scrollback = temp.length > this.maxLength ? temp.length : this.maxLength; diff --git a/src/xterm.js b/src/xterm.js index 4f4fb097e5..8b1182019d 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -24,6 +24,7 @@ import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; import { CHARSETS } from './Charsets'; +import throttle from './utils/Throttle'; /** * Terminal Emulation References: @@ -1826,7 +1827,7 @@ Terminal.prototype.error = function() { * @param {number} x The number of columns to resize to. * @param {number} y The number of rows to resize to. */ -Terminal.prototype.resize = function(x, y, force) { +Terminal.prototype.resize = throttle(function(x, y, force) { if (isNaN(x) || isNaN(y)) { return; } @@ -1842,6 +1843,8 @@ Terminal.prototype.resize = function(x, y, force) { return; } + let startTime = Date.now(); + if (x < 1) x = 1; if (y < 1) y = 1; @@ -1957,7 +1960,7 @@ Terminal.prototype.resize = function(x, y, force) { this.geometry = [this.cols, this.rows]; this.emit('resize', {terminal: this, cols: x, rows: y}); -}; +}, 250); /** * Updates the range of rows to refresh From e680d4d29e94589870d8986a681e27ddcaed01f7 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Fri, 28 Apr 2017 07:03:33 +0100 Subject: [PATCH 07/17] Removed Throttle function --- src/utils/Throttle.js | 38 -------------------------------------- src/xterm.js | 5 ++--- 2 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 src/utils/Throttle.js diff --git a/src/utils/Throttle.js b/src/utils/Throttle.js deleted file mode 100644 index 9f9678e9de..0000000000 --- a/src/utils/Throttle.js +++ /dev/null @@ -1,38 +0,0 @@ -// Returns a function, that, when invoked, will only be triggered at most once -// during a given window of time. Normally, the throttled function will run -// as much as it can, without ever going more than once per `wait` duration; -// but if you'd like to disable the execution on the leading edge, pass -// `{leading: false}`. To disable execution on the trailing edge, ditto. -const throttle = function(func, wait, options) { - var context, args, result; - var timeout = null; - var previous = 0; - if (!options) options = {}; - var later = function() { - previous = options.leading === false ? 0 : Date.now(); - timeout = null; - result = func.apply(context, args); - if (!timeout) context = args = null; - }; - return function() { - var now = Date.now(); - if (!previous && options.leading === false) previous = now; - var remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0 || remaining > wait) { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - previous = now; - result = func.apply(context, args); - if (!timeout) context = args = null; - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; -}; - -export default throttle; diff --git a/src/xterm.js b/src/xterm.js index 8b1182019d..a339fda81b 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -24,7 +24,6 @@ import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; import { CHARSETS } from './Charsets'; -import throttle from './utils/Throttle'; /** * Terminal Emulation References: @@ -1827,7 +1826,7 @@ Terminal.prototype.error = function() { * @param {number} x The number of columns to resize to. * @param {number} y The number of rows to resize to. */ -Terminal.prototype.resize = throttle(function(x, y, force) { +Terminal.prototype.resize = function(x, y, force) { if (isNaN(x) || isNaN(y)) { return; } @@ -1960,7 +1959,7 @@ Terminal.prototype.resize = throttle(function(x, y, force) { this.geometry = [this.cols, this.rows]; this.emit('resize', {terminal: this, cols: x, rows: y}); -}, 250); +}; /** * Updates the range of rows to refresh From 0e37a42e9b8d8465d331bca9d717c78beb3d6e95 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Fri, 28 Apr 2017 07:35:43 +0100 Subject: [PATCH 08/17] Streamlined concatWrapped logic --- src/utils/WrappableList.ts | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index a81b373dd3..675a0815bf 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -1,20 +1,6 @@ import { CircularList } from './CircularList'; import { RowData } from '../Types'; -// Much faster than native filter -// https://jsperf.com/function-loops/4 -function standardFilter(array, fn) { - let results = []; - let item; - let i; - let len; - for (i = 0, len = array.length; i < len; i++) { - item = array[i]; - if (fn(item)) results.push(item); - } - return results; -} - function fastForeach(array, fn) { let i = 0; let len = array.length; @@ -49,10 +35,6 @@ function chunkArray(chunkSize, array) { return temparray; } -function notNull(value) { - return value !== null; -} - export class WrappableList extends CircularList { private _wrappedLineIncrement: number[] = []; public wrappedLines: number[] = []; @@ -105,6 +87,7 @@ export class WrappableList extends CircularList { const temp = []; const tempWrapped = []; const skip = []; + const wrappedLines = this.wrappedLines; this._adjustWrappedLines(); // Using in index accessor is much quicker when we need to calculate previouslyWrapped many times @@ -118,7 +101,7 @@ export class WrappableList extends CircularList { skip.push(next); line = line.concat(this.get(next)); } - return next === index ? line : standardFilter(line, notNull); + return line; }; const reflowLine = (line, index) => { From d176aeb773f6f925f9bb1d0fbd58fedc1f98c327 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Fri, 28 Apr 2017 07:47:20 +0100 Subject: [PATCH 09/17] Improved trimmedLength performance --- src/utils/WrappableList.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index 675a0815bf..d00249b5c8 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -10,9 +10,10 @@ function fastForeach(array, fn) { } function trimmedLength(line, min) { - let i = line.length - 1; - for (i; i >= 0; i--) { - if (line[i] && line[i][1] !== null) { + let i = 0; + let len = line.length; + for (i; i < len; i++) { + if (line[i] && line[i][1] === null) { break; } } From ca6259692f5555633ed9f15c95f708091593c73f Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Fri, 28 Apr 2017 07:56:25 +0100 Subject: [PATCH 10/17] Dramatic performance improvements by removing use of indexOf --- src/utils/WrappableList.ts | 9 ++++----- src/xterm.js | 2 -- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index d00249b5c8..42da1769ec 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -87,26 +87,25 @@ export class WrappableList extends CircularList { public reflow(width: number): void { const temp = []; const tempWrapped = []; - const skip = []; + const skip = {}; const wrappedLines = this.wrappedLines; this._adjustWrappedLines(); // Using in index accessor is much quicker when we need to calculate previouslyWrapped many times const wrappedLinesObject = this._numArrayToObject(this.wrappedLines); - const previouslyWrapped = (i) => wrappedLinesObject[i] !== undefined; const concatWrapped = (line, index) => { let next = index; - while (previouslyWrapped(next)) { + while (wrappedLinesObject[next] !== undefined) { next++; - skip.push(next); + skip[next] = null; line = line.concat(this.get(next)); } return line; }; const reflowLine = (line, index) => { - if (line && skip.indexOf(index) === -1) { + if (line && skip[index] === undefined) { line = concatWrapped(line, index); diff --git a/src/xterm.js b/src/xterm.js index a339fda81b..4f4fb097e5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -1842,8 +1842,6 @@ Terminal.prototype.resize = function(x, y, force) { return; } - let startTime = Date.now(); - if (x < 1) x = 1; if (y < 1) y = 1; From 09df48d8c1dc29d9ec40dd9505f040f72e58bdb5 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Mon, 1 May 2017 20:38:27 +0100 Subject: [PATCH 11/17] Reduced trimmed length complexity --- src/utils/WrappableList.ts | 8 ++------ src/xterm.js | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index 42da1769ec..bfae10d1e9 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -9,7 +9,7 @@ function fastForeach(array, fn) { } } -function trimmedLength(line, min) { +function trimmedLength(line) { let i = 0; let len = line.length; for (i; i < len; i++) { @@ -18,10 +18,6 @@ function trimmedLength(line, min) { } } - if (i >= min) { - i++; - } - return i; } @@ -109,7 +105,7 @@ export class WrappableList extends CircularList { line = concatWrapped(line, index); - const trim = trimmedLength(line, width); + const trim = trimmedLength(line); if (trim > width) { fastForeach(chunkArray(width, line.slice(0, trim)), (chunk, i, chunks) => { temp.push(chunk); diff --git a/src/xterm.js b/src/xterm.js index 4f4fb097e5..a5ed4c94f9 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -1831,6 +1831,8 @@ Terminal.prototype.resize = function(x, y, force) { return; } + var startTime = Date.now(); + var line , el , i @@ -1957,6 +1959,8 @@ Terminal.prototype.resize = function(x, y, force) { this.geometry = [this.cols, this.rows]; this.emit('resize', {terminal: this, cols: x, rows: y}); + + console.log('RESIZE IN', Date.now() - startTime); }; /** From 54b50a4f4d8219d387632fb5049ac4ed43380bda Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Mon, 1 May 2017 20:45:14 +0100 Subject: [PATCH 12/17] reducing reflow complexity --- src/utils/WrappableList.ts | 53 +++++++++++++++++++++----------------- src/xterm.js | 21 ++++----------- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index bfae10d1e9..095661b81c 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -80,46 +80,51 @@ export class WrappableList extends CircularList { * A record of which lines are wrapped is stored in `wrappedLines` and is used to join and split * lines correctly. */ - public reflow(width: number): void { + public reflow(width: number, oldWidth: number): void { const temp = []; const tempWrapped = []; - const skip = {}; const wrappedLines = this.wrappedLines; + let masterIndex = 0; + let len = this.length; + let line; + let trim; + let isWidthDecreasing = width < oldWidth; this._adjustWrappedLines(); // Using in index accessor is much quicker when we need to calculate previouslyWrapped many times const wrappedLinesObject = this._numArrayToObject(this.wrappedLines); - const concatWrapped = (line, index) => { + const concatWrapped = (data, index) => { let next = index; while (wrappedLinesObject[next] !== undefined) { next++; - skip[next] = null; - line = line.concat(this.get(next)); + masterIndex++; + Array.prototype.push.apply(data, this.get(next)); } - return line; + return data; }; - const reflowLine = (line, index) => { - if (line && skip[index] === undefined) { - - line = concatWrapped(line, index); - - const trim = trimmedLength(line); - if (trim > width) { - fastForeach(chunkArray(width, line.slice(0, trim)), (chunk, i, chunks) => { - temp.push(chunk); - if (i < chunks.length - 1) { - tempWrapped.push(temp.length - 1); - } - }); - } else { - temp.push(line.slice(0, width)); + // A for loop is used here so that masterIndex can be advanced when concatting lines in + // the 'concatWrapped' method + for (masterIndex; masterIndex < len; masterIndex++) { + line = concatWrapped(this.get(masterIndex), masterIndex); + trim = trimmedLength(line); + + if (trim > width) { + line.length = trim; + fastForeach(chunkArray(width, line), (chunk, i, chunks) => { + temp.push(chunk); + if (i < chunks.length - 1) { + tempWrapped.push(temp.length - 1); + } + }); + } else { + if (isWidthDecreasing) { + line.length = width; } + temp.push(line); } - }; - - this.forEach(reflowLine); + } // Reset the list internals using the reflowed lines const scrollback = temp.length > this.maxLength ? temp.length : this.maxLength; diff --git a/src/xterm.js b/src/xterm.js index a5ed4c94f9..67cec49e42 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -1831,8 +1831,6 @@ Terminal.prototype.resize = function(x, y, force) { return; } - var startTime = Date.now(); - var line , el , i @@ -1852,7 +1850,11 @@ Terminal.prototype.resize = function(x, y, force) { let cachedLines = this.lines.length; - this.lines.reflow(x); + var startTime = Date.now(); + + this.lines.reflow(x, this.cols); + + console.log('RESIZE IN', Date.now() - startTime); let ymove = this.lines.length - cachedLines; @@ -1871,17 +1873,6 @@ Terminal.prototype.resize = function(x, y, force) { } } - if (j < x) { - ch = [this.defAttr, null, 1]; // does xterm use the default attr? - i = this.lines.length; - while (i--) { - while (this.lines.get(i).length < x) { - this.lines.get(i).push(ch); - } - } - } - - this.cols = x; this.setupStops(this.cols); @@ -1959,8 +1950,6 @@ Terminal.prototype.resize = function(x, y, force) { this.geometry = [this.cols, this.rows]; this.emit('resize', {terminal: this, cols: x, rows: y}); - - console.log('RESIZE IN', Date.now() - startTime); }; /** From a3ade3765c02e6f87cbe29d085e1a7e37a98637d Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Wed, 3 May 2017 07:31:08 +0100 Subject: [PATCH 13/17] Fixed undefined character bug --- src/utils/WrappableList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index 095661b81c..c70b0cbb72 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -13,7 +13,7 @@ function trimmedLength(line) { let i = 0; let len = line.length; for (i; i < len; i++) { - if (line[i] && line[i][1] === null) { + if (!line[i] || (line[i] && line[i][1] === null)) { break; } } From 2722a7d2a76eed20916c6dda6aa4eea4270b7f46 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Wed, 3 May 2017 07:47:20 +0100 Subject: [PATCH 14/17] Fixed tab characters --- src/InputHandler.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 476af4a221..9fd32d06a5 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -78,7 +78,7 @@ export class InputHandler implements IInputHandler { if (removed[2] === 0 && this._terminal.lines.get(row)[this._terminal.cols - 2] && this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2) - this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, null, 1]; + this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1]; // insert empty cell at cursor this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, null, 1]); @@ -154,7 +154,12 @@ export class InputHandler implements IInputHandler { * Horizontal Tab (HT) (Ctrl-I). */ public tab(): void { - this._terminal.x = this._terminal.nextStop(); + const stop = this._terminal.nextStop(); + let row = this._terminal.lines.get(this._terminal.y + this._terminal.ybase); + while (this._terminal.x <= stop) { + row[this._terminal.x] = [this._terminal.defAttr, ' ', 1]; + this._terminal.x++; + } } /** @@ -187,7 +192,7 @@ export class InputHandler implements IInputHandler { row = this._terminal.y + this._terminal.ybase; j = this._terminal.x; - ch = [this._terminal.eraseAttr(), null, 1]; // xterm + ch = [this._terminal.eraseAttr(), ' ', 1]; // xterm while (param-- && j < this._terminal.cols) { this._terminal.lines.get(row).splice(j++, 0, ch); From 7ac41195611f3ba0eac334457f548d2dcfade987 Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Thu, 4 May 2017 09:54:07 +0100 Subject: [PATCH 15/17] Fix tests to account for null characters being used instead of spaces. --- src/test/escape-sequences-test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/escape-sequences-test.js b/src/test/escape-sequences-test.js index 49f623416b..690fd7bdf4 100644 --- a/src/test/escape-sequences-test.js +++ b/src/test/escape-sequences-test.js @@ -62,10 +62,12 @@ function formatError(in_, out_, expected) { function terminalToString(term) { var result = ''; var line_s = ''; + var ch; for (var line = term.ybase; line < term.ybase + term.rows; line++) { line_s = ''; for (var cell=0; cell Date: Mon, 15 May 2017 03:39:08 +0100 Subject: [PATCH 16/17] Increase reflow performance, don't go over max scrollback, don't manipulate circular list internals --- src/Interfaces.ts | 3 ++- src/Renderer.ts | 6 +++++ src/utils/WrappableList.ts | 55 +++++++++++++++++++++++++++++--------- src/xterm.js | 4 +-- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index a028cd1146..191831360d 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -78,7 +78,8 @@ export interface IWrappableList extends ICircularList { /** * Reflows lines in this list to a new maxwidth. */ - reflow(width: number): void; + wrappedLines: number[]; + reflow(width: number, oldWidth: number): IWrappableList; } export interface LinkMatcherOptions { diff --git a/src/Renderer.ts b/src/Renderer.ts index 4131abc4b2..6cbb82eca1 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -278,6 +278,12 @@ export class Renderer { } this._terminal.children[y].innerHTML = out; + + if (this._terminal.lines.wrappedLines.indexOf(row) > -1) { + this._terminal.children[y].style.background = 'red'; + } else { + this._terminal.children[y].style.background = 'black'; + } } if (parent) { diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index c70b0cbb72..75b0c1481b 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -1,6 +1,8 @@ import { CircularList } from './CircularList'; import { RowData } from '../Types'; +import { IWrappableList } from '../Interfaces'; + function fastForeach(array, fn) { let i = 0; let len = array.length; @@ -32,12 +34,20 @@ function chunkArray(chunkSize, array) { return temparray; } +function fastCeil(n: number): number { + let f = (n << 0); + return f === n ? f : f + 1; +} + export class WrappableList extends CircularList { private _wrappedLineIncrement: number[] = []; + private _blankline: RowData; public wrappedLines: number[] = []; - constructor(maxLength: number) { + constructor(maxLength: number, private _terminal) { super(maxLength); + + this._blankline = this._terminal.blankLine(); } public push(value: RowData): void { @@ -80,7 +90,7 @@ export class WrappableList extends CircularList { * A record of which lines are wrapped is stored in `wrappedLines` and is used to join and split * lines correctly. */ - public reflow(width: number, oldWidth: number): void { + public reflow(width: number, oldWidth: number) { const temp = []; const tempWrapped = []; const wrappedLines = this.wrappedLines; @@ -89,6 +99,8 @@ export class WrappableList extends CircularList { let line; let trim; let isWidthDecreasing = width < oldWidth; + let i = 0; + let xj; this._adjustWrappedLines(); // Using in index accessor is much quicker when we need to calculate previouslyWrapped many times @@ -112,12 +124,18 @@ export class WrappableList extends CircularList { if (trim > width) { line.length = trim; - fastForeach(chunkArray(width, line), (chunk, i, chunks) => { - temp.push(chunk); - if (i < chunks.length - 1) { + xj = fastCeil(trim / width) - 1; + for (i = 0; i < trim; i += width) { + if (width > line.length) { + temp.push(line); + } else { + temp.push(line.splice(0, width)); + } + + if (xj-- > 0) { tempWrapped.push(temp.length - 1); } - }); + } } else { if (isWidthDecreasing) { line.length = width; @@ -126,12 +144,23 @@ export class WrappableList extends CircularList { } } - // Reset the list internals using the reflowed lines - const scrollback = temp.length > this.maxLength ? temp.length : this.maxLength; - this._length = temp.length; - this._array = temp; - this._array.length = scrollback; - this.wrappedLines = tempWrapped; - this._startIndex = 0; + // Chop the reflow list to length and push it into a new CircularList, also compensate wrapped + // lines for new start point of list + const scrollback = this.maxLength; + let pushStart = temp.length > scrollback ? + temp.length - scrollback : + 0; + if (pushStart > 0) { + for (i = 0; i < tempWrapped.length; i++) { + tempWrapped[i] -= pushStart; + } + } + let newList = new WrappableList(scrollback, this._terminal); + for (i = pushStart; i < temp.length; i++) { + newList.push(temp[i]); + } + newList.wrappedLines = tempWrapped; + + return newList; } } diff --git a/src/xterm.js b/src/xterm.js index 67cec49e42..32912af13e 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -243,7 +243,7 @@ function Terminal(options) { * An array of all lines in the entire buffer, including the prompt. The lines are array of * characters which are 2-length arrays where [0] is an attribute and [1] is the character. */ - this.lines = new WrappableList(this.scrollback); + this.lines = new WrappableList(this.scrollback, this); var i = this.rows; while (i--) { this.lines.push(this.blankLine()); @@ -1852,7 +1852,7 @@ Terminal.prototype.resize = function(x, y, force) { var startTime = Date.now(); - this.lines.reflow(x, this.cols); + this.lines = this.lines.reflow(x, this.cols); console.log('RESIZE IN', Date.now() - startTime); From 4fa6db6a00b05555dc4e35f739d009cead4cc7cd Mon Sep 17 00:00:00 2001 From: Lucian Buzzo Date: Mon, 15 May 2017 05:27:41 +0100 Subject: [PATCH 17/17] remove unused functions --- src/utils/WrappableList.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/utils/WrappableList.ts b/src/utils/WrappableList.ts index 75b0c1481b..7ac771e6fd 100644 --- a/src/utils/WrappableList.ts +++ b/src/utils/WrappableList.ts @@ -23,22 +23,6 @@ function trimmedLength(line) { return i; } -function chunkArray(chunkSize, array) { - let temparray = []; - let i = 0; - let j = array.length; - for (i; i < j; i += chunkSize) { - temparray.push(array.slice(i, i + chunkSize)); - } - - return temparray; -} - -function fastCeil(n: number): number { - let f = (n << 0); - return f === n ? f : f + 1; -} - export class WrappableList extends CircularList { private _wrappedLineIncrement: number[] = []; private _blankline: RowData;