From fd34caee2322af515fc1e94785d3a41dbc7e85da Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Fri, 7 Dec 2018 22:50:59 -0500 Subject: [PATCH 1/7] Use a time-based limit to Terminal._innerWrite The idea is that it should run for a bit and then let the renderer draw a frame so that the terminal look responsive. The existing approach limits the work done using a fixed number elements from the write buffer so the duration of a frame can vary widely. This approach looks at the clock to determine when to stop, we basically allocate an amount of time each frame to write, while the rest can be used for rendering. From my tests this change makes the terminal feel a lot smoother. --- src/Terminal.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 2cfc1ca88..a641a86b0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -64,10 +64,12 @@ const document = (typeof window !== 'undefined') ? window.document : null; const WRITE_BUFFER_PAUSE_THRESHOLD = 5; /** - * The number of writes to perform in a single batch before allowing the - * renderer to catch up with a 0ms setTimeout. + * The max number of ms to spend on writes before allowing the renderer to + * catch up with a 0ms setTimeout. A value of < 33 to keep us close to + * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS + * depends on the time it takes for the renderer to draw the frame. */ -const WRITE_BATCH_SIZE = 300; +const WRITE_TIMEOUT_MS = 12; /** * The set of options that only have an effect when set in the Terminal constructor. @@ -1358,13 +1360,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.writeBuffer = []; } - const writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE); - while (writeBatch.length > 0) { - const data = writeBatch.shift(); + const time = Date.now(); + while (this.writeBuffer.length > 0) { + const data = this.writeBuffer.shift(); // If XOFF was sent in order to catch up with the pty process, resume it if // the writeBuffer is empty to allow more data to come in. - if (this._xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) { + if (this._xoffSentToCatchUp && this.writeBuffer.length === 0 && this.writeBuffer.length === 0) { this.handler(C0.DC1); this._xoffSentToCatchUp = false; } @@ -1382,6 +1384,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.updateRange(this.buffer.y); this.refresh(this._refreshStart, this._refreshEnd); + + if (Date.now() - time >= WRITE_TIMEOUT_MS) { + break; + } } if (this.writeBuffer.length > 0) { // Allow renderer to catch up before processing the next batch From 3d2ae2b01edd34e9475c7d58884a501c2426237f Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Sun, 9 Dec 2018 17:58:44 -0500 Subject: [PATCH 2/7] Removing redundant condition. Clearer variable name --- src/Terminal.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index a641a86b0..2c7f648bb 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1360,13 +1360,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.writeBuffer = []; } - const time = Date.now(); + const startTime = Date.now(); while (this.writeBuffer.length > 0) { const data = this.writeBuffer.shift(); // If XOFF was sent in order to catch up with the pty process, resume it if // the writeBuffer is empty to allow more data to come in. - if (this._xoffSentToCatchUp && this.writeBuffer.length === 0 && this.writeBuffer.length === 0) { + if (this._xoffSentToCatchUp && this.writeBuffer.length === 0) { this.handler(C0.DC1); this._xoffSentToCatchUp = false; } @@ -1385,7 +1385,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.updateRange(this.buffer.y); this.refresh(this._refreshStart, this._refreshEnd); - if (Date.now() - time >= WRITE_TIMEOUT_MS) { + if (Date.now() - startTime >= WRITE_TIMEOUT_MS) { break; } } From 92be01dd8188b9b2eb730048526f51443b4fac6c Mon Sep 17 00:00:00 2001 From: Ahtsham Raziq Date: Sat, 9 Feb 2019 23:24:32 +0500 Subject: [PATCH 3/7] Compose file: fix variable substitution --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8e6a2f46b..6eefed896 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: volumes: - ./:/usr/src/app ports: - - ${XTERMJS_PORT:3000}:3000 + - ${XTERMJS_PORT:-3000}:3000 command: ["npm", "start"] watch: From 817401bbcd08c45ffa8169341213d49b7de81823 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 11 Feb 2019 05:32:47 -0800 Subject: [PATCH 4/7] Align y draw coord with how cache draws it Fixes #1937 --- src/renderer/BaseRenderLayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/BaseRenderLayer.ts b/src/renderer/BaseRenderLayer.ts index 3e0b86430..a609d79ce 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/renderer/BaseRenderLayer.ts @@ -241,7 +241,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillText( charData[CHAR_DATA_CHAR_INDEX], x * this._scaledCellWidth + this._scaledCharLeft, - (y + 0.5) * this._scaledCellHeight + this._scaledCharTop); + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); } /** @@ -316,7 +316,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillText( chars, x * this._scaledCellWidth + this._scaledCharLeft, - (y + 0.5) * this._scaledCellHeight + this._scaledCharTop); + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); this._ctx.restore(); } From 3285374618a2ea112e5124c3b551ae0ac0761035 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 20 Feb 2019 07:03:40 -0800 Subject: [PATCH 5/7] Disable reflow when winptyCompat is on Fixes #1943 --- src/Buffer.ts | 6 +++++- src/addons/winptyCompat/winptyCompat.ts | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Buffer.ts b/src/Buffer.ts index 7f4b6071c..bf0bfe170 100644 --- a/src/Buffer.ts +++ b/src/Buffer.ts @@ -211,7 +211,7 @@ export class Buffer implements IBuffer { this.scrollBottom = newRows - 1; - if (this._hasScrollback) { + if (this._isReflowEnabled) { this._reflow(newCols, newRows); // Trim the end of the line off if cols shrunk @@ -226,6 +226,10 @@ export class Buffer implements IBuffer { this._rows = newRows; } + private get _isReflowEnabled(): boolean { + return this._hasScrollback && !(this._terminal as any).isWinptyCompatEnabled; + } + private _reflow(newCols: number, newRows: number): void { if (this._cols === newCols) { return; diff --git a/src/addons/winptyCompat/winptyCompat.ts b/src/addons/winptyCompat/winptyCompat.ts index aec580edb..d162f4e92 100644 --- a/src/addons/winptyCompat/winptyCompat.ts +++ b/src/addons/winptyCompat/winptyCompat.ts @@ -19,6 +19,8 @@ export function winptyCompatInit(terminal: Terminal): void { return; } + (addonTerminal._core as any).isWinptyCompatEnabled = true; + // Winpty does not support wraparound mode which means that lines will never // be marked as wrapped. This causes issues for things like copying a line // retaining the wrapped new line characters or if consumers are listening From 07430a4892365e65946c05b34d51ba668fbc2b9f Mon Sep 17 00:00:00 2001 From: Jesse Stolwijk Date: Tue, 5 Mar 2019 00:00:54 +0100 Subject: [PATCH 6/7] Replace array shift with offset (#1955) --- src/Terminal.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 4d480eb6e..19b5f1256 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1350,19 +1350,20 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } } - protected _innerWrite(): void { + protected _innerWrite(bufferOffset: number = 0): void { // Ensure the terminal isn't disposed if (this._isDisposed) { this.writeBuffer = []; } const startTime = Date.now(); - while (this.writeBuffer.length > 0) { - const data = this.writeBuffer.shift(); + while (this.writeBuffer.length > bufferOffset) { + const data = this.writeBuffer[bufferOffset]; + bufferOffset++; // If XOFF was sent in order to catch up with the pty process, resume it if - // the writeBuffer is empty to allow more data to come in. - if (this._xoffSentToCatchUp && this.writeBuffer.length === 0) { + // we reached the end of the writeBuffer to allow more data to come in. + if (this._xoffSentToCatchUp && this.writeBuffer.length === bufferOffset) { this.handler(C0.DC1); this._xoffSentToCatchUp = false; } @@ -1385,11 +1386,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II break; } } - if (this.writeBuffer.length > 0) { + if (this.writeBuffer.length > bufferOffset) { // Allow renderer to catch up before processing the next batch - setTimeout(() => this._innerWrite(), 0); + setTimeout(() => this._innerWrite(bufferOffset), 0); } else { this._writeInProgress = false; + this.writeBuffer = []; } } From bc41cc7d279e7edd2b7f50b8032252efc4d88f31 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Thu, 7 Mar 2019 00:15:03 +0000 Subject: [PATCH 7/7] Fix #1908 --- src/ui/MouseZoneManager.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/ui/MouseZoneManager.ts b/src/ui/MouseZoneManager.ts index a232f5b94..790227239 100644 --- a/src/ui/MouseZoneManager.ts +++ b/src/ui/MouseZoneManager.ts @@ -23,6 +23,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { private _areZonesActive: boolean = false; private _mouseMoveListener: (e: MouseEvent) => any; + private _mouseLeaveListener: (e: MouseEvent) => any; private _clickListener: (e: MouseEvent) => any; private _tooltipTimeout: number = null; @@ -38,6 +39,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { // These events are expensive, only listen to it when mouse zones are active this._mouseMoveListener = e => this._onMouseMove(e); + this._mouseLeaveListener = e => this._onMouseLeave(e); this._clickListener = e => this._onClick(e); } @@ -89,6 +91,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { if (!this._areZonesActive) { this._areZonesActive = true; this._terminal.element.addEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.addEventListener('mouseleave', this._mouseLeaveListener); this._terminal.element.addEventListener('click', this._clickListener); } } @@ -97,6 +100,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { if (this._areZonesActive) { this._areZonesActive = false; this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener); + this._terminal.element.removeEventListener('mouseleave', this._mouseLeaveListener); this._terminal.element.removeEventListener('click', this._clickListener); } } @@ -169,6 +173,18 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager { } } + private _onMouseLeave(e: MouseEvent): void { + // Fire the hover end callback and cancel any existing timer if the mouse + // leaves the terminal element + if (this._currentZone) { + this._currentZone.leaveCallback(); + this._currentZone = null; + if (this._tooltipTimeout) { + clearTimeout(this._tooltipTimeout); + } + } + } + private _onClick(e: MouseEvent): void { // Find the active zone and click it if found const zone = this._findZoneEventAt(e);