From 047729ee9e0901dcd3eaa0f35fecfb3d2a39725a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 31 Jan 2017 21:23:21 -0800 Subject: [PATCH 1/7] Don't requeue animation frame if not needed Fixes #517 --- src/xterm.js | 64 +++++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index f88c9452..fa8d6ad4 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -222,6 +222,7 @@ function Terminal(options) { this.writeBuffer = []; this.writeInProgress = false; this.refreshFramesSkipped = 0; + this.refreshAnimationFrame = null; /** * Whether _xterm.js_ sent XOFF in order to catch up with the pty process. @@ -659,7 +660,6 @@ Terminal.prototype.open = function(parent) { // Setup loop that draws to screen this.queueRefresh(0, this.rows - 1); - this.refreshLoop(); // Initialize global actions that // need to be taken on the document. @@ -1085,6 +1085,9 @@ Terminal.flags = { */ Terminal.prototype.queueRefresh = function(start, end) { this.refreshRowsQueue.push({ start: start, end: end }); + if (!this.refreshAnimationFrame) { + this.refreshAnimationFrame = window.requestAnimationFrame(this.refreshLoop.bind(this)); + } } /** @@ -1092,40 +1095,39 @@ Terminal.prototype.queueRefresh = function(start, end) { * necessary before queueing up the next one. */ Terminal.prototype.refreshLoop = function() { - // Don't refresh if there were no row changes - if (this.refreshRowsQueue.length > 0) { - // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it - // will need to be immediately refreshed anyway. This saves a lot of - // rendering time as the viewport DOM does not need to be refreshed, no - // scroll events, no layouts, etc. - var skipFrame = this.writeBuffer.length > 0 && this.refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; + // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it + // will need to be immediately refreshed anyway. This saves a lot of + // rendering time as the viewport DOM does not need to be refreshed, no + // scroll events, no layouts, etc. + var skipFrame = this.writeBuffer.length > 0 && this.refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; + if (skipFrame) { + this.refreshAnimationFrame = window.requestAnimationFrame(this.refreshLoop.bind(this)); + return; + } - if (!skipFrame) { - this.refreshFramesSkipped = 0; - var start; - var end; - if (this.refreshRowsQueue.length > 4) { - // Just do a full refresh when 5+ refreshes are queued - start = 0; - end = this.rows - 1; - } else { - // Get start and end rows that need refreshing - start = this.refreshRowsQueue[0].start; - end = this.refreshRowsQueue[0].end; - for (var i = 1; i < this.refreshRowsQueue.length; i++) { - if (this.refreshRowsQueue[i].start < start) { - start = this.refreshRowsQueue[i].start; - } - if (this.refreshRowsQueue[i].end > end) { - end = this.refreshRowsQueue[i].end; - } - } + this.refreshFramesSkipped = 0; + var start; + var end; + if (this.refreshRowsQueue.length > 4) { + // Just do a full refresh when 5+ refreshes are queued + start = 0; + end = this.rows - 1; + } else { + // Get start and end rows that need refreshing + start = this.refreshRowsQueue[0].start; + end = this.refreshRowsQueue[0].end; + for (var i = 1; i < this.refreshRowsQueue.length; i++) { + if (this.refreshRowsQueue[i].start < start) { + start = this.refreshRowsQueue[i].start; + } + if (this.refreshRowsQueue[i].end > end) { + end = this.refreshRowsQueue[i].end; } - this.refreshRowsQueue = []; - this.refresh(start, end); } } - window.requestAnimationFrame(this.refreshLoop.bind(this)); + this.refreshRowsQueue = []; + this.refreshAnimationFrame = null; + this.refresh(start, end); } /** From 92068f36f2e02ec4c10c4f88f6f79f8ce9fbcd61 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 31 Jan 2017 22:45:28 -0800 Subject: [PATCH 2/7] Pull renderer out of xterm.js --- src/Interfaces.ts | 27 ++++- src/Renderer.ts | 303 ++++++++++++++++++++++++++++++++++++++++++++++ src/xterm.js | 298 +++------------------------------------------ 3 files changed, 345 insertions(+), 283 deletions(-) create mode 100644 src/Renderer.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 049a599c..c876d256 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -18,10 +18,19 @@ export interface ITerminal { element: HTMLElement; rowContainer: HTMLElement; textarea: HTMLTextAreaElement; + ybase: number; ydisp: number; - lines: string[]; + lines: ICircularList; rows: number; + cols: number; browser: IBrowser; + writeBuffer: string[]; + children: HTMLElement[]; + cursorHidden: boolean; + cursorState: number; + x: number; + y: number; + defAttr: number; /** * Emit the 'data' event and populate the given data. @@ -31,6 +40,22 @@ export interface ITerminal { on(event: string, callback: () => void); scrollDisp(disp: number, suppressScrollEvent: boolean); cancel(ev: Event, force?: boolean); + log(text: string): void; + emit(event: string, data: any); +} + +interface ICircularList { + length: number; + maxLength: number; + + forEach(callbackfn: (value: T, index: number, array: T[]) => void): void; + get(index: number): T; + set(index: number, value: T): void; + push(value: T): void; + pop(): T; + splice(start: number, deleteCount: number, ...items: T[]): void; + trimStart(count: number): void; + shiftElements(start: number, count: number, offset: number): void; } /** diff --git a/src/Renderer.ts b/src/Renderer.ts new file mode 100644 index 00000000..e43c77a2 --- /dev/null +++ b/src/Renderer.ts @@ -0,0 +1,303 @@ +import { ITerminal } from './Interfaces'; + +/** + * The maximum number of refresh frames to skip when the write buffer is non- + * empty. Note that these frames may be intermingled with frames that are + * skipped via requestAnimationFrame's mechanism. + */ +const MAX_REFRESH_FRAME_SKIP = 5; + +// TODO: Convert flags to number enum +/** + * Flags used to render terminal text properly + */ +const FLAGS = { + BOLD: 1, + UNDERLINE: 2, + BLINK: 4, + INVERSE: 8, + INVISIBLE: 16 +}; + +let brokenBold: boolean = null; + +export class Renderer { + /** A queue of the rows to be refreshed */ + private _refreshRowsQueue: {start: number, end: number}[] = []; + private _refreshFramesSkipped = 0; + private _refreshAnimationFrame = null; + + constructor(private _terminal: ITerminal) { + // Figure out whether boldness affects + // the character width of monospace fonts. + if (brokenBold === null) { + brokenBold = checkBoldBroken((this._terminal).document); + console.log('check brokenBold: ' + brokenBold); + } + + // TODO: Pull all DOM interactions into Renderer.constructor + } + + /** + * Queues a refresh between two rows (inclusive), to be done on next animation + * frame. + * @param {number} start The start row. + * @param {number} end The end row. + */ + public queueRefresh(start: number, end: number): void { + this._refreshRowsQueue.push({ start: start, end: end }); + if (!this._refreshAnimationFrame) { + this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); + } + } + + /** + * Performs the refresh loop callback, calling refresh only if a refresh is + * necessary before queueing up the next one. + */ + private _refreshLoop(): void { + // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it + // will need to be immediately refreshed anyway. This saves a lot of + // rendering time as the viewport DOM does not need to be refreshed, no + // scroll events, no layouts, etc. + const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; + if (skipFrame) { + this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); + return; + } + + this._refreshFramesSkipped = 0; + let start; + let end; + if (this._refreshRowsQueue.length > 4) { + // Just do a full refresh when 5+ refreshes are queued + start = 0; + end = this._terminal.rows - 1; + } else { + // Get start and end rows that need refreshing + start = this._refreshRowsQueue[0].start; + end = this._refreshRowsQueue[0].end; + for (let i = 1; i < this._refreshRowsQueue.length; i++) { + if (this._refreshRowsQueue[i].start < start) { + start = this._refreshRowsQueue[i].start; + } + if (this._refreshRowsQueue[i].end > end) { + end = this._refreshRowsQueue[i].end; + } + } + } + this._refreshRowsQueue = []; + this._refreshAnimationFrame = null; + this._refresh(start, end); + } + + /** + * Refreshes (re-renders) terminal content within two rows (inclusive) + * + * Rendering Engine: + * + * In the screen buffer, each character is stored as a an array with a character + * and a 32-bit integer: + * - First value: a utf-16 character. + * - Second value: + * - Next 9 bits: background color (0-511). + * - Next 9 bits: foreground color (0-511). + * - Next 14 bits: a mask for misc. flags: + * - 1=bold + * - 2=underline + * - 4=blink + * - 8=inverse + * - 16=invisible + * + * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) + * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) + */ + private _refresh(start: number, end: number): void { + // TODO: Use fat arrow functions for callbacks to avoid `self` + let self = this; + + let x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement; + + // If this is a big refresh, remove the terminal rows from the DOM for faster calculations + if (end - start >= this._terminal.rows / 2) { + parent = this._terminal.element.parentNode; + if (parent) { + this._terminal.element.removeChild(this._terminal.rowContainer); + } + } + + width = this._terminal.cols; + y = start; + + if (end >= this._terminal.rows) { + this._terminal.log('`end` is too large. Most likely a bad CSR.'); + end = this._terminal.rows - 1; + } + + for (; y <= end; y++) { + row = y + this._terminal.ydisp; + + line = this._terminal.lines.get(row); + if (!line || !this._terminal.children[y]) { + // Continue if the line is not available, this means a resize is currently in progress + continue; + } + out = ''; + + if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp) + && this._terminal.cursorState + && !this._terminal.cursorHidden) { + x = this._terminal.x; + } else { + x = -1; + } + + attr = this._terminal.defAttr; + i = 0; + + for (; i < width; i++) { + if (!line[i]) { + // Continue if the character is not available, this means a resize is currently in progress + continue; + } + data = line[i][0]; + ch = line[i][1]; + ch_width = line[i][2]; + if (!ch_width) + continue; + + if (i === x) data = -1; + + if (data !== attr) { + if (attr !== this._terminal.defAttr) { + out += ''; + } + if (data !== this._terminal.defAttr) { + if (data === -1) { + out += ''; + } else { + let classNames = []; + + bg = data & 0x1ff; + fg = (data >> 9) & 0x1ff; + flags = data >> 18; + + if (flags & FLAGS.BOLD) { + if (!brokenBold) { + classNames.push('xterm-bold'); + } + // See: XTerm*boldColors + if (fg < 8) fg += 8; + } + + if (flags & FLAGS.UNDERLINE) { + classNames.push('xterm-underline'); + } + + if (flags & FLAGS.BLINK) { + classNames.push('xterm-blink'); + } + + // If inverse flag is on, then swap the foreground and background variables. + if (flags & FLAGS.INVERSE) { + /* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */ + bg = [fg, fg = bg][0]; + // Should inverse just be before the + // above boldColors effect instead? + if ((flags & 1) && fg < 8) fg += 8; + } + + if (flags & FLAGS.INVISIBLE) { + classNames.push('xterm-hidden'); + } + + /** + * Weird situation: Invert flag used black foreground and white background results + * in invalid background color, positioned at the 256 index of the 256 terminal + * color map. Pin the colors manually in such a case. + * + * Source: https://github.com/sourcelair/xterm.js/issues/57 + */ + if (flags & FLAGS.INVERSE) { + if (bg === 257) { + bg = 15; + } + if (fg === 256) { + fg = 0; + } + } + + if (bg < 256) { + classNames.push('xterm-bg-color-' + bg); + } + + if (fg < 256) { + classNames.push('xterm-color-' + fg); + } + + out += '': + out += '>'; + break; + default: + if (ch <= ' ') { + out += ' '; + } else { + out += ch; + } + break; + } + if (ch_width === 2) { + out += ''; + } + + attr = data; + } + + if (attr !== this._terminal.defAttr) { + out += ''; + } + + this._terminal.children[y].innerHTML = out; + } + + if (parent) { + this._terminal.element.appendChild(this._terminal.rowContainer); + } + + this._terminal.emit('refresh', {element: this._terminal.element, start: start, end: end}); + }; +} + + +// if bold is broken, we can't +// use it in the terminal. +function checkBoldBroken(document) { + const body = document.getElementsByTagName('body')[0]; + const el = document.createElement('span'); + el.innerHTML = 'hello world'; + body.appendChild(el); + const w1 = el.scrollWidth; + el.style.fontWeight = 'bold'; + const w2 = el.scrollWidth; + body.removeChild(el); + return w1 !== w2; +} diff --git a/src/xterm.js b/src/xterm.js index fa8d6ad4..49e0b531 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -18,6 +18,7 @@ import { CircularList } from './utils/CircularList'; import { C0 } from './EscapeSequences'; import { InputHandler } from './InputHandler'; import { Parser } from './Parser'; +import { Renderer } from './Renderer'; import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; import * as Keyboard from './utils/Keyboard'; @@ -50,13 +51,6 @@ var WRITE_BUFFER_PAUSE_THRESHOLD = 5; */ var WRITE_BATCH_SIZE = 300; -/** - * The maximum number of refresh frames to skip when the write buffer is non- - * empty. Note that these frames may be intermingled with frames that are - * skipped via requestAnimationFrame's mechanism. - */ -var MAX_REFRESH_FRAME_SKIP = 5; - /** * Terminal */ @@ -157,9 +151,6 @@ function Terminal(options) { */ this.y = 0; - /** A queue of the rows to be refreshed */ - this.refreshRowsQueue = []; - this.cursorState = 0; this.cursorHidden = false; this.convertEol; @@ -217,12 +208,11 @@ function Terminal(options) { this.inputHandler = new InputHandler(this); this.parser = new Parser(this.inputHandler, this); + this.renderer = null; // user input states this.writeBuffer = []; this.writeInProgress = false; - this.refreshFramesSkipped = 0; - this.refreshAnimationFrame = null; /** * Whether _xterm.js_ sent XOFF in order to catch up with the pty process. @@ -657,6 +647,7 @@ Terminal.prototype.open = function(parent) { this.charMeasure.measure(); this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure); + this.renderer = new Renderer(this); // Setup loop that draws to screen this.queueRefresh(0, this.rows - 1); @@ -681,12 +672,6 @@ Terminal.prototype.open = function(parent) { // them into terminal mouse protocols. this.bindMouse(); - // Figure out whether boldness affects - // the character width of monospace fonts. - if (Terminal.brokenBold == null) { - Terminal.brokenBold = isBoldBroken(this.document); - } - /** * This event is emitted when terminal has completed opening. * @@ -1065,263 +1050,26 @@ Terminal.prototype.destroy = function() { //this.emit('close'); }; - /** - * Flags used to render terminal text properly - */ -Terminal.flags = { - BOLD: 1, - UNDERLINE: 2, - BLINK: 4, - INVERSE: 8, - INVISIBLE: 16 -} - -/** - * Queues a refresh between two rows (inclusive), to be done on next animation - * frame. - * @param {number} start The start row. - * @param {number} end The end row. - */ -Terminal.prototype.queueRefresh = function(start, end) { - this.refreshRowsQueue.push({ start: start, end: end }); - if (!this.refreshAnimationFrame) { - this.refreshAnimationFrame = window.requestAnimationFrame(this.refreshLoop.bind(this)); - } -} - -/** - * Performs the refresh loop callback, calling refresh only if a refresh is - * necessary before queueing up the next one. - */ -Terminal.prototype.refreshLoop = function() { - // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it - // will need to be immediately refreshed anyway. This saves a lot of - // rendering time as the viewport DOM does not need to be refreshed, no - // scroll events, no layouts, etc. - var skipFrame = this.writeBuffer.length > 0 && this.refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP; - if (skipFrame) { - this.refreshAnimationFrame = window.requestAnimationFrame(this.refreshLoop.bind(this)); - return; - } - - this.refreshFramesSkipped = 0; - var start; - var end; - if (this.refreshRowsQueue.length > 4) { - // Just do a full refresh when 5+ refreshes are queued - start = 0; - end = this.rows - 1; - } else { - // Get start and end rows that need refreshing - start = this.refreshRowsQueue[0].start; - end = this.refreshRowsQueue[0].end; - for (var i = 1; i < this.refreshRowsQueue.length; i++) { - if (this.refreshRowsQueue[i].start < start) { - start = this.refreshRowsQueue[i].start; - } - if (this.refreshRowsQueue[i].end > end) { - end = this.refreshRowsQueue[i].end; - } - } - } - this.refreshRowsQueue = []; - this.refreshAnimationFrame = null; - this.refresh(start, end); -} - -/** - * Refreshes (re-renders) terminal content within two rows (inclusive) - * - * Rendering Engine: - * - * In the screen buffer, each character is stored as a an array with a character - * and a 32-bit integer: - * - First value: a utf-16 character. - * - Second value: - * - Next 9 bits: background color (0-511). - * - Next 9 bits: foreground color (0-511). - * - Next 14 bits: a mask for misc. flags: - * - 1=bold - * - 2=underline - * - 4=blink - * - 8=inverse - * - 16=invisible - * + * Tells the renderer to refresh terminal content between two rows (inclusive) at the next + * opportunity. * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) */ Terminal.prototype.refresh = function(start, end) { - var self = this; + this.queueRefresh(start, end); +}; - var x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement; - - // If this is a big refresh, remove the terminal rows from the DOM for faster calculations - if (end - start >= this.rows / 2) { - parent = this.element.parentNode; - if (parent) { - this.element.removeChild(this.rowContainer); - } +/** + * Tells the renderer to refresh terminal content between two rows (inclusive) at the next + * opportunity. + * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) + * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) + */ +Terminal.prototype.queueRefresh = function(start, end) { + if (this.renderer) { + this.renderer.queueRefresh(start, end); } - - width = this.cols; - y = start; - - if (end >= this.rows.length) { - this.log('`end` is too large. Most likely a bad CSR.'); - end = this.rows.length - 1; - } - - for (; y <= end; y++) { - row = y + this.ydisp; - - line = this.lines.get(row); - if (!line || !this.children[y]) { - // Continue if the line is not available, this means a resize is currently in progress - continue; - } - out = ''; - - if (this.y === y - (this.ybase - this.ydisp) - && this.cursorState - && !this.cursorHidden) { - x = this.x; - } else { - x = -1; - } - - attr = this.defAttr; - i = 0; - - for (; i < width; i++) { - if (!line[i]) { - // Continue if the character is not available, this means a resize is currently in progress - continue; - } - data = line[i][0]; - ch = line[i][1]; - ch_width = line[i][2]; - if (!ch_width) - continue; - - if (i === x) data = -1; - - if (data !== attr) { - if (attr !== this.defAttr) { - out += ''; - } - if (data !== this.defAttr) { - if (data === -1) { - out += ''; - } else { - var classNames = []; - - bg = data & 0x1ff; - fg = (data >> 9) & 0x1ff; - flags = data >> 18; - - if (flags & Terminal.flags.BOLD) { - if (!Terminal.brokenBold) { - classNames.push('xterm-bold'); - } - // See: XTerm*boldColors - if (fg < 8) fg += 8; - } - - if (flags & Terminal.flags.UNDERLINE) { - classNames.push('xterm-underline'); - } - - if (flags & Terminal.flags.BLINK) { - classNames.push('xterm-blink'); - } - - // If inverse flag is on, then swap the foreground and background variables. - if (flags & Terminal.flags.INVERSE) { - /* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */ - bg = [fg, fg = bg][0]; - // Should inverse just be before the - // above boldColors effect instead? - if ((flags & 1) && fg < 8) fg += 8; - } - - if (flags & Terminal.flags.INVISIBLE) { - classNames.push('xterm-hidden'); - } - - /** - * Weird situation: Invert flag used black foreground and white background results - * in invalid background color, positioned at the 256 index of the 256 terminal - * color map. Pin the colors manually in such a case. - * - * Source: https://github.com/sourcelair/xterm.js/issues/57 - */ - if (flags & Terminal.flags.INVERSE) { - if (bg == 257) { - bg = 15; - } - if (fg == 256) { - fg = 0; - } - } - - if (bg < 256) { - classNames.push('xterm-bg-color-' + bg); - } - - if (fg < 256) { - classNames.push('xterm-color-' + fg); - } - - out += '': - out += '>'; - break; - default: - if (ch <= ' ') { - out += ' '; - } else { - out += ch; - } - break; - } - if (ch_width === 2) { - out += ''; - } - - attr = data; - } - - if (attr !== this.defAttr) { - out += ''; - } - - this.children[y].innerHTML = out; - } - - if (parent) { - this.element.appendChild(this.rowContainer); - } - - this.emit('refresh', {element: this.element, start: start, end: end}); }; /** @@ -2389,20 +2137,6 @@ function inherits(child, parent) { child.prototype = new f; } -// if bold is broken, we can't -// use it in the terminal. -function isBoldBroken(document) { - var body = document.getElementsByTagName('body')[0]; - var el = document.createElement('span'); - el.innerHTML = 'hello world'; - body.appendChild(el); - var w1 = el.scrollWidth; - el.style.fontWeight = 'bold'; - var w2 = el.scrollWidth; - body.removeChild(el); - return w1 !== w2; -} - function indexOf(obj, el) { var i = obj.length; while (i--) { From ca4e99e810cfa930d5c1402b1f262c02e768eb88 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 1 Feb 2017 10:21:43 -0800 Subject: [PATCH 3/7] Add license --- src/Renderer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Renderer.ts b/src/Renderer.ts index e43c77a2..15f36e69 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -1,3 +1,7 @@ +/** + * @license MIT + */ + import { ITerminal } from './Interfaces'; /** From 9d8f6c9ae7de96439933fa0d5af45597191090ea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 1 Feb 2017 10:30:58 -0800 Subject: [PATCH 4/7] Polish --- src/Renderer.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Renderer.ts b/src/Renderer.ts index 15f36e69..d99f9be4 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -36,10 +36,11 @@ export class Renderer { // the character width of monospace fonts. if (brokenBold === null) { brokenBold = checkBoldBroken((this._terminal).document); - console.log('check brokenBold: ' + brokenBold); } - // TODO: Pull all DOM interactions into Renderer.constructor + // TODO: Pull more DOM interactions into Renderer.constructor, element for + // example should be owned by Renderer (and also exposed by Terminal due to + // to established public API). } /** From 3c635f3ce52b28f62a074ed29787f670821ca8c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 1 Feb 2017 11:13:00 -0800 Subject: [PATCH 5/7] Remove Terminal.queueRefresh, call refresh directly --- src/InputHandler.ts | 2 +- src/test/escape-sequences-test.js | 2 +- src/test/test.js | 4 ---- src/xterm.js | 26 ++++++++------------------ 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 55fb13ca..10e72e9d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1125,7 +1125,7 @@ export class InputHandler implements IInputHandler { // this.x = this.savedX; // this.y = this.savedY; // } - this._terminal.queueRefresh(0, this._terminal.rows - 1); + this._terminal.refresh(0, this._terminal.rows - 1); this._terminal.viewport.syncScrollArea(); this._terminal.showCursor(); } diff --git a/src/test/escape-sequences-test.js b/src/test/escape-sequences-test.js index 684de079..f35bc00a 100644 --- a/src/test/escape-sequences-test.js +++ b/src/test/escape-sequences-test.js @@ -75,7 +75,7 @@ describe('xterm output comparison', function() { beforeEach(function () { xterm = new Terminal(COLS, ROWS); - xterm.queueRefresh = function() {}; + xterm.refresh = function() {}; xterm.viewport = { syncScrollArea: function() {} }; diff --git a/src/test/test.js b/src/test/test.js index 890b9ad1..d0d70686 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -14,10 +14,6 @@ describe('xterm.js', function() { xterm.compositionHelper = { keydown: function(){ return true; } }; - // Force synchronous refreshes - xterm.queueRefresh = function(start, end) { - xterm.refresh(start, end); - }; // Force synchronous writes xterm.write = function(data) { xterm.writeBuffer.push(data); diff --git a/src/xterm.js b/src/xterm.js index d43ce91e..0229df90 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -461,7 +461,7 @@ Terminal.prototype.blur = function() { */ Terminal.bindBlur = function (term) { on(term.textarea, 'blur', function (ev) { - term.queueRefresh(term.y, term.y); + term.refresh(term.y, term.y); if (term.sendFocus) { term.send(C0.ESC + '[O'); } @@ -650,7 +650,7 @@ Terminal.prototype.open = function(parent) { this.renderer = new Renderer(this); // Setup loop that draws to screen - this.queueRefresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); // Initialize global actions that // need to be taken on the document. @@ -1057,16 +1057,6 @@ Terminal.prototype.destroy = function() { * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) */ Terminal.prototype.refresh = function(start, end) { - this.queueRefresh(start, end); -}; - -/** - * Tells the renderer to refresh terminal content between two rows (inclusive) at the next - * opportunity. - * @param {number} start The row to start from (between 0 and terminal's height terminal - 1) - * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) - */ -Terminal.prototype.queueRefresh = function(start, end) { if (this.renderer) { this.renderer.queueRefresh(start, end); } @@ -1078,7 +1068,7 @@ Terminal.prototype.queueRefresh = function(start, end) { Terminal.prototype.showCursor = function() { if (!this.cursorState) { this.cursorState = 1; - this.queueRefresh(this.y, this.y); + this.refresh(this.y, this.y); } }; @@ -1167,7 +1157,7 @@ Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) { this.emit('scroll', this.ydisp); } - this.queueRefresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); }; /** @@ -1239,7 +1229,7 @@ Terminal.prototype.innerWrite = function() { this.parser.parse(data); this.updateRange(this.y); - this.queueRefresh(this.refreshStart, this.refreshEnd); + this.refresh(this.refreshStart, this.refreshEnd); } if (this.writeBuffer.length > 0) { // Allow renderer to catch up before processing the next batch @@ -1824,7 +1814,7 @@ Terminal.prototype.resize = function(x, y) { this.charMeasure.measure(); - this.queueRefresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); this.normal = null; @@ -1956,7 +1946,7 @@ Terminal.prototype.clear = function() { for (var i = 1; i < this.rows; i++) { this.lines.push(this.blankLine()); } - this.queueRefresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); this.emit('scroll', this.ydisp); }; @@ -2094,7 +2084,7 @@ Terminal.prototype.reset = function() { var customKeydownHandler = this.customKeydownHandler; Terminal.call(this, this.options); this.customKeydownHandler = customKeydownHandler; - this.queueRefresh(0, this.rows - 1); + this.refresh(0, this.rows - 1); this.viewport.syncScrollArea(); }; From 20f22fb658b0ddaf5a0dbf05b6517a51bf3f3136 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 1 Feb 2017 11:18:44 -0800 Subject: [PATCH 6/7] Convert flags to enum --- src/Renderer.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Renderer.ts b/src/Renderer.ts index d99f9be4..9b6234ca 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -11,16 +11,15 @@ import { ITerminal } from './Interfaces'; */ const MAX_REFRESH_FRAME_SKIP = 5; -// TODO: Convert flags to number enum /** - * Flags used to render terminal text properly + * Flags used to render terminal text properly. */ -const FLAGS = { - BOLD: 1, - UNDERLINE: 2, - BLINK: 4, - INVERSE: 8, - INVISIBLE: 16 +enum FLAGS { + BOLD = 1, + UNDERLINE = 2, + BLINK = 4, + INVERSE = 8, + INVISIBLE = 16 }; let brokenBold: boolean = null; @@ -118,9 +117,6 @@ export class Renderer { * @param {number} end The row to end at (between fromRow and terminal's height terminal - 1) */ private _refresh(start: number, end: number): void { - // TODO: Use fat arrow functions for callbacks to avoid `self` - let self = this; - let x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement; // If this is a big refresh, remove the terminal rows from the DOM for faster calculations From cc78fdd63c6a3b33247d83d35c83efa0f8f4acd1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 1 Feb 2017 11:19:54 -0800 Subject: [PATCH 7/7] Hold on to renderer if a reset occurs --- src/xterm.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xterm.js b/src/xterm.js index 0229df90..6204d135 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -208,7 +208,8 @@ function Terminal(options) { this.inputHandler = new InputHandler(this); this.parser = new Parser(this.inputHandler, this); - this.renderer = null; + // Reuse renderer if the Terminal is being recreated via a Terminal.reset call. + this.renderer = this.renderer || null; // user input states this.writeBuffer = [];