From 25eb7d647267c4b63343a6e0af3248a59fa43a73 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 15 Sep 2022 17:19:34 +0300 Subject: [PATCH 01/56] =?UTF-8?q?chore:=20lint=20using=20=F0=9F=90=8APutou?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/browser/renderer/dom/DomRenderer.ts | 4 +-- src/common/Color.ts | 2 +- src/common/services/InstantiationService.ts | 4 ++- src/common/services/OptionsService.test.ts | 2 +- test/api/MouseTracking.api.ts | 36 ++++++++++----------- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 8df6b3028..9dd5154c9 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -231,11 +231,11 @@ export class DomRenderer extends Disposable implements IRenderer { ` background-color: ${this._colors.selectionInactiveBackgroundOpaque.css};` + `}`; // Colors - this._colors.ansi.forEach((c, i) => { + for (const [i, c] of this._colors.ansi.entries()) { styles += `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; - }); + } styles += `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`; diff --git a/src/common/Color.ts b/src/common/Color.ts index a66e39c1b..b7ff177db 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -99,7 +99,7 @@ export namespace color { */ export namespace css { export function toColor(css: string): IColor { - if (css.match(/#[0-9a-f]{3,8}/i)) { + if (css.match(/#[\da-f]{3,8}/i)) { switch (css.length) { case 4: { // #rgb const r = parseInt(css.slice(1, 2).repeat(2), 16); diff --git a/src/common/services/InstantiationService.ts b/src/common/services/InstantiationService.ts index 8280948ac..cf1256c1d 100644 --- a/src/common/services/InstantiationService.ts +++ b/src/common/services/InstantiationService.ts @@ -29,7 +29,9 @@ export class ServiceCollection { } public forEach(callback: (id: IServiceIdentifier, instance: any) => any): void { - this._entries.forEach((value, key) => callback(key, value)); + for (const [key, value] of this._entries.entries()) { + callback(key, value); + } } public has(id: IServiceIdentifier): boolean { diff --git a/src/common/services/OptionsService.test.ts b/src/common/services/OptionsService.test.ts index a65cb986b..3ff22ac09 100644 --- a/src/common/services/OptionsService.test.ts +++ b/src/common/services/OptionsService.test.ts @@ -59,7 +59,7 @@ describe('OptionsService', () => { }); it('normalizes invalid fontWeight option values', () => { service.options.fontWeight = 350; - assert.doesNotThrow(() => service.options.fontWeight = 10000), 'fontWeight should be normalized instead of throwing'; + assert.doesNotThrow(() => service.options.fontWeight = 10000, 'fontWeight should be normalized instead of throwing'); assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Values greater than 1000 should be reset to default'); service.options.fontWeight = 350; diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index d240738f1..1b00fee5f 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -572,7 +572,7 @@ describe('Mouse Tracking Tests', async () => { await page.keyboard.up('Control'); await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: false } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: false } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } } ]); @@ -587,7 +587,7 @@ describe('Mouse Tracking Tests', async () => { await page.keyboard.up('Alt'); await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: false, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: false, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } } ]); @@ -630,7 +630,7 @@ describe('Mouse Tracking Tests', async () => { // await page.keyboard.up('Shift'); await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } } ]); }); @@ -722,7 +722,7 @@ describe('Mouse Tracking Tests', async () => { await page.keyboard.up('Control'); await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } } ]); @@ -737,7 +737,7 @@ describe('Mouse Tracking Tests', async () => { await page.keyboard.up('Alt'); await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } } ]); @@ -779,7 +779,7 @@ describe('Mouse Tracking Tests', async () => { // await page.keyboard.up('Shift'); await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } } ]); }); @@ -884,7 +884,7 @@ describe('Mouse Tracking Tests', async () => { await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: false } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: false } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: false } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } } ]); @@ -900,7 +900,7 @@ describe('Mouse Tracking Tests', async () => { await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: false, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: false, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } } ]); @@ -944,7 +944,7 @@ describe('Mouse Tracking Tests', async () => { await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } } ]); }); @@ -1040,7 +1040,7 @@ describe('Mouse Tracking Tests', async () => { await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: false } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } } ]); @@ -1056,7 +1056,7 @@ describe('Mouse Tracking Tests', async () => { await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } } ]); @@ -1100,7 +1100,7 @@ describe('Mouse Tracking Tests', async () => { await pollFor(page, () => getReports(encoding), [ { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } } ]); }); @@ -1205,7 +1205,7 @@ describe('Mouse Tracking Tests', async () => { { col: 44, row: 25, state: { action: 'move', button: '', modifier: { control: true, shift: false, meta: false } } }, { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: false } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: false } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: false } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } } ]); @@ -1221,7 +1221,7 @@ describe('Mouse Tracking Tests', async () => { { col: 44, row: 25, state: { action: 'move', button: '', modifier: { control: false, shift: false, meta: true } } }, { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: false, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: false, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } } ]); @@ -1266,7 +1266,7 @@ describe('Mouse Tracking Tests', async () => { { col: 44, row: 25, state: { action: 'move', button: '', modifier: { control: true, shift: false, meta: true } } }, { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: '', modifier: { control: true, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } } ]); }); @@ -1366,7 +1366,7 @@ describe('Mouse Tracking Tests', async () => { { col: 44, row: 25, state: { action: 'move', button: '', modifier: { control: true, shift: false, meta: false } } }, { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: false } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: false } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: false } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: false } } } ]); @@ -1382,7 +1382,7 @@ describe('Mouse Tracking Tests', async () => { { col: 44, row: 25, state: { action: 'move', button: '', modifier: { control: false, shift: false, meta: true } } }, { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: false, shift: false, meta: true } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: false, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: false, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: false, shift: false, meta: true } } } ]); @@ -1427,7 +1427,7 @@ describe('Mouse Tracking Tests', async () => { { col: 44, row: 25, state: { action: 'move', button: '', modifier: { control: true, shift: false, meta: true } } }, { col: 44, row: 25, state: { action: 'press', button: 'left', modifier: { control: true, shift: false, meta: true } } }, { col: 45, row: 25, state: { action: 'move', button: 'left', modifier: { control: true, shift: false, meta: true } } }, - { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } }, + { col: 45, row: 25, state: { action: 'release', button: 'left', modifier: { control: true, shift: false, meta: true } } } // { col: 45, row: 25, state: { action: 'down', button: 'wheel', modifier: { control: true, shift: false, meta: true } } } ]); }); From bbff3e8396975108783d203e573701b5ece66a7c Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 15 Sep 2022 22:52:50 +0300 Subject: [PATCH 02/56] Update src/common/services/InstantiationService.ts --- src/common/services/InstantiationService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/services/InstantiationService.ts b/src/common/services/InstantiationService.ts index cf1256c1d..375e442de 100644 --- a/src/common/services/InstantiationService.ts +++ b/src/common/services/InstantiationService.ts @@ -30,7 +30,7 @@ export class ServiceCollection { public forEach(callback: (id: IServiceIdentifier, instance: any) => any): void { for (const [key, value] of this._entries.entries()) { - callback(key, value); + callback(key, value); } } From 1f45d5344c7049d8997a011b5378b73dce0f8d7a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:10:29 -0700 Subject: [PATCH 03/56] Use the browser's IdleDeadline to determine max task time --- src/common/Idle.ts | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/common/Idle.ts b/src/common/Idle.ts index 529d38a4e..2aebc599d 100644 --- a/src/common/Idle.ts +++ b/src/common/Idle.ts @@ -11,16 +11,8 @@ export class IdleTaskQueue { private _tasks: (() => void)[] = []; private _idleCallback?: number; - private _maxTaskDuration: number; private _i = 0; - /** - * @param targetFps The target frame rate. - */ - constructor(targetFps: number = 240) { - this._maxTaskDuration = 1000 / targetFps; - } - /** * Adds a task to the queue which will run in a future idle callback. */ @@ -53,16 +45,22 @@ export class IdleTaskQueue { private _start(): void { if (!this._idleCallback) { - this._idleCallback = requestIdleCallback(() => this._process()); + this._idleCallback = requestIdleCallback(this._process.bind(this)); } } - private _process(): void { - const start = performance.now(); + private _process(deadline: IdleDeadline): void { this._idleCallback = undefined; + let taskDuration = 0; + let longestTask = 0; while (this._i < this._tasks.length) { + taskDuration = performance.now(); this._tasks[this._i++](); - if (performance.now() - start > this._maxTaskDuration) { + taskDuration = performance.now() - taskDuration; + longestTask = Math.max(taskDuration, longestTask); + // Guess the following task will take a similar time to task that just finished, allow + // additional room to try avoid exceeding the deadline + if (longestTask * 1.5 > deadline.timeRemaining()) { this._start(); return; } @@ -71,14 +69,15 @@ export class IdleTaskQueue { } } +/** + * An object that tracks a single debounced task that will run on the next idle frame. When called + * multiple times, only the last set task will run. + */ export class DebouncedIdleTask { private _queue: IdleTaskQueue; - /** - * @param targetFps The target frame rate. - */ - constructor(targetFps: number = 240) { - this._queue = new IdleTaskQueue(targetFps); + constructor() { + this._queue = new IdleTaskQueue(); } public set(task: () => void): void { From ab90dcd0dc2562a6d0952eeca5c5d531bdb9851b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:13:04 -0700 Subject: [PATCH 04/56] Clarify comment --- src/common/Idle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Idle.ts b/src/common/Idle.ts index 2aebc599d..e05ad0687 100644 --- a/src/common/Idle.ts +++ b/src/common/Idle.ts @@ -58,7 +58,7 @@ export class IdleTaskQueue { this._tasks[this._i++](); taskDuration = performance.now() - taskDuration; longestTask = Math.max(taskDuration, longestTask); - // Guess the following task will take a similar time to task that just finished, allow + // Guess the following task will take a similar time to the longest task in this batch, allow // additional room to try avoid exceeding the deadline if (longestTask * 1.5 > deadline.timeRemaining()) { this._start(); From 8f8813a19a52e20d242b5017e109da6805d4d4b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 15:16:02 -0700 Subject: [PATCH 05/56] Add PriorityTaskQueue --- src/common/{Idle.ts => TaskQueue.ts} | 65 +++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 7 deletions(-) rename src/common/{Idle.ts => TaskQueue.ts} (54%) diff --git a/src/common/Idle.ts b/src/common/TaskQueue.ts similarity index 54% rename from src/common/Idle.ts rename to src/common/TaskQueue.ts index e05ad0687..49e3f5ec3 100644 --- a/src/common/Idle.ts +++ b/src/common/TaskQueue.ts @@ -3,16 +3,24 @@ * @license MIT */ -/** - * A queue of that runs tasks over several idle callbacks, trying to maintain the specified - * frame rate. The tasks will run in the order they are enqueued, but they will run some time later, - * and care should be taken to ensure they're non-urgent and will not introduce race conditions. - */ -export class IdleTaskQueue { +import { isNode } from 'common/Platform'; + +interface ITaskQueue { +} + +interface ITaskDeadline { + timeRemaining(): number; +} +type CallbackWithDeadline = (deadline: ITaskDeadline) => void; + +abstract class TaskQueue implements ITaskQueue { private _tasks: (() => void)[] = []; private _idleCallback?: number; private _i = 0; + protected abstract _requestCallback(callback: CallbackWithDeadline): number; + protected abstract _cancelCallback(identifier: number): void; + /** * Adds a task to the queue which will run in a future idle callback. */ @@ -69,12 +77,55 @@ export class IdleTaskQueue { } } +/** + * A queue of that runs tasks over several tasks via setTimeout, trying to maintain above 60 frames + * per second. The tasks will run in the order they are enqueued, but they will run some time later, + * and care should be taken to ensure they're non-urgent and will not introduce race conditions. + */ +export class PriorityTaskQueue extends TaskQueue { + protected _requestCallback(callback: CallbackWithDeadline): number { + return setTimeout(() => callback(this._createDeadline(16))); + } + + protected _cancelCallback(identifier: number): void { + clearTimeout(identifier); + } + + private _createDeadline(duration: number): ITaskDeadline { + const end = performance.now() + duration; + return { + timeRemaining: () => Math.max(0, end - performance.now()) + }; + } +} + +class IdleTaskQueueInternal extends TaskQueue { + protected _requestCallback(callback: IdleRequestCallback): number { + return requestIdleCallback(callback); + } + + protected _cancelCallback(identifier: number): void { + cancelIdleCallback(identifier); + } +} + +/** + * A queue of that runs tasks over several idle callbacks, trying to respect the idle callback's + * deadline given by the environment. The tasks will run in the order they are enqueued, but they + * will run some time later, and care should be taken to ensure they're non-urgent and will not + * introduce race conditions. + * + * This reverts to a {@link PriorityTaskQueue} if the environment does not support idle callbacks. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export const IdleTaskQueue = (!isNode && 'requestIdleCallback' in window) ? IdleTaskQueueInternal : PriorityTaskQueue; + /** * An object that tracks a single debounced task that will run on the next idle frame. When called * multiple times, only the last set task will run. */ export class DebouncedIdleTask { - private _queue: IdleTaskQueue; + private _queue: IdleTaskQueueInternal | PriorityTaskQueue; constructor() { this._queue = new IdleTaskQueue(); From 494fe3a95d8bca2afaaf7af8c10485f2ec5f8167 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:13:53 -0700 Subject: [PATCH 06/56] Fill in ITaskQueue --- src/common/TaskQueue.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/common/TaskQueue.ts b/src/common/TaskQueue.ts index 49e3f5ec3..27e3a492d 100644 --- a/src/common/TaskQueue.ts +++ b/src/common/TaskQueue.ts @@ -6,6 +6,20 @@ import { isNode } from 'common/Platform'; interface ITaskQueue { + /** + * Adds a task to the queue which will run in a future idle callback. + */ + enqueue(task: () => void): void; + + /** + * Flushes the queue, running all remaining tasks synchronously. + */ + flush(): void; + + /** + * Clears any remaining tasks from the queue, these will not be run. + */ + clear(): void; } interface ITaskDeadline { @@ -21,17 +35,11 @@ abstract class TaskQueue implements ITaskQueue { protected abstract _requestCallback(callback: CallbackWithDeadline): number; protected abstract _cancelCallback(identifier: number): void; - /** - * Adds a task to the queue which will run in a future idle callback. - */ public enqueue(task: () => void): void { this._tasks.push(task); this._start(); } - /** - * Flushes the queue, running all remaining tasks synchronously. - */ public flush(): void { while (this._i < this._tasks.length) { this._tasks[this._i++](); @@ -39,9 +47,6 @@ abstract class TaskQueue implements ITaskQueue { this.clear(); } - /** - * Clears any remaining tasks from the queue, these will not be run. - */ public clear(): void { if (this._idleCallback) { cancelIdleCallback(this._idleCallback); From 04d196a32dd36657ad367ed115693b5d5f987981 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:14:38 -0700 Subject: [PATCH 07/56] Use task queue interface --- src/common/TaskQueue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/TaskQueue.ts b/src/common/TaskQueue.ts index 27e3a492d..3b1acfa60 100644 --- a/src/common/TaskQueue.ts +++ b/src/common/TaskQueue.ts @@ -130,7 +130,7 @@ export const IdleTaskQueue = (!isNode && 'requestIdleCallback' in window) ? Idle * multiple times, only the last set task will run. */ export class DebouncedIdleTask { - private _queue: IdleTaskQueueInternal | PriorityTaskQueue; + private _queue: ITaskQueue; constructor() { this._queue = new IdleTaskQueue(); From c548977a3eb5b96f70d440900d66c0d5abf38d80 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:16:14 -0700 Subject: [PATCH 08/56] Fix imports --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 2 +- src/browser/services/RenderService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index dfcede38a..4764ede4e 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -16,7 +16,7 @@ import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; -import { IdleTaskQueue } from 'common/Idle'; +import { IdleTaskQueue } from 'common/TaskQueue'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 3849bdd33..972586098 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { DebouncedIdleTask } from 'common/Idle'; +import { DebouncedIdleTask } from 'common/TaskQueue'; interface ISelectionState { start: [number, number] | undefined; From c6e4788344bf5f83596efed24a9634496d3df6ad Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:23:14 -0700 Subject: [PATCH 09/56] Use microtasks on server This reduces demo input latency on my mac approx 10ms -> 4ms --- demo/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/server.js b/demo/server.js index 8d295942f..8bb684a2d 100644 --- a/demo/server.js +++ b/demo/server.js @@ -86,7 +86,7 @@ function startServer() { return (data) => { s += data; if (!sender) { - sender = setTimeout(() => { + sender = queueMicrotask(() => { socket.send(s); s = ''; sender = null; @@ -103,7 +103,7 @@ function startServer() { buffer.push(data); length += data.length; if (!sender) { - sender = setTimeout(() => { + sender = queueMicrotask(() => { socket.send(Buffer.concat(buffer, length)); buffer = []; sender = null; From 0b8120808c539feee99965f8fbebc58d4a83ffde Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:27:56 -0700 Subject: [PATCH 10/56] Prefer microtask to timeout in write queue This seems to make little difference but it's more correct as we want to write as fast as possible to minimize input latency --- src/common/input/WriteBuffer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 67fd751e9..c9dfd039c 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -105,7 +105,7 @@ export class WriteBuffer { // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; - setTimeout(() => this._innerWrite()); + queueMicrotask(() => this._innerWrite()); } this._pendingData += data.length; @@ -217,7 +217,7 @@ export class WriteBuffer { this._callbacks = this._callbacks.slice(this._bufferOffset); this._bufferOffset = 0; } - setTimeout(() => this._innerWrite()); + queueMicrotask(() => this._innerWrite()); } else { this._writeBuffer.length = 0; this._callbacks.length = 0; From 2b597a82a119982dbffa1eea6c280c8054e30a61 Mon Sep 17 00:00:00 2001 From: Daniel Steinberg Date: Mon, 26 Sep 2022 11:19:44 -0400 Subject: [PATCH 11/56] Move real-world uses section down --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 2c450e424..5c4d2084e 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,26 @@ The full API for xterm.js is contained within the [TypeScript declaration file]( Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs. +## Releases + +Xterm.js follows a monthly release cycle roughly. + +All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones). + +### Beta builds + +Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with: + +```bash +npm install -S xterm@beta +``` + +These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes. + +## Contributing + +You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development. + ## Real-world uses Xterm.js is used in several world-class applications to provide great terminal experiences. @@ -197,26 +217,6 @@ Xterm.js is used in several world-class applications to provide great terminal e Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only. -## Releases - -Xterm.js follows a monthly release cycle roughly. - -All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones). - -### Beta builds - -Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with: - -```bash -npm install -S xterm@beta -``` - -These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes. - -## Contributing - -You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development. - ## License Agreement If you contribute code to this project, you implicitly allow your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. From 25b69e5ed0939a6c7723421f8c3501c3ee4d0c6c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 27 Sep 2022 06:43:07 -0700 Subject: [PATCH 12/56] Fix repetitive microtask problem See #4145 --- src/common/input/WriteBuffer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index c9dfd039c..3c7727bb2 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -217,7 +217,7 @@ export class WriteBuffer { this._callbacks = this._callbacks.slice(this._bufferOffset); this._bufferOffset = 0; } - queueMicrotask(() => this._innerWrite()); + setTimeout(() => this._innerWrite()); } else { this._writeBuffer.length = 0; this._callbacks.length = 0; From d74e43a42a238d613a34f050f4301fc4a164b096 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 27 Sep 2022 06:44:18 -0700 Subject: [PATCH 13/56] Remove queuemicrotask polyfill Support seems good --- src/common/input/WriteBuffer.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index c9dfd039c..c324d4349 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -33,12 +33,6 @@ const WRITE_TIMEOUT_MS = 12; */ const WRITE_BUFFER_LENGTH_THRESHOLD = 50; -// queueMicrotask polyfill for nodejs < v11 -const qmt: (cb: () => void) => void = (typeof queueMicrotask === 'undefined') - ? (cb: () => void) => { Promise.resolve().then(cb); } - : queueMicrotask; - - export class WriteBuffer { private _writeBuffer: (string | Uint8Array)[] = []; private _callbacks: ((() => void) | undefined)[] = []; @@ -194,7 +188,7 @@ export class WriteBuffer { // 2. spawn a promise immediately resolving to `true` // (executed on the same queue, thus properly aligned before continuation happens) result.catch(err => { - qmt(() => {throw err;}); + queueMicrotask(() => {throw err;}); return Promise.resolve(false); }).then(continuation); return; From 8201925fe886a61626b9201466e87db6e1a9bf83 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 27 Sep 2022 06:46:14 -0700 Subject: [PATCH 14/56] Fix priority task queue and idle callback fallback Mistake of not hooking up the abstract methods, this should throw on Safari for example. --- src/common/TaskQueue.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/TaskQueue.ts b/src/common/TaskQueue.ts index 3b1acfa60..94c5c53b3 100644 --- a/src/common/TaskQueue.ts +++ b/src/common/TaskQueue.ts @@ -49,7 +49,7 @@ abstract class TaskQueue implements ITaskQueue { public clear(): void { if (this._idleCallback) { - cancelIdleCallback(this._idleCallback); + this._cancelCallback(this._idleCallback); this._idleCallback = undefined; } this._i = 0; @@ -58,11 +58,11 @@ abstract class TaskQueue implements ITaskQueue { private _start(): void { if (!this._idleCallback) { - this._idleCallback = requestIdleCallback(this._process.bind(this)); + this._idleCallback = this._requestCallback(this._process.bind(this)); } } - private _process(deadline: IdleDeadline): void { + private _process(deadline: ITaskDeadline): void { this._idleCallback = undefined; let taskDuration = 0; let longestTask = 0; From 23328a394bbb10790014d78f888d96174e1aae98 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:01:36 -0700 Subject: [PATCH 15/56] Fix trusted types in dom renderer Fixes #4156 --- src/browser/renderer/dom/DomRendererRowFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index d3eb9e8e6..b6ee7bf17 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -166,7 +166,7 @@ export class DomRendererRowFactory { if (cell.isUnderline()) { charElement.classList.add(`${UNDERLINE_CLASS}-${cell.extended.underlineStyle}`); if (charElement.textContent === ' ') { - charElement.innerHTML = ' '; + charElement.textContent = '\xa0'; // =   } if (!cell.isUnderlineColorDefault()) { if (cell.isUnderlineColorRGB()) { From 44388e5e6c206c272c9d2370b85235dee6a729bf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Sep 2022 08:55:29 -0700 Subject: [PATCH 16/56] Use microtask to flush the write buffer after input Fixes #4158 --- src/common/CoreTerminal.ts | 1 + src/common/input/WriteBuffer.ts | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index d7cb0f7eb..02a943927 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -132,6 +132,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(forwardEvent(this._bufferService.onResize, this._onResize)); this.register(forwardEvent(this.coreService.onData, this._onData)); this.register(forwardEvent(this.coreService.onBinary, this._onBinary)); + this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput())); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 2cdf4e3c5..1ac98b51e 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -40,11 +40,16 @@ export class WriteBuffer { private _bufferOffset = 0; private _isSyncWriting = false; private _syncCalls = 0; + private _didUserInput = false; public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } private _onWriteParsed = new EventEmitter(); constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } + public handleUserInput(): void { + this._didUserInput = true; + } + /** * @deprecated Unreliable, to be removed soon. */ @@ -99,7 +104,15 @@ export class WriteBuffer { // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; - queueMicrotask(() => this._innerWrite()); + // If this is the first write call after the user has done some input, + // parse it immediately in an upcoming microtask to minimize reduce input, + // otherwise schedule for the next event + if (this._didUserInput) { + this._didUserInput = false; + queueMicrotask(() => this._innerWrite()); + } else { + setTimeout(() => this._innerWrite()); + } } this._pendingData += data.length; From 34df17e36dc309171880bd996b8b37f412bbb4de Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:51:28 -0700 Subject: [PATCH 17/56] Call innerWrite immediately over a microtask --- src/common/input/WriteBuffer.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 1ac98b51e..4f316f24b 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -104,15 +104,20 @@ export class WriteBuffer { // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; + // If this is the first write call after the user has done some input, // parse it immediately in an upcoming microtask to minimize reduce input, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; - queueMicrotask(() => this._innerWrite()); - } else { - setTimeout(() => this._innerWrite()); + this._pendingData += data.length; + this._writeBuffer.push(data); + this._callbacks.push(callback); + this._innerWrite(); + return; } + + setTimeout(() => this._innerWrite()); } this._pendingData += data.length; From cd6f370c2855ff0d4ed99030a5a4494323858cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 28 Sep 2022 20:03:55 +0200 Subject: [PATCH 18/56] unbuffered delivery for userInput response only --- demo/server.js | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/demo/server.js b/demo/server.js index 8bb684a2d..c594da3e6 100644 --- a/demo/server.js +++ b/demo/server.js @@ -79,14 +79,25 @@ function startServer() { console.log('Connected to terminal ' + term.pid); ws.send(logs[term.pid]); + // unbuffered delivery after user input + let userInput = false; + // string message buffering - function buffer(socket, timeout) { + function buffer(socket, timeout, maxSize) { let s = ''; let sender = null; return (data) => { s += data; - if (!sender) { - sender = queueMicrotask(() => { + if (s.length > maxSize || userInput) { + userInput = false; + socket.send(s); + s = ''; + if (sender) { + clearTimeout(sender); + sender = null; + } + } else if (!sender) { + sender = setTimeout(() => { socket.send(s); s = ''; sender = null; @@ -95,15 +106,24 @@ function startServer() { }; } // binary message buffering - function bufferUtf8(socket, timeout) { + function bufferUtf8(socket, timeout, maxSize) { let buffer = []; let sender = null; let length = 0; return (data) => { buffer.push(data); length += data.length; - if (!sender) { - sender = queueMicrotask(() => { + if (length > maxSize || userInput) { + userInput = false; + socket.send(Buffer.concat(buffer, length)); + buffer = []; + length = 0; + if (sender) { + clearTimeout(sender); + sender = null; + } + } else if (!sender) { + sender = setTimeout(() => { socket.send(Buffer.concat(buffer, length)); buffer = []; sender = null; @@ -112,7 +132,7 @@ function startServer() { } }; } - const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5); + const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 2, 262144); // WARNING: This is a naive implementation that will not throttle the flow of data. This means // it could flood the communication channel and make the terminal unresponsive. Learn more about @@ -126,6 +146,7 @@ function startServer() { }); ws.on('message', function(msg) { term.write(msg); + userInput = true; }); ws.on('close', function () { term.kill(); From 193d305dcd37537aa47758e580059ffd220b8723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 29 Sep 2022 10:36:08 +0200 Subject: [PATCH 19/56] revert timeout to 5ms, remove log in server.js --- demo/server.js | 17 +++-------------- src/common/input/WriteBuffer.ts | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/demo/server.js b/demo/server.js index c594da3e6..0e82f9e96 100644 --- a/demo/server.js +++ b/demo/server.js @@ -16,8 +16,7 @@ function startServer() { var app = express(); expressWs(app); - var terminals = {}, - logs = {}; + var terminals = {}; app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.get('/logo.png', (req, res) => { @@ -55,10 +54,6 @@ function startServer() { console.log('Created terminal with PID: ' + term.pid); terminals[term.pid] = term; - logs[term.pid] = ''; - term.on('data', function(data) { - logs[term.pid] += data; - }); res.send(term.pid.toString()); res.end(); }); @@ -77,7 +72,6 @@ function startServer() { app.ws('/terminals/:pid', function (ws, req) { var term = terminals[parseInt(req.params.pid)]; console.log('Connected to terminal ' + term.pid); - ws.send(logs[term.pid]); // unbuffered delivery after user input let userInput = false; @@ -132,17 +126,13 @@ function startServer() { } }; } - const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 2, 262144); + const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 5, 262144); // WARNING: This is a naive implementation that will not throttle the flow of data. This means // it could flood the communication channel and make the terminal unresponsive. Learn more about // the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/ term.on('data', function(data) { - try { - send(data); - } catch (ex) { - // The WebSocket is not open, ignore - } + send(data); }); ws.on('message', function(msg) { term.write(msg); @@ -153,7 +143,6 @@ function startServer() { console.log('Closed terminal ' + term.pid); // Clean things up delete terminals[term.pid]; - delete logs[term.pid]; }); }); diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 4f316f24b..8cb1edf4b 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -106,7 +106,7 @@ export class WriteBuffer { this._bufferOffset = 0; // If this is the first write call after the user has done some input, - // parse it immediately in an upcoming microtask to minimize reduce input, + // parse it immediately to minimize reduce input, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; From 150499b7e5ee56fa4bc0985f0099ce739ff6f802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 29 Sep 2022 10:46:25 +0200 Subject: [PATCH 20/56] comment fix --- src/common/input/WriteBuffer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 8cb1edf4b..5e19105d8 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -106,7 +106,7 @@ export class WriteBuffer { this._bufferOffset = 0; // If this is the first write call after the user has done some input, - // parse it immediately to minimize reduce input, + // parse it immediately to minimize input latency, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; From 52ed65d7bda0c041d9ce29e2b53558d2a3fbf237 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:26:07 -0700 Subject: [PATCH 21/56] Move w objects to $ prefix variables Fixes #4138 --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 58 +++++++------ .../src/RectangleRenderer.ts | 50 ++++++----- addons/xterm-addon-webgl/src/WebglRenderer.ts | 84 +++++++++---------- .../src/atlas/WebglCharAtlas.ts | 16 ++-- src/common/buffer/BufferLine.ts | 14 ++-- src/common/services/DecorationService.ts | 14 ++-- 6 files changed, 112 insertions(+), 124 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index a7f4a700f..689b847a2 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -70,13 +70,11 @@ const INDICES_PER_CELL = 10; const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; const CELL_POSITION_INDICES = 2; -/** Work variables to avoid garbage collection. */ -const w: { i: number, glyph: IRasterizedGlyph | undefined, leftCellPadding: number, clippedPixels: number } = { - i: 0, - glyph: undefined, - leftCellPadding: 0, - clippedPixels: 0 -}; +// Work variables to avoid garbage collection +let $i = 0; +let $glyph: IRasterizedGlyph | undefined = undefined; +let $leftCellPadding = 0; +let $clippedPixels = 0; export class GlyphRenderer extends Disposable { private _atlas: WebglCharAtlas | undefined; @@ -186,12 +184,12 @@ export class GlyphRenderer extends Disposable { } private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { - w.i = (y * this._terminal.cols + x) * INDICES_PER_CELL; + $i = (y * this._terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null character, allow space character to continue as it may have // underline/strikethrough styles if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { - fill(array, 0, w.i, w.i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); + fill(array, 0, $i, $i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -201,39 +199,39 @@ export class GlyphRenderer extends Disposable { // Get the glyph if (chars && chars.length > 1) { - w.glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext); + $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext); } else { - w.glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext); + $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext); } - w.leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2); - if (bg !== lastBg && w.glyph.offset.x > w.leftCellPadding) { - w.clippedPixels = w.glyph.offset.x - w.leftCellPadding; + $leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2); + if (bg !== lastBg && $glyph.offset.x > $leftCellPadding) { + $clippedPixels = $glyph.offset.x - $leftCellPadding; // a_origin - array[w.i ] = -(w.glyph.offset.x - w.clippedPixels) + this._dimensions.scaledCharLeft; - array[w.i + 1] = -w.glyph.offset.y + this._dimensions.scaledCharTop; + array[$i ] = -($glyph.offset.x - $clippedPixels) + this._dimensions.scaledCharLeft; + array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop; // a_size - array[w.i + 2] = (w.glyph.size.x - w.clippedPixels) / this._dimensions.scaledCanvasWidth; - array[w.i + 3] = w.glyph.size.y / this._dimensions.scaledCanvasHeight; + array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.scaledCanvasWidth; + array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight; // a_texcoord - array[w.i + 4] = w.glyph.texturePositionClipSpace.x + w.clippedPixels / this._atlas.cacheCanvas.width; - array[w.i + 5] = w.glyph.texturePositionClipSpace.y; + array[$i + 4] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 5] = $glyph.texturePositionClipSpace.y; // a_texsize - array[w.i + 6] = w.glyph.sizeClipSpace.x - w.clippedPixels / this._atlas.cacheCanvas.width; - array[w.i + 7] = w.glyph.sizeClipSpace.y; + array[$i + 6] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 7] = $glyph.sizeClipSpace.y; } else { // a_origin - array[w.i ] = -w.glyph.offset.x + this._dimensions.scaledCharLeft; - array[w.i + 1] = -w.glyph.offset.y + this._dimensions.scaledCharTop; + array[$i ] = -$glyph.offset.x + this._dimensions.scaledCharLeft; + array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop; // a_size - array[w.i + 2] = w.glyph.size.x / this._dimensions.scaledCanvasWidth; - array[w.i + 3] = w.glyph.size.y / this._dimensions.scaledCanvasHeight; + array[$i + 2] = $glyph.size.x / this._dimensions.scaledCanvasWidth; + array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight; // a_texcoord - array[w.i + 4] = w.glyph.texturePositionClipSpace.x; - array[w.i + 5] = w.glyph.texturePositionClipSpace.y; + array[$i + 4] = $glyph.texturePositionClipSpace.x; + array[$i + 5] = $glyph.texturePositionClipSpace.y; // a_texsize - array[w.i + 6] = w.glyph.sizeClipSpace.x; - array[w.i + 7] = w.glyph.sizeClipSpace.y; + array[$i + 6] = $glyph.sizeClipSpace.x; + array[$i + 7] = $glyph.sizeClipSpace.y; } // a_cellpos only changes on resize } diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ef08fb7c2..dccc7f6a9 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -58,17 +58,15 @@ const BYTES_PER_RECTANGLE = INDICES_PER_RECTANGLE * Float32Array.BYTES_PER_ELEME const INITIAL_BUFFER_RECTANGLE_CAPACITY = 20 * INDICES_PER_RECTANGLE; -/** Work variables to avoid garbage collection. */ -const w: { rgba: number, isDefault: boolean, x1: number, y1: number, r: number, g: number, b: number, a: number } = { - rgba: 0, - isDefault: false, - x1: 0, - y1: 0, - r: 0, - g: 0, - b: 0, - a: 0 -}; +// Work variables to avoid garbage collection +let $rgba = 0; +let $isDefault = false; +let $x1 = 0; +let $y1 = 0; +let $r = 0; +let $g = 0; +let $b = 0; +let $a = 0; export class RectangleRenderer extends Disposable { @@ -232,47 +230,47 @@ export class RectangleRenderer extends Disposable { } private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { - w.isDefault = false; + $isDefault = false; if (fg & FgFlags.INVERSE) { switch (fg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: - w.rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; + $rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - w.rgba = (fg & Attributes.RGB_MASK) << 8; + $rgba = (fg & Attributes.RGB_MASK) << 8; break; case Attributes.CM_DEFAULT: default: - w.rgba = this._colors.foreground.rgba; + $rgba = this._colors.foreground.rgba; } } else { switch (bg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: - w.rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; + $rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - w.rgba = (bg & Attributes.RGB_MASK) << 8; + $rgba = (bg & Attributes.RGB_MASK) << 8; break; case Attributes.CM_DEFAULT: default: - w.rgba = this._colors.background.rgba; - w.isDefault = true; + $rgba = this._colors.background.rgba; + $isDefault = true; } } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); } - w.x1 = startX * this._dimensions.scaledCellWidth; - w.y1 = y * this._dimensions.scaledCellHeight; - w.r = ((w.rgba >> 24) & 0xFF) / 255; - w.g = ((w.rgba >> 16) & 0xFF) / 255; - w.b = ((w.rgba >> 8 ) & 0xFF) / 255; - w.a = (!w.isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1; + $x1 = startX * this._dimensions.scaledCellWidth; + $y1 = y * this._dimensions.scaledCellHeight; + $r = (($rgba >> 24) & 0xFF) / 255; + $g = (($rgba >> 16) & 0xFF) / 255; + $b = (($rgba >> 8 ) & 0xFF) / 255; + $a = (!$isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1; - this._addRectangle(vertices.attributes, offset, w.x1, w.y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, w.r, w.g, w.b, w.a); + this._addRectangle(vertices.attributes, offset, $x1, $y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, $r, $g, $b, $a); } private _addRectangle(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, r: number, g: number, b: number, a: number): void { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d8daa0d18..b276cd053 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -26,14 +26,12 @@ import { CharData, IBufferLine, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { ICoreService, IDecorationService } from 'common/services/Services'; -/** Work variables to avoid garbage collection. */ -const w: { fg: number, bg: number, hasFg: boolean, hasBg: boolean, isSelected: boolean } = { - fg: 0, - bg: 0, - hasFg: false, - hasBg: false, - isSelected: false -}; +// Work variables to avoid garbage collection +let $fg = 0; +let $bg = 0; +let $hasFg = false; +let $hasBg = false; +let $isSelected = false; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -475,89 +473,89 @@ export class WebglRenderer extends Disposable implements IRenderer { // override logic throughout the different sub-renderers // Reset overrides work variables - w.bg = 0; - w.fg = 0; - w.hasBg = false; - w.hasFg = false; - w.isSelected = false; + $bg = 0; + $fg = 0; + $hasBg = false; + $hasFg = false; + $isSelected = false; // Apply decorations on the bottom layer this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { if (d.backgroundColorRGB) { - w.bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; } if (d.foregroundColorRGB) { - w.fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } }); // Apply the selection color if needed - w.isSelected = this._isCellSelected(x, y); - if (w.isSelected) { - w.bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $isSelected = this._isCellSelected(x, y); + if ($isSelected) { + $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + $hasBg = true; if (this._colors.selectionForeground) { - w.fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } } // Apply decorations on the top layer this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { if (d.backgroundColorRGB) { - w.bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; } if (d.foregroundColorRGB) { - w.fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } }); // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag // ahead of time in order to use the correct cache key - if (w.hasBg) { - if (w.isSelected) { + if ($hasBg) { + if ($isSelected) { // Non-RGB attributes from model + force non-dim + override + force RGB color mode - w.bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | w.bg | Attributes.CM_RGB; + $bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; } else { // Non-RGB attributes from model + override + force RGB color mode - w.bg = (this._workCell.bg & ~Attributes.RGB_MASK) | w.bg | Attributes.CM_RGB; + $bg = (this._workCell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; } } - if (w.hasFg) { + if ($hasFg) { // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - w.fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | w.fg | Attributes.CM_RGB; + $fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; } // Handle case where inverse was specified by only one of bg override or fg override was set, // resolving the other inverse color and setting the inverse flag if needed. if (this._workColors.fg & FgFlags.INVERSE) { - if (w.hasBg && !w.hasFg) { + if ($hasBg && !$hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - w.fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { - w.fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); + $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); } - w.hasFg = true; + $hasFg = true; } - if (!w.hasBg && w.hasFg) { + if (!$hasBg && $hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - w.bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { - w.bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); + $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); } - w.hasBg = true; + $hasBg = true; } } // Use the override if it exists - this._workColors.bg = w.hasBg ? w.bg : this._workColors.bg; - this._workColors.fg = w.hasFg ? w.fg : this._workColors.fg; + this._workColors.bg = $hasBg ? $bg : this._workColors.bg; + this._workColors.fg = $hasFg ? $fg : this._workColors.fg; } private _isCellSelected(x: number, y: number): boolean { diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 4764ede4e..dee1e02c5 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -54,10 +54,8 @@ interface ICharAtlasActiveRow { height: number; } -/** Work variables to avoid garbage collection. */ -const w: { glyph: IRasterizedGlyph | undefined } = { - glyph: undefined -}; +// Work variables to avoid garbage collection +let $glyph = undefined; export class WebglCharAtlas implements IDisposable { private _didWarmUp: boolean = false; @@ -184,12 +182,12 @@ export class WebglCharAtlas implements IDisposable { fg: number, ext: number ): IRasterizedGlyph { - w.glyph = cacheMap.get(key, bg, fg, ext); - if (!w.glyph) { - w.glyph = this._drawToCache(key, bg, fg, ext); - cacheMap.set(key, bg, fg, ext, w.glyph); + $glyph = cacheMap.get(key, bg, fg, ext); + if (!$glyph) { + $glyph = this._drawToCache(key, bg, fg, ext); + cacheMap.set(key, bg, fg, ext, $glyph); } - return w.glyph; + return $glyph; } private _getColorFromAnsiIndex(idx: number): IColor { diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 43e898392..875ac6c97 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -37,10 +37,8 @@ const enum Cell { export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData()); -/** Work variables to avoid garbage collection. */ -const w: { startIndex: number } = { - startIndex: 0 -}; +// Work variables to avoid garbage collection +let $startIndex = 0; /** * Typed array based bufferline implementation. @@ -178,10 +176,10 @@ export class BufferLine implements IBufferLine { * to GC as it significantly reduced the amount of new objects/references needed. */ public loadCell(index: number, cell: ICellData): ICellData { - w.startIndex = index * CELL_SIZE; - cell.content = this._data[w.startIndex + Cell.CONTENT]; - cell.fg = this._data[w.startIndex + Cell.FG]; - cell.bg = this._data[w.startIndex + Cell.BG]; + $startIndex = index * CELL_SIZE; + cell.content = this._data[$startIndex + Cell.CONTENT]; + cell.fg = this._data[$startIndex + Cell.FG]; + cell.bg = this._data[$startIndex + Cell.BG]; if (cell.content & Content.IS_COMBINED_MASK) { cell.combinedData = this._combined[index]; } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index e5d115a14..dae801518 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -11,11 +11,9 @@ import { SortedList } from 'common/SortedList'; import { IColor } from 'common/Types'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; -/** Work variables to avoid garbage collection. */ -const w = { - xmin: 0, - xmax: 0 -}; +// Work variables to avoid garbage collection +let $xmin = 0; +let $xmax = 0; export class DecorationService extends Disposable implements IDecorationService { public serviceBrand: any; @@ -76,9 +74,9 @@ export class DecorationService extends Disposable implements IDecorationService public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { this._decorations.forEachByKey(line, d => { - w.xmin = d.options.x ?? 0; - w.xmax = w.xmin + (d.options.width ?? 1); - if (x >= w.xmin && x < w.xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { + $xmin = d.options.x ?? 0; + $xmax = $xmin + (d.options.width ?? 1); + if (x >= $xmin && x < $xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { callback(d); } }); From 3a90f7e7c0a508aeed422173df89d6b9366be341 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:39:02 -0700 Subject: [PATCH 22/56] Inline dirty row service into input handler Fixes #4147 --- src/common/CoreTerminal.ts | 12 +-- src/common/InputHandler.test.ts | 18 ++-- src/common/InputHandler.ts | 123 ++++++++++++++++++------- src/common/TestUtils.test.ts | 12 +-- src/common/services/DirtyRowService.ts | 53 ----------- src/common/services/Services.ts | 13 --- 6 files changed, 102 insertions(+), 129 deletions(-) delete mode 100644 src/common/services/DirtyRowService.ts diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 02a943927..fae3efa58 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -22,7 +22,7 @@ */ import { Disposable } from 'common/Lifecycle'; -import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; @@ -31,7 +31,6 @@ import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent, ScrollSource import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; -import { DirtyRowService } from 'common/services/DirtyRowService'; import { UnicodeService } from 'common/services/UnicodeService'; import { CharsetService } from 'common/services/CharsetService'; import { updateWindowsModeWrappedState } from 'common/WindowsMode'; @@ -49,7 +48,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected readonly _bufferService: IBufferService; protected readonly _logService: ILogService; protected readonly _charsetService: ICharsetService; - protected readonly _dirtyRowService: IDirtyRowService; protected readonly _oscLinkService: IOscLinkService; public readonly coreMouseService: ICoreMouseService; @@ -114,8 +112,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._instantiationService.setService(ICoreService, this.coreService); this.coreMouseService = this._instantiationService.createInstance(CoreMouseService); this._instantiationService.setService(ICoreMouseService, this.coreMouseService); - this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); - this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); this.unicodeService = this._instantiationService.createInstance(UnicodeService); this._instantiationService.setService(IUnicodeService, this.unicodeService); this._charsetService = this._instantiationService.createInstance(CharsetService); @@ -124,7 +120,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._instantiationService.setService(IOscLinkService, this._oscLinkService); // Register input handler and handle/forward events - this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._dirtyRowService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); this.register(this._inputHandler); @@ -136,11 +132,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); - this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); })); this.register(this._inputHandler.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); - this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); })); // Setup WriteBuffer diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index f734b002c..d9127e7b2 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -11,7 +11,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test'; +import { MockCoreService, MockBufferService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test'; import { IBufferService, ICoreService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; @@ -67,7 +67,7 @@ describe('InputHandler', () => { bufferService.resize(80, 30); coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService); - inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); describe('SL/SR/DECIC/DECDC', () => { @@ -236,7 +236,7 @@ describe('InputHandler', () => { describe('setMode', () => { it('should toggle bracketedPasteMode', () => { const coreService = new MockCoreService(); - const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(coreService.decPrivateModes.bracketedPasteMode, true); @@ -258,7 +258,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -305,7 +304,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -356,7 +354,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -394,7 +391,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -445,7 +441,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -572,7 +567,6 @@ describe('InputHandler', () => { new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -599,7 +593,7 @@ describe('InputHandler', () => { beforeEach(() => { bufferService = new MockBufferService(80, 30); - handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', async () => { await handler.parseP('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); @@ -796,7 +790,7 @@ describe('InputHandler', () => { describe('colon notation', () => { let inputHandler2: TestInputHandler; beforeEach(() => { - inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); describe('should equal to semicolon', () => { it('CSI 38:2::50:100:150 m', async () => { @@ -2278,7 +2272,7 @@ describe('InputHandler - async handlers', () => { coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService); coreService.onData(data => { console.log(data); }); - inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); it('async CUP with CPR check', async () => { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index b599bb7e4..dd61cf347 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -12,11 +12,11 @@ import { Disposable } from 'common/Lifecycle'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; +import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; @@ -104,6 +104,8 @@ export enum WindowsOptionsReportType { // create a warning log if an async handler takes longer than the limit (in ms) const SLOW_ASYNC_LIMIT = 5000; +// Work variables to avoid garbage collection +let $temp = 0; /** * The terminal's standard implementation of IInputHandler, this handles all @@ -120,6 +122,7 @@ export class InputHandler extends Disposable implements IInputHandler { private _windowTitle = ''; private _iconName = ''; private _currentLinkId?: number; + private _dirtyRowTracker: IDirtyRowTracker; protected _windowTitleStack: string[] = []; protected _iconNameStack: string[] = []; @@ -169,7 +172,6 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _bufferService: IBufferService, private readonly _charsetService: ICharsetService, private readonly _coreService: ICoreService, - private readonly _dirtyRowService: IDirtyRowService, private readonly _logService: ILogService, private readonly _optionsService: IOptionsService, private readonly _oscLinkService: IOscLinkService, @@ -179,6 +181,7 @@ export class InputHandler extends Disposable implements IInputHandler { ) { super(); this.register(this._parser); + this._dirtyRowTracker = new DirtyRowTracker(this._bufferService); // Track properties used in performance critical code manually to avoid using slow getters this._activeBuffer = this._bufferService.buffer; @@ -459,7 +462,7 @@ export class InputHandler extends Disposable implements IInputHandler { // Clear the dirty row service so we know which lines changed as a result of parsing // Important: do not clear between async calls, otherwise we lost pending update information. if (!wasPaused) { - this._dirtyRowService.clearRange(); + this._dirtyRowTracker.clearRange(); } // process big data in smaller chunks @@ -493,7 +496,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // Refresh any dirty rows accumulated as part of parsing - this._onRequestRefreshRows.fire(this._dirtyRowService.start, this._dirtyRowService.end); + this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); } public print(data: Uint32Array, start: number, end: number): void { @@ -507,7 +510,7 @@ export class InputHandler extends Disposable implements IInputHandler { const curAttr = this._curAttrData; let bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!; - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) { @@ -635,7 +638,7 @@ export class InputHandler extends Disposable implements IInputHandler { bufferRow.setCellFromCodePoint(this._activeBuffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended); } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** @@ -699,7 +702,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF." */ public lineFeed(): boolean { - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); if (this._optionsService.rawOptions.convertEol) { this._activeBuffer.x = 0; } @@ -714,7 +717,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._activeBuffer.x >= this._bufferService.cols) { this._activeBuffer.x--; } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); this._onLineFeed.fire(); return true; @@ -842,14 +845,14 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.y = this._coreService.decPrivateModes.origin ? Math.min(this._activeBuffer.scrollBottom, Math.max(this._activeBuffer.scrollTop, this._activeBuffer.y)) : Math.min(this._bufferService.rows - 1, Math.max(0, this._activeBuffer.y)); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** * Set absolute cursor position. */ private _setCursor(x: number, y: number): void { - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); if (this._coreService.decPrivateModes.origin) { this._activeBuffer.x = x; this._activeBuffer.y = this._activeBuffer.scrollTop + y; @@ -858,7 +861,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.y = y; } this._restrictCursor(); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** @@ -1178,16 +1181,16 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 0: j = this._activeBuffer.y; - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect); for (; j < this._bufferService.rows; j++) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); break; case 1: j = this._activeBuffer.y; - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); // Deleted front part of line and everything before. This line will no longer be wrapped. this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true, respectProtect); if (this._activeBuffer.x + 1 >= this._bufferService.cols) { @@ -1197,15 +1200,15 @@ export class InputHandler extends Disposable implements IInputHandler { while (j--) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(0); + this._dirtyRowTracker.markDirty(0); break; case 2: j = this._bufferService.rows; - this._dirtyRowService.markDirty(j - 1); + this._dirtyRowTracker.markDirty(j - 1); while (j--) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(0); + this._dirtyRowTracker.markDirty(0); break; case 3: // Clear scrollback (everything not in viewport) @@ -1257,7 +1260,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true, respectProtect); break; } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); return true; } @@ -1289,7 +1292,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(row, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? return true; } @@ -1323,7 +1326,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(j, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? return true; } @@ -1349,7 +1352,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -1375,7 +1378,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -1395,7 +1398,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 1); this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1411,7 +1414,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 1); this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 0, this._activeBuffer.getBlankLine(DEFAULT_ATTR_DATA)); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1443,7 +1446,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1476,7 +1479,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1499,7 +1502,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1522,7 +1525,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1544,7 +1547,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -3220,7 +3223,7 @@ export class InputHandler extends Disposable implements IInputHandler { const scrollRegionHeight = this._activeBuffer.scrollBottom - this._activeBuffer.scrollTop; this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase + this._activeBuffer.y, scrollRegionHeight, 1); this._activeBuffer.lines.set(this._activeBuffer.ybase + this._activeBuffer.y, this._activeBuffer.getBlankLine(this._eraseAttrData())); - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); } else { this._activeBuffer.y--; this._restrictCursor(); // quickfix to not run out of bounds @@ -3293,7 +3296,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } } - this._dirtyRowService.markAllDirty(); + this._dirtyRowTracker.markAllDirty(); this._setCursor(0, 0); return true; } @@ -3344,4 +3347,60 @@ export class InputHandler extends Disposable implements IInputHandler { if (data === ' q') return f(`P1$r${STYLES[opts.cursorStyle] - (opts.cursorBlink ? 1 : 0)} q`); return f(`P0$r`); } + + public markRangeDirty(y1: number, y2: number): void { + this._dirtyRowTracker.markRangeDirty(y1, y2); + } +} + +export interface IDirtyRowTracker { + readonly start: number; + readonly end: number; + + clearRange(): void; + markDirty(y: number): void; + markRangeDirty(y1: number, y2: number): void; + markAllDirty(): void; +} + +class DirtyRowTracker implements IDirtyRowTracker { + public start!: number; + public end!: number; + + constructor( + @IBufferService private readonly _bufferService: IBufferService + ) { + this.clearRange(); + } + + public clearRange(): void { + this.start = this._bufferService.buffer.y; + this.end = this._bufferService.buffer.y; + } + + public markDirty(y: number): void { + if (y < this.start) { + this.start = y; + } else if (y > this.end) { + this.end = y; + } + } + + public markRangeDirty(y1: number, y2: number): void { + if (y1 > y2) { + $temp = y1; + y1 = y2; + y2 = $temp; + } + if (y1 < this.start) { + this.start = y1; + } + if (y2 > this.end) { + this.end = y2; + } + } + + public markAllDirty(): void { + this.markRangeDirty(0, this._bufferService.rows - 1); + } } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 8fb71a5d5..e302d3e4b 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -100,16 +100,6 @@ export class MockCoreService implements ICoreService { public triggerBinaryEvent(data: string): void { } } -export class MockDirtyRowService implements IDirtyRowService { - public serviceBrand: any; - public start: number = 0; - public end: number = 0; - public clearRange(): void { } - public markDirty(y: number): void { } - public markRangeDirty(y1: number, y2: number): void { } - public markAllDirty(): void { } -} - export class MockLogService implements ILogService { public serviceBrand: any; public logLevel = LogLevelEnum.DEBUG; diff --git a/src/common/services/DirtyRowService.ts b/src/common/services/DirtyRowService.ts deleted file mode 100644 index 1c43b67ef..000000000 --- a/src/common/services/DirtyRowService.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IBufferService, IDirtyRowService } from 'common/services/Services'; - -export class DirtyRowService implements IDirtyRowService { - public serviceBrand: any; - - private _start!: number; - private _end!: number; - - public get start(): number { return this._start; } - public get end(): number { return this._end; } - - constructor( - @IBufferService private readonly _bufferService: IBufferService - ) { - this.clearRange(); - } - - public clearRange(): void { - this._start = this._bufferService.buffer.y; - this._end = this._bufferService.buffer.y; - } - - public markDirty(y: number): void { - if (y < this._start) { - this._start = y; - } else if (y > this._end) { - this._end = y; - } - } - - public markRangeDirty(y1: number, y2: number): void { - if (y1 > y2) { - const temp = y1; - y1 = y2; - y2 = temp; - } - if (y1 < this._start) { - this._start = y1; - } - if (y2 > this._end) { - this._end = y2; - } - } - - public markAllDirty(): void { - this.markRangeDirty(0, this._bufferService.rows - 1); - } -} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 22edad1dd..e2b517cde 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -122,19 +122,6 @@ export interface ICharsetService { setgCharset(g: number, charset: ICharset | undefined): void; } -export const IDirtyRowService = createDecorator('DirtyRowService'); -export interface IDirtyRowService { - serviceBrand: undefined; - - readonly start: number; - readonly end: number; - - clearRange(): void; - markDirty(y: number): void; - markRangeDirty(y1: number, y2: number): void; - markAllDirty(): void; -} - export interface IServiceIdentifier { (...args: any[]): void; type: T; From cf9949840b65059aae90589862809318e1b95959 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:52:26 -0700 Subject: [PATCH 23/56] Use readonly over getter for exposing events Fixes #4164 --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 2 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 7 +++-- .../typings/xterm-addon-webgl.d.ts | 4 +-- src/browser/Linkifier2.ts | 4 +-- src/browser/Terminal.ts | 20 +++++++------- src/browser/renderer/dom/DomRenderer.ts | 2 +- src/browser/services/CharSizeService.ts | 2 +- src/browser/services/RenderService.ts | 8 +++--- src/browser/services/SelectionService.ts | 8 +++--- src/common/CircularList.ts | 6 ++--- src/common/CoreTerminal.ts | 13 +++++----- src/common/InputHandler.ts | 26 +++++++++---------- src/common/buffer/BufferSet.ts | 2 +- src/common/buffer/Marker.ts | 2 +- src/common/input/WriteBuffer.ts | 3 ++- src/common/public/BufferNamespaceApi.ts | 3 ++- src/common/services/BufferService.ts | 4 +-- src/common/services/CoreMouseService.ts | 11 +++----- src/common/services/CoreService.ts | 6 ++--- src/common/services/DecorationService.ts | 6 ++--- src/common/services/OptionsService.ts | 2 +- src/common/services/UnicodeService.ts | 3 ++- src/headless/Terminal.ts | 11 ++++---- 24 files changed, 78 insertions(+), 81 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index b642efbca..68475829d 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -28,7 +28,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; private _onRequestRedraw = new EventEmitter(); - public get onRequestRedraw(): IEvent { return this._onRequestRedraw.event; } + public readonly onRequestRedraw = this._onRequestRedraw.event; constructor( private _colors: IColorSet, diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 5b98a048d..a96e7c64c 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -16,9 +16,9 @@ export class WebglAddon implements ITerminalAddon { private _renderer?: WebglRenderer; private _onChangeTextureAtlas = new EventEmitter(); - public get onChangeTextureAtlas(): IEvent { return this._onChangeTextureAtlas.event; } + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; private _onContextLoss = new EventEmitter(); - public get onContextLoss(): IEvent { return this._onContextLoss.event; } + public readonly onContextLoss = this._onContextLoss.event; constructor( private _preserveDrawingBuffer?: boolean diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d8daa0d18..74488c6c8 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -56,12 +56,11 @@ export class WebglRenderer extends Disposable implements IRenderer { private _contextRestorationTimeout: number | undefined; private _onChangeTextureAtlas = new EventEmitter(); - public get onChangeTextureAtlas(): IEvent { return this._onChangeTextureAtlas.event; } + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; private _onRequestRedraw = new EventEmitter(); - public get onRequestRedraw(): IEvent { return this._onRequestRedraw.event; } - + public readonly onRequestRedraw = this._onRequestRedraw.event; private _onContextLoss = new EventEmitter(); - public get onContextLoss(): IEvent { return this._onContextLoss.event; } + public readonly onContextLoss = this._onContextLoss.event; constructor( private _terminal: Terminal, diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts index 74aed0cc7..6865b6dbe 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -15,12 +15,12 @@ declare module 'xterm-addon-webgl' { /** * An event that is fired when the renderer loses its canvas context. */ - public get onContextLoss(): IEvent; + public readonly onContextLoss: IEvent; /** * An event that is fired when the texture atlas of the renderer changes. */ - public get onChangeTextureAtlas(): IEvent; + public readonly onChangeTextureAtlas: IEvent; constructor(preserveDrawingBuffer?: boolean); diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 9c978949b..6bf9fe237 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -27,9 +27,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeLine: number = -1; private _onShowLinkUnderline = this.register(new EventEmitter()); - public get onShowLinkUnderline(): IEvent { return this._onShowLinkUnderline.event; } + public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; private _onHideLinkUnderline = this.register(new EventEmitter()); - public get onHideLinkUnderline(): IEvent { return this._onHideLinkUnderline.event; } + public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; constructor( @IBufferService private readonly _bufferService: IBufferService diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index afdac748c..e03be89c3 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -123,26 +123,26 @@ export class Terminal extends CoreTerminal implements ITerminal { private _theme: ITheme | undefined; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); - public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } + public readonly onKey = this._onKey.event; private _onRender = new EventEmitter<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + public readonly onRender = this._onRender.event; private _onSelectionChange = new EventEmitter(); - public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + public readonly onSelectionChange = this._onSelectionChange.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } + public readonly onTitleChange = this._onTitleChange.event; private _onBell = new EventEmitter(); - public get onBell(): IEvent { return this._onBell.event; } + public readonly onBell = this._onBell.event; private _onFocus = new EventEmitter(); - public get onFocus(): IEvent { return this._onFocus.event; } + public readonly onFocus = this._onFocus.event; private _onBlur = new EventEmitter(); - public get onBlur(): IEvent { return this._onBlur.event; } + public readonly onBlur = this._onBlur.event; private _onA11yCharEmitter = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } + public readonly onA11yChar = this._onA11yCharEmitter.event; private _onA11yTabEmitter = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } + public readonly onA11yTab = this._onA11yTabEmitter.event; /** * Creates a new `Terminal` object. diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 8df6b3028..223e24705 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - public get onRequestRedraw(): IEvent { return new EventEmitter().event; } + public readonly onRequestRedraw = new EventEmitter().event; constructor( private _colors: IColorSet, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index b04e157f6..d5cc37c86 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -17,7 +17,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } private _onCharSizeChange = new EventEmitter(); - public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; } + public readonly onCharSizeChange = this._onCharSizeChange.event; constructor( document: Document, diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 972586098..692134297 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -40,13 +40,13 @@ export class RenderService extends Disposable implements IRenderService { }; private _onDimensionsChange = new EventEmitter(); - public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } + public readonly onDimensionsChange = this._onDimensionsChange.event; private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); - public get onRenderedViewportChange(): IEvent<{ start: number, end: number }> { return this._onRenderedViewportChange.event; } + public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; private _onRender = new EventEmitter<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + public readonly onRender = this._onRender.event; private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); - public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; } + public readonly onRefreshRequest = this._onRefreshRequest.event; public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 4ee1ffa1f..29c8e664b 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -112,13 +112,13 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionEnd: [number, number] | undefined = undefined; private _onLinuxMouseSelection = this.register(new EventEmitter()); - public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } + public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; private _onRedrawRequest = this.register(new EventEmitter()); - public get onRequestRedraw(): IEvent { return this._onRedrawRequest.event; } + public readonly onRequestRedraw = this._onRedrawRequest.event; private _onSelectionChange = this.register(new EventEmitter()); - public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + public readonly onSelectionChange = this._onSelectionChange.event; private _onRequestScrollLines = this.register(new EventEmitter()); - public get onRequestScrollLines(): IEvent { return this._onRequestScrollLines.event; } + public readonly onRequestScrollLines = this._onRequestScrollLines.event; constructor( private readonly _element: HTMLElement, diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 4d2c04eca..c0c6bb5b0 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -26,11 +26,11 @@ export class CircularList implements ICircularList { private _length: number; public onDeleteEmitter = new EventEmitter(); - public get onDelete(): IEvent { return this.onDeleteEmitter.event; } + public readonly onDelete = this.onDeleteEmitter.event; public onInsertEmitter = new EventEmitter(); - public get onInsert(): IEvent { return this.onInsertEmitter.event; } + public readonly onInsert = this.onInsertEmitter.event; public onTrimEmitter = new EventEmitter(); - public get onTrim(): IEvent { return this.onTrimEmitter.event; } + public readonly onTrim = this.onTrimEmitter.event; constructor( private _maxLength: number diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 02a943927..da70f84a3 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -62,21 +62,22 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _windowsMode: IDisposable | undefined; private _onBinary = new EventEmitter(); - public get onBinary(): IEvent { return this._onBinary.event; } + public readonly onBinary = this._onBinary.event; private _onData = new EventEmitter(); - public get onData(): IEvent { return this._onData.event; } + public readonly onData = this._onData.event; protected _onLineFeed = new EventEmitter(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } + public readonly onLineFeed = this._onLineFeed.event; private _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } - protected _onScroll = new EventEmitter(); - public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } + public readonly onResize = this._onResize.event; protected _onWriteParsed = new EventEmitter(); + public readonly onWriteParsed = this._onWriteParsed.event; + /** * Internally we track the source of the scroll but this is meaningless outside the library so * it's filtered out. */ protected _onScrollApi?: EventEmitter; + protected _onScroll = new EventEmitter(); public get onScroll(): IEvent { if (!this._onScrollApi) { this._onScrollApi = new EventEmitter(); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index b599bb7e4..bcdb20654 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -130,32 +130,32 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; private _onRequestBell = new EventEmitter(); - public get onRequestBell(): IEvent { return this._onRequestBell.event; } + public readonly onRequestBell = this._onRequestBell.event; private _onRequestRefreshRows = new EventEmitter(); - public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } + public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; private _onRequestReset = new EventEmitter(); - public get onRequestReset(): IEvent { return this._onRequestReset.event; } + public readonly onRequestReset = this._onRequestReset.event; private _onRequestSendFocus = new EventEmitter(); - public get onRequestSendFocus(): IEvent { return this._onRequestSendFocus.event; } + public readonly onRequestSendFocus = this._onRequestSendFocus.event; private _onRequestSyncScrollBar = new EventEmitter(); - public get onRequestSyncScrollBar(): IEvent { return this._onRequestSyncScrollBar.event; } + public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; private _onRequestWindowsOptionsReport = new EventEmitter(); - public get onRequestWindowsOptionsReport(): IEvent { return this._onRequestWindowsOptionsReport.event; } + public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; private _onA11yChar = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yChar.event; } + public readonly onA11yChar = this._onA11yChar.event; private _onA11yTab = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTab.event; } + public readonly onA11yTab = this._onA11yTab.event; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onLineFeed = new EventEmitter(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } + public readonly onLineFeed = this._onLineFeed.event; private _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } + public readonly onScroll = this._onScroll.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } + public readonly onTitleChange = this._onTitleChange.event; private _onColor = new EventEmitter(); - public get onColor(): IEvent { return this._onColor.event; } + public readonly onColor = this._onColor.event; private _parseStack: IParseStack = { paused: false, diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index f940bb8f3..b2223b032 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -20,7 +20,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _activeBuffer!: Buffer; private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); - public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; } + public readonly onBufferActivate = this._onBufferActivate.event; /** * Create a new BufferSet for the given terminal. diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 72c4085ca..9737b3894 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -16,7 +16,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } + public readonly onDispose = this._onDispose.event; constructor( public line: number diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 4f316f24b..9f816d6c4 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -41,8 +41,9 @@ export class WriteBuffer { private _isSyncWriting = false; private _syncCalls = 0; private _didUserInput = false; - public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } + private _onWriteParsed = new EventEmitter(); + public readonly onWriteParsed = this._onWriteParsed.event; constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index d86f6bf5a..f331a0b76 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -11,8 +11,9 @@ import { ICoreTerminal } from 'common/Types'; export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; + private _onBufferChange = new EventEmitter(); - public get onBufferChange(): IEvent { return this._onBufferChange.event; } + public readonly onBufferChange = this._onBufferChange.event; constructor(private _core: ICoreTerminal) { this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index e3b7dcd87..49f311470 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -23,9 +23,9 @@ export class BufferService extends Disposable implements IBufferService { public isUserScrolling: boolean = false; private _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + public readonly onResize = this._onResize.event; private _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } + public readonly onScroll = this._onScroll.event; public get buffer(): IBuffer { return this.buffers.active; } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 54e991f8c..465a4968b 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -170,9 +170,11 @@ export class CoreMouseService implements ICoreMouseService { private _encodings: { [name: string]: CoreMouseEncoding } = {}; private _activeProtocol: string = ''; private _activeEncoding: string = ''; - private _onProtocolChange = new EventEmitter(); private _lastEvent: ICoreMouseEvent | null = null; + private _onProtocolChange = new EventEmitter(); + public readonly onProtocolChange = this._onProtocolChange.event; + constructor( @IBufferService private readonly _bufferService: IBufferService, @ICoreService private readonly _coreService: ICoreService @@ -225,13 +227,6 @@ export class CoreMouseService implements ICoreMouseService { this._lastEvent = null; } - /** - * Event to announce changes in mouse tracking. - */ - public get onProtocolChange(): IEvent { - return this._onProtocolChange.event; - } - /** * Triggers a mouse event to be sent. * diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 20a346031..a144eb1d3 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -35,11 +35,11 @@ export class CoreService extends Disposable implements ICoreService { private _scrollToBottom: (() => void) | undefined; private _onData = this.register(new EventEmitter()); - public get onData(): IEvent { return this._onData.event; } + public readonly onData = this._onData.event; private _onUserInput = this.register(new EventEmitter()); - public get onUserInput(): IEvent { return this._onUserInput.event; } + public readonly onUserInput = this._onUserInput.event; private _onBinary = this.register(new EventEmitter()); - public get onBinary(): IEvent { return this._onBinary.event; } + public readonly onBinary = this._onBinary.event; constructor( // TODO: Move this into a service diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index e5d115a14..d0f9b9919 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -28,9 +28,9 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); private _onDecorationRegistered = this.register(new EventEmitter()); - public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } + public readonly onDecorationRegistered = this._onDecorationRegistered.event; private _onDecorationRemoved = this.register(new EventEmitter()); - public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } + public readonly onDecorationRemoved = this._onDecorationRemoved.event; public get decorations(): IterableIterator { return this._decorations.values(); } @@ -99,7 +99,7 @@ class Decoration extends Disposable implements IInternalDecoration { public readonly onRenderEmitter = this.register(new EventEmitter()); public readonly onRender = this.onRenderEmitter.event; - private _onDispose = this.register(new EventEmitter()); + private readonly _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; private _cachedBg: IColor | undefined | null = null; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index c7e8d294f..d6662c315 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -58,7 +58,7 @@ export class OptionsService implements IOptionsService { public options: Required; private _onOptionChange = new EventEmitter(); - public get onOptionChange(): IEvent { return this._onOptionChange.event; } + public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { // set the default value of each option diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index e96b75793..b5d19623b 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -13,8 +13,9 @@ export class UnicodeService implements IUnicodeService { private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null); private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; + private _onChange = new EventEmitter(); - public get onChange(): IEvent { return this._onChange.event; } + public readonly onChange = this._onChange.event; constructor() { const defaultProvider = new UnicodeV6(); diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 1cad0ee2d..1b1162e5e 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -33,16 +33,15 @@ export class Terminal extends CoreTerminal { public get options(): Required { return this.optionsService.options; } private _onBell = new EventEmitter(); - public get onBell(): IEvent { return this._onBell.event; } + public readonly onBell = this._onBell.event; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } - + public readonly onTitleChange = this._onTitleChange.event; private _onA11yCharEmitter = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } + public readonly onA11yChar = this._onA11yCharEmitter.event; private _onA11yTabEmitter = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } + public readonly onA11yTab = this._onA11yTabEmitter.event; /** * Creates a new `Terminal` object. From 1a22c44f2b7ca10d23755c86b78d3361fa2570be Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:02:32 -0700 Subject: [PATCH 24/56] Make all emitters readonly --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 2 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 6 ++--- src/browser/Linkifier2.ts | 4 +-- src/browser/Terminal.ts | 20 +++++++------- src/browser/services/CharSizeService.ts | 2 +- src/browser/services/RenderService.ts | 8 +++--- src/browser/services/SelectionService.ts | 8 +++--- src/common/CircularList.ts | 6 ++--- src/common/CoreTerminal.ts | 8 +++--- src/common/InputHandler.ts | 26 +++++++++---------- src/common/buffer/BufferSet.ts | 2 +- src/common/buffer/Marker.ts | 2 +- src/common/input/WriteBuffer.ts | 2 +- src/common/public/BufferNamespaceApi.ts | 2 +- src/common/services/BufferService.ts | 6 ++--- src/common/services/CoreMouseService.ts | 2 +- src/common/services/CoreService.ts | 6 ++--- src/common/services/DecorationService.ts | 4 +-- src/common/services/OptionsService.ts | 2 +- src/common/services/UnicodeService.ts | 2 +- src/headless/Terminal.ts | 10 +++---- 22 files changed, 67 insertions(+), 67 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 68475829d..fd9629c06 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -27,7 +27,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - private _onRequestRedraw = new EventEmitter(); + private readonly _onRequestRedraw = new EventEmitter(); public readonly onRequestRedraw = this._onRequestRedraw.event; constructor( diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index a96e7c64c..45858e8c8 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -15,9 +15,9 @@ export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = new EventEmitter(); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private _onContextLoss = new EventEmitter(); + private readonly _onContextLoss = new EventEmitter(); public readonly onContextLoss = this._onContextLoss.event; constructor( diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 74488c6c8..0fc634a79 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -55,11 +55,11 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - private _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = new EventEmitter(); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private _onRequestRedraw = new EventEmitter(); + private readonly _onRequestRedraw = new EventEmitter(); public readonly onRequestRedraw = this._onRequestRedraw.event; - private _onContextLoss = new EventEmitter(); + private readonly _onContextLoss = new EventEmitter(); public readonly onContextLoss = this._onContextLoss.event; constructor( diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 6bf9fe237..cf5b9dd2e 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -26,9 +26,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeProviderReplies: Map | undefined; private _activeLine: number = -1; - private _onShowLinkUnderline = this.register(new EventEmitter()); + private readonly _onShowLinkUnderline = this.register(new EventEmitter()); public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; - private _onHideLinkUnderline = this.register(new EventEmitter()); + private readonly _onHideLinkUnderline = this.register(new EventEmitter()); public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; constructor( diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index e03be89c3..9c10fc16e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -122,26 +122,26 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); + private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); public readonly onKey = this._onKey.event; - private _onRender = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); public readonly onRender = this._onRender.event; - private _onSelectionChange = new EventEmitter(); + private readonly _onSelectionChange = new EventEmitter(); public readonly onSelectionChange = this._onSelectionChange.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onBell = new EventEmitter(); + private readonly _onBell = new EventEmitter(); public readonly onBell = this._onBell.event; - private _onFocus = new EventEmitter(); + private readonly _onFocus = new EventEmitter(); public readonly onFocus = this._onFocus.event; - private _onBlur = new EventEmitter(); + private readonly _onBlur = new EventEmitter(); public readonly onBlur = this._onBlur.event; - private _onA11yCharEmitter = new EventEmitter(); + private readonly _onA11yCharEmitter = new EventEmitter(); public readonly onA11yChar = this._onA11yCharEmitter.event; - private _onA11yTabEmitter = new EventEmitter(); + private readonly _onA11yTabEmitter = new EventEmitter(); public readonly onA11yTab = this._onA11yTabEmitter.event; /** diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index d5cc37c86..7062deece 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -16,7 +16,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - private _onCharSizeChange = new EventEmitter(); + private readonly _onCharSizeChange = new EventEmitter(); public readonly onCharSizeChange = this._onCharSizeChange.event; constructor( diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 692134297..66dbb7aa2 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -39,13 +39,13 @@ export class RenderService extends Disposable implements IRenderService { columnSelectMode: false }; - private _onDimensionsChange = new EventEmitter(); + private readonly _onDimensionsChange = new EventEmitter(); public readonly onDimensionsChange = this._onDimensionsChange.event; - private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; - private _onRender = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); public readonly onRender = this._onRender.event; - private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); public readonly onRefreshRequest = this._onRefreshRequest.event; public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 29c8e664b..3780c7ee8 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -111,13 +111,13 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionStart: [number, number] | undefined = undefined; private _oldSelectionEnd: [number, number] | undefined = undefined; - private _onLinuxMouseSelection = this.register(new EventEmitter()); + private readonly _onLinuxMouseSelection = this.register(new EventEmitter()); public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; - private _onRedrawRequest = this.register(new EventEmitter()); + private readonly _onRedrawRequest = this.register(new EventEmitter()); public readonly onRequestRedraw = this._onRedrawRequest.event; - private _onSelectionChange = this.register(new EventEmitter()); + private readonly _onSelectionChange = this.register(new EventEmitter()); public readonly onSelectionChange = this._onSelectionChange.event; - private _onRequestScrollLines = this.register(new EventEmitter()); + private readonly _onRequestScrollLines = this.register(new EventEmitter()); public readonly onRequestScrollLines = this._onRequestScrollLines.event; constructor( diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index c0c6bb5b0..599db1aba 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -25,11 +25,11 @@ export class CircularList implements ICircularList { private _startIndex: number; private _length: number; - public onDeleteEmitter = new EventEmitter(); + public readonly onDeleteEmitter = new EventEmitter(); public readonly onDelete = this.onDeleteEmitter.event; - public onInsertEmitter = new EventEmitter(); + public readonly onInsertEmitter = new EventEmitter(); public readonly onInsert = this.onInsertEmitter.event; - public onTrimEmitter = new EventEmitter(); + public readonly onTrimEmitter = new EventEmitter(); public readonly onTrim = this.onTrimEmitter.event; constructor( diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index da70f84a3..3a17c567a 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -61,15 +61,15 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - private _onBinary = new EventEmitter(); + private readonly _onBinary = new EventEmitter(); public readonly onBinary = this._onBinary.event; - private _onData = new EventEmitter(); + private readonly _onData = new EventEmitter(); public readonly onData = this._onData.event; protected _onLineFeed = new EventEmitter(); public readonly onLineFeed = this._onLineFeed.event; - private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); public readonly onResize = this._onResize.event; - protected _onWriteParsed = new EventEmitter(); + protected readonly _onWriteParsed = new EventEmitter(); public readonly onWriteParsed = this._onWriteParsed.event; /** diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index bcdb20654..0ace846af 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -129,32 +129,32 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; - private _onRequestBell = new EventEmitter(); + private readonly _onRequestBell = new EventEmitter(); public readonly onRequestBell = this._onRequestBell.event; - private _onRequestRefreshRows = new EventEmitter(); + private readonly _onRequestRefreshRows = new EventEmitter(); public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; - private _onRequestReset = new EventEmitter(); + private readonly _onRequestReset = new EventEmitter(); public readonly onRequestReset = this._onRequestReset.event; - private _onRequestSendFocus = new EventEmitter(); + private readonly _onRequestSendFocus = new EventEmitter(); public readonly onRequestSendFocus = this._onRequestSendFocus.event; - private _onRequestSyncScrollBar = new EventEmitter(); + private readonly _onRequestSyncScrollBar = new EventEmitter(); public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; - private _onRequestWindowsOptionsReport = new EventEmitter(); + private readonly _onRequestWindowsOptionsReport = new EventEmitter(); public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; - private _onA11yChar = new EventEmitter(); + private readonly _onA11yChar = new EventEmitter(); public readonly onA11yChar = this._onA11yChar.event; - private _onA11yTab = new EventEmitter(); + private readonly _onA11yTab = new EventEmitter(); public readonly onA11yTab = this._onA11yTab.event; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onLineFeed = new EventEmitter(); + private readonly _onLineFeed = new EventEmitter(); public readonly onLineFeed = this._onLineFeed.event; - private _onScroll = new EventEmitter(); + private readonly _onScroll = new EventEmitter(); public readonly onScroll = this._onScroll.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onColor = new EventEmitter(); + private readonly _onColor = new EventEmitter(); public readonly onColor = this._onColor.event; private _parseStack: IParseStack = { diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index b2223b032..46fcb0979 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -19,7 +19,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _alt!: Buffer; private _activeBuffer!: Buffer; - private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); + private readonly _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); public readonly onBufferActivate = this._onBufferActivate.event; /** diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 9737b3894..56d64a726 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -15,7 +15,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } - private _onDispose = new EventEmitter(); + private readonly _onDispose = new EventEmitter(); public readonly onDispose = this._onDispose.event; constructor( diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 9f816d6c4..bf72ae21c 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -42,7 +42,7 @@ export class WriteBuffer { private _syncCalls = 0; private _didUserInput = false; - private _onWriteParsed = new EventEmitter(); + private readonly _onWriteParsed = new EventEmitter(); public readonly onWriteParsed = this._onWriteParsed.event; constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index f331a0b76..033f5955d 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -12,7 +12,7 @@ export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; - private _onBufferChange = new EventEmitter(); + private readonly _onBufferChange = new EventEmitter(); public readonly onBufferChange = this._onBufferChange.event; constructor(private _core: ICoreTerminal) { diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 49f311470..1bc93041c 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,7 +6,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types'; @@ -22,9 +22,9 @@ export class BufferService extends Disposable implements IBufferService { /** Whether the user is scrolling (locks the scroll position) */ public isUserScrolling: boolean = false; - private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); public readonly onResize = this._onResize.event; - private _onScroll = new EventEmitter(); + private readonly _onScroll = new EventEmitter(); public readonly onScroll = this._onScroll.event; public get buffer(): IBuffer { return this.buffers.active; } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 465a4968b..8c2a24def 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -172,7 +172,7 @@ export class CoreMouseService implements ICoreMouseService { private _activeEncoding: string = ''; private _lastEvent: ICoreMouseEvent | null = null; - private _onProtocolChange = new EventEmitter(); + private readonly _onProtocolChange = new EventEmitter(); public readonly onProtocolChange = this._onProtocolChange.event; constructor( diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index a144eb1d3..9282197b3 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -34,11 +34,11 @@ export class CoreService extends Disposable implements ICoreService { // Circular dependency, this must be unset or memory will leak after Terminal.dispose private _scrollToBottom: (() => void) | undefined; - private _onData = this.register(new EventEmitter()); + private readonly _onData = this.register(new EventEmitter()); public readonly onData = this._onData.event; - private _onUserInput = this.register(new EventEmitter()); + private readonly _onUserInput = this.register(new EventEmitter()); public readonly onUserInput = this._onUserInput.event; - private _onBinary = this.register(new EventEmitter()); + private readonly _onBinary = this.register(new EventEmitter()); public readonly onBinary = this._onBinary.event; constructor( diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index d0f9b9919..9fb611c61 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -27,9 +27,9 @@ export class DecorationService extends Disposable implements IDecorationService */ private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); - private _onDecorationRegistered = this.register(new EventEmitter()); + private readonly _onDecorationRegistered = this.register(new EventEmitter()); public readonly onDecorationRegistered = this._onDecorationRegistered.event; - private _onDecorationRemoved = this.register(new EventEmitter()); + private readonly _onDecorationRemoved = this.register(new EventEmitter()); public readonly onDecorationRemoved = this._onDecorationRemoved.event; public get decorations(): IterableIterator { return this._decorations.values(); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d6662c315..33aa3ee23 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -57,7 +57,7 @@ export class OptionsService implements IOptionsService { public readonly rawOptions: Required; public options: Required; - private _onOptionChange = new EventEmitter(); + private readonly _onOptionChange = new EventEmitter(); public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index b5d19623b..239f4d622 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -14,7 +14,7 @@ export class UnicodeService implements IUnicodeService { private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - private _onChange = new EventEmitter(); + private readonly _onChange = new EventEmitter(); public readonly onChange = this._onChange.event; constructor() { diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 1b1162e5e..639988ebd 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -32,15 +32,15 @@ export class Terminal extends CoreTerminal { // TODO: We should remove options once components adopt optionsService public get options(): Required { return this.optionsService.options; } - private _onBell = new EventEmitter(); + private readonly _onBell = new EventEmitter(); public readonly onBell = this._onBell.event; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onA11yCharEmitter = new EventEmitter(); + private readonly _onA11yCharEmitter = new EventEmitter(); public readonly onA11yChar = this._onA11yCharEmitter.event; - private _onA11yTabEmitter = new EventEmitter(); + private readonly _onA11yTabEmitter = new EventEmitter(); public readonly onA11yTab = this._onA11yTabEmitter.event; /** From ed15a8e17031b8317b5e7dfcae0cdada20523391 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:45:40 -0700 Subject: [PATCH 25/56] Create new event with emitter object to simplify code --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 11 +- addons/xterm-addon-search/src/SearchAddon.ts | 11 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 12 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 19 ++-- src/browser/Linkifier2.ts | 10 +- src/browser/Terminal.ts | 55 ++++------ src/browser/TestUtils.test.ts | 20 ++-- .../decorations/BufferDecorationRenderer.ts | 4 +- src/browser/renderer/dom/DomRenderer.ts | 4 +- src/browser/services/CharSizeService.ts | 7 +- src/browser/services/RenderService.ts | 20 ++-- src/browser/services/SelectionService.ts | 28 ++--- src/common/CircularList.ts | 25 ++--- src/common/CoreTerminal.ts | 27 ++--- src/common/EventEmitter.ts | 16 ++- src/common/InputHandler.ts | 103 ++++++++---------- src/common/TestUtils.test.ts | 22 ++-- src/common/Types.d.ts | 11 +- src/common/buffer/Buffer.test.ts | 6 +- src/common/buffer/Buffer.ts | 4 +- src/common/buffer/BufferReflow.ts | 2 +- src/common/buffer/BufferSet.ts | 11 +- src/common/buffer/Marker.ts | 7 +- src/common/input/WriteBuffer.ts | 7 +- src/common/public/BufferNamespaceApi.ts | 7 +- src/common/services/BufferService.ts | 14 +-- src/common/services/CoreMouseService.ts | 7 +- src/common/services/CoreService.ts | 17 ++- src/common/services/DecorationService.ts | 22 ++-- src/common/services/OptionsService.ts | 7 +- src/common/services/Services.ts | 4 +- src/common/services/UnicodeService.ts | 7 +- src/headless/Terminal.ts | 27 ++--- 33 files changed, 247 insertions(+), 307 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index fd9629c06..726ad0803 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -14,7 +14,7 @@ import { IColorSet, ILinkifier2 } from 'browser/Types'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from './atlas/CharAtlasCache'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; let nextRendererId = 1; @@ -27,8 +27,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - private readonly _onRequestRedraw = new EventEmitter(); - public readonly onRequestRedraw = this._onRequestRedraw.event; + public readonly onRequestRedraw = initEvent(); constructor( private _colors: IColorSet, @@ -48,7 +47,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), - new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) + new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this.onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) ]; this.dimensions = { scaledCharWidth: 0, @@ -128,7 +127,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode)); // Selection foreground requires a full re-render if (this._colors.selectionForeground) { - this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } @@ -201,6 +200,6 @@ export class CanvasRenderer extends Disposable implements IRenderer { } private _requestRedrawViewport(): void { - this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 689899ef1..d14e1fe76 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -4,7 +4,7 @@ */ import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm'; -import { EventEmitter } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; export interface ISearchOptions { regex?: boolean; @@ -72,8 +72,7 @@ export class SearchAddon implements ITerminalAddon { private _resultIndex: number | undefined; - private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>(); - public readonly onDidChangeResults = this._onDidChangeResults.event; + public readonly onDidChangeResults = initEvent<{ resultIndex: number, resultCount: number } | undefined>(); public activate(terminal: Terminal): void { this._terminal = terminal; @@ -89,7 +88,7 @@ export class SearchAddon implements ITerminalAddon { this._highlightTimeout = setTimeout(() => { this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true }); this._resultIndex = this._searchResults ? this._searchResults.size - 1 : -1; - this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 }); + this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 }); }, 200); } } @@ -325,9 +324,9 @@ export class SearchAddon implements ITerminalAddon { private _fireResults(term: string, found: boolean, searchOptions?: ISearchOptions): boolean { if (searchOptions?.decorations) { if (this._resultIndex !== undefined && this._searchResults?.size !== undefined) { - this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); + this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); } else { - this._onDidChangeResults.fire(undefined); + this.onDidChangeResults.fire(undefined); } } this._cachedSearchTerm = term; diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 45858e8c8..548be30c3 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -7,7 +7,7 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; -import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent, initEvent } from 'common/EventEmitter'; import { isSafari } from 'common/Platform'; import { ICoreService, IDecorationService } from 'common/services/Services'; @@ -15,10 +15,8 @@ export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private readonly _onChangeTextureAtlas = new EventEmitter(); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onContextLoss = new EventEmitter(); - public readonly onContextLoss = this._onContextLoss.event; + public readonly onChangeTextureAtlas = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _preserveDrawingBuffer?: boolean @@ -39,8 +37,8 @@ export class WebglAddon implements ITerminalAddon { const decorationService: IDecorationService = (terminal as any)._core._decorationService; const colors: IColorSet = (terminal as any)._core._colorManager.colors; this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer); - forwardEvent(this._renderer.onContextLoss, this._onContextLoss); - forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); + forwardEvent(this._renderer.onContextLoss, this.onContextLoss); + forwardEvent(this._renderer.onChangeTextureAtlas, this.onChangeTextureAtlas); renderService.setRenderer(this._renderer); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 58fbb6ed3..7a4872428 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -18,7 +18,7 @@ import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, initEvent } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; @@ -53,12 +53,9 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - private readonly _onChangeTextureAtlas = new EventEmitter(); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onRequestRedraw = new EventEmitter(); - public readonly onRequestRedraw = this._onRequestRedraw.event; - private readonly _onContextLoss = new EventEmitter(); - public readonly onContextLoss = this._onContextLoss.event; + public readonly onChangeTextureAtlas = initEvent(); + public readonly onRequestRedraw = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _terminal: Terminal, @@ -75,7 +72,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers = [ new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core, this._coreBrowserService), - new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._onRequestRedraw, this._coreBrowserService, coreService) + new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this.onRequestRedraw, this._coreBrowserService, coreService) ]; this.dimensions = { scaledCharWidth: 0, @@ -115,7 +112,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._contextRestorationTimeout = setTimeout(() => { this._contextRestorationTimeout = undefined; console.warn('webgl context not restored; firing onContextLoss'); - this._onContextLoss.fire(e); + this.onContextLoss.fire(e); }, 3000 /* ms */); })); this.register(addDisposableDomListener(this._canvas, 'webglcontextrestored', (e) => { @@ -283,7 +280,7 @@ export class WebglRenderer extends Disposable implements IRenderer { throw new Error('The webgl renderer only works with the webgl char atlas'); } if (this._charAtlas !== atlas) { - this._onChangeTextureAtlas.fire(atlas.cacheCanvas); + this.onChangeTextureAtlas.fire(atlas.cacheCanvas); } this._charAtlas = atlas; this._charAtlas.warmUp(); @@ -676,7 +673,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _requestRedrawViewport(): void { - this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } } diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index cf5b9dd2e..844b64f82 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -7,7 +7,7 @@ import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent import { IDisposable } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -26,10 +26,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeProviderReplies: Map | undefined; private _activeLine: number = -1; - private readonly _onShowLinkUnderline = this.register(new EventEmitter()); - public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; - private readonly _onHideLinkUnderline = this.register(new EventEmitter()); - public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; + public readonly onShowLinkUnderline = this.register(initEvent()); + public readonly onHideLinkUnderline = this.register(initEvent()); constructor( @IBufferService private readonly _bufferService: IBufferService @@ -343,7 +341,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { const range = link.range; const scrollOffset = this._bufferService.buffer.ydisp; const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined); - const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline; + const emitter = showEvent ? this.onShowLinkUnderline : this.onHideLinkUnderline; emitter.fire(event); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9c10fc16e..90c1b1345 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -37,7 +37,7 @@ import { ITheme, IMarker, IDisposable, ILinkProvider, IDecorationOptions, IDecor import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; @@ -122,27 +122,16 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); - public readonly onKey = this._onKey.event; - private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); - public readonly onRender = this._onRender.event; - private readonly _onSelectionChange = new EventEmitter(); - public readonly onSelectionChange = this._onSelectionChange.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onBell = new EventEmitter(); - public readonly onBell = this._onBell.event; - - private readonly _onFocus = new EventEmitter(); - public readonly onFocus = this._onFocus.event; - private readonly _onBlur = new EventEmitter(); - public readonly onBlur = this._onBlur.event; - private readonly _onA11yCharEmitter = new EventEmitter(); - public readonly onA11yChar = this._onA11yCharEmitter.event; - private readonly _onA11yTabEmitter = new EventEmitter(); - public readonly onA11yTab = this._onA11yTabEmitter.event; + public readonly onCursorMove = initEvent(); + public readonly onKey = initEvent<{ key: string, domEvent: KeyboardEvent }>(); + public readonly onRender = initEvent<{ start: number, end: number }>(); + public readonly onSelectionChange = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onBell = initEvent(); + public readonly onFocus = initEvent(); + public readonly onBlur = initEvent(); + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); /** * Creates a new `Terminal` object. @@ -169,16 +158,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this._instantiationService.setService(IDecorationService, this._decorationService); // Setup InputHandler listeners - this.register(this._inputHandler.onRequestBell(() => this._onBell.fire())); + this.register(this._inputHandler.onRequestBell(() => this.onBell.fire())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event))); - this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); - this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); - this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); - this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); + this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab)); // Setup listeners this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); @@ -326,7 +315,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.updateCursorStyle(ev); this.element!.classList.add('focus'); this._showCursor(); - this._onFocus.fire(); + this.onFocus.fire(); } /** @@ -349,7 +338,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.coreService.triggerDataEvent(C0.ESC + '[O'); } this.element!.classList.remove('focus'); - this._onBlur.fire(); + this.onBlur.fire(); } private _syncTextArea(): void { @@ -512,7 +501,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const renderer = this._createRenderer(); this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement)); this._instantiationService.setService(IRenderService, this._renderService); - this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); + this.register(this._renderService.onRenderedViewportChange(e => this.onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); this._compositionView = document.createElement('div'); @@ -552,7 +541,7 @@ export class Terminal extends CoreTerminal implements ITerminal { )); this._instantiationService.setService(ISelectionService, this._selectionService); this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent))); - this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire())); + this.register(this._selectionService.onSelectionChange(() => this.onSelectionChange.fire())); this.register(this._selectionService.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode))); this.register(this._selectionService.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it @@ -1101,7 +1090,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.textarea!.value = ''; } - this._onKey.fire({ key: result.key, domEvent: event }); + this.onKey.fire({ key: result.key, domEvent: event }); this._showCursor(); this.coreService.triggerDataEvent(result.key, true); @@ -1184,7 +1173,7 @@ export class Terminal extends CoreTerminal implements ITerminal { key = String.fromCharCode(key); - this._onKey.fire({ key, domEvent: ev }); + this.onKey.fire({ key, domEvent: ev }); this._showCursor(); this.coreService.triggerDataEvent(key, true); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 0b5e00c17..959203756 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; @@ -352,7 +352,7 @@ export class MockCoreBrowserService implements ICoreBrowserService { export class MockCharSizeService implements ICharSizeService { public serviceBrand: undefined; public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - public onCharSizeChange: IEvent = new EventEmitter().event; + public onCharSizeChange: IEvent = initEvent(); constructor(public width: number, public height: number) {} public measure(): void {} } @@ -370,10 +370,10 @@ export class MockMouseService implements IMouseService { export class MockRenderService implements IRenderService { public serviceBrand: undefined; - public onDimensionsChange: IEvent = new EventEmitter().event; - public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; - public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; - public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event; + public onDimensionsChange: IEvent = initEvent(); + public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>(); + public onRender: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>(); + public onRefreshRequest: IEvent<{ start: number, end: number}, void> = initEvent<{ start: number, end: number }>(); public dimensions: IRenderDimensions = { scaledCharWidth: 0, scaledCharHeight: 0, @@ -457,10 +457,10 @@ export class MockSelectionService implements ISelectionService { public hasSelection: boolean = false; public selectionStart: [number, number] | undefined; public selectionEnd: [number, number] | undefined; - public onLinuxMouseSelection = new EventEmitter().event; - public onRequestRedraw = new EventEmitter().event; - public onRequestScrollLines = new EventEmitter().event; - public onSelectionChange = new EventEmitter().event; + public onLinuxMouseSelection = initEvent(); + public onRequestRedraw = initEvent(); + public onRequestScrollLines = initEvent(); + public onSelectionChange = initEvent(); public disable(): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 7fcc5ea95..23ba174f8 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -95,7 +95,7 @@ export class BufferDecorationRenderer extends Disposable { // outside of viewport if (decoration.element) { decoration.element.style.display = 'none'; - decoration.onRenderEmitter.fire(decoration.element); + decoration.onRender.fire(decoration.element); } } else { let element = this._decorationElements.get(decoration); @@ -108,7 +108,7 @@ export class BufferDecorationRenderer extends Disposable { } element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`; element.style.display = this._altBufferIsActive ? 'none' : 'block'; - decoration.onRenderEmitter.fire(element); + decoration.onRender.fire(element); } } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 223e24705..39de7b2cf 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { color } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - public readonly onRequestRedraw = new EventEmitter().event; + public readonly onRequestRedraw = initEvent(); constructor( private _colors: IColorSet, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 7062deece..583006d61 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -4,7 +4,7 @@ */ import { IOptionsService } from 'common/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; export class CharSizeService implements ICharSizeService { @@ -16,8 +16,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - private readonly _onCharSizeChange = new EventEmitter(); - public readonly onCharSizeChange = this._onCharSizeChange.event; + public readonly onCharSizeChange = initEvent(); constructor( document: Document, @@ -32,7 +31,7 @@ export class CharSizeService implements ICharSizeService { if (result.width !== this.width || result.height !== this.height) { this.width = result.width; this.height = result.height; - this._onCharSizeChange.fire(); + this.onCharSizeChange.fire(); } } } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 66dbb7aa2..dfb4df80b 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -5,7 +5,7 @@ import { IRenderer, IRenderDimensions } from 'browser/renderer/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -39,14 +39,10 @@ export class RenderService extends Disposable implements IRenderService { columnSelectMode: false }; - private readonly _onDimensionsChange = new EventEmitter(); - public readonly onDimensionsChange = this._onDimensionsChange.event; - private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); - public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; - private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); - public readonly onRender = this._onRender.event; - private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); - public readonly onRefreshRequest = this._onRefreshRequest.event; + public readonly onDimensionsChange = initEvent(); + public readonly onRenderedViewportChange = initEvent<{ start: number, end: number }>(); + public readonly onRender = initEvent<{ start: number, end: number }>(); + public readonly onRefreshRequest = initEvent<{ start: number, end: number }>(); public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } @@ -135,9 +131,9 @@ export class RenderService extends Disposable implements IRenderService { // Fire render event only if it was not a redraw if (!this._isNextRenderRedrawOnly) { - this._onRenderedViewportChange.fire({ start, end }); + this.onRenderedViewportChange.fire({ start, end }); } - this._onRender.fire({ start, end }); + this.onRender.fire({ start, end }); this._isNextRenderRedrawOnly = true; } @@ -157,7 +153,7 @@ export class RenderService extends Disposable implements IRenderService { if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) { return; } - this._onDimensionsChange.fire(this._renderer.dimensions); + this.onDimensionsChange.fire(this._renderer.dimensions); } public dispose(): void { diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 3780c7ee8..5da197b13 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -9,7 +9,7 @@ import { IBufferLine, IDisposable } from 'common/Types'; import * as Browser from 'common/Platform'; import { SelectionModel } from 'browser/selection/SelectionModel'; import { CellData } from 'common/buffer/CellData'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IMouseService, ISelectionService, IRenderService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferRange, ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; @@ -111,14 +111,10 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionStart: [number, number] | undefined = undefined; private _oldSelectionEnd: [number, number] | undefined = undefined; - private readonly _onLinuxMouseSelection = this.register(new EventEmitter()); - public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; - private readonly _onRedrawRequest = this.register(new EventEmitter()); - public readonly onRequestRedraw = this._onRedrawRequest.event; - private readonly _onSelectionChange = this.register(new EventEmitter()); - public readonly onSelectionChange = this._onSelectionChange.event; - private readonly _onRequestScrollLines = this.register(new EventEmitter()); - public readonly onRequestScrollLines = this._onRequestScrollLines.event; + public readonly onLinuxMouseSelection = this.register(initEvent()); + public readonly onRequestRedraw = this.register(initEvent()); + public readonly onSelectionChange = this.register(initEvent()); + public readonly onRequestScrollLines = this.register(initEvent()); constructor( private readonly _element: HTMLElement, @@ -260,7 +256,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.clearSelection(); this._removeMouseDownListeners(); this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } /** @@ -279,7 +275,7 @@ export class SelectionService extends Disposable implements ISelectionService { if (Browser.isLinux && isLinuxMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { - this._onLinuxMouseSelection.fire(this.selectionText); + this.onLinuxMouseSelection.fire(this.selectionText); } } } @@ -290,7 +286,7 @@ export class SelectionService extends Disposable implements ISelectionService { */ private _refresh(): void { this._refreshAnimationFrame = undefined; - this._onRedrawRequest.fire({ + this.onRequestRedraw.fire({ start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd, columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN @@ -358,7 +354,7 @@ export class SelectionService extends Disposable implements ISelectionService { public selectAll(): void { this._model.isSelectAllActive = true; this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } public selectLines(start: number, end: number): void { @@ -368,7 +364,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.selectionStart = [0, start]; this._model.selectionEnd = [this._bufferService.cols, end]; this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } /** @@ -665,7 +661,7 @@ export class SelectionService extends Disposable implements ISelectionService { return; } if (this._dragScrollAmount) { - this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false }); + this.onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false }); // Re-evaluate selection // If the cursor was above or below the viewport, make sure it's at the // start or end of the viewport respectively. This should only happen when @@ -743,7 +739,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._oldSelectionStart = start; this._oldSelectionEnd = end; this._oldHasSelection = hasSelection; - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void { diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 599db1aba..537434494 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -4,7 +4,7 @@ */ import { ICircularList } from 'common/Types'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; export interface IInsertEvent { index: number; @@ -25,12 +25,9 @@ export class CircularList implements ICircularList { private _startIndex: number; private _length: number; - public readonly onDeleteEmitter = new EventEmitter(); - public readonly onDelete = this.onDeleteEmitter.event; - public readonly onInsertEmitter = new EventEmitter(); - public readonly onInsert = this.onInsertEmitter.event; - public readonly onTrimEmitter = new EventEmitter(); - public readonly onTrim = this.onTrimEmitter.event; + public readonly onDelete = initEvent(); + public readonly onInsert = initEvent(); + public readonly onTrim = initEvent(); constructor( private _maxLength: number @@ -107,7 +104,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(this._length)] = value; if (this._length === this._maxLength) { this._startIndex = ++this._startIndex % this._maxLength; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); } else { this._length++; } @@ -123,7 +120,7 @@ export class CircularList implements ICircularList { throw new Error('Can only recycle when the buffer is full'); } this._startIndex = ++this._startIndex % this._maxLength; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); return this._array[this._getCyclicIndex(this._length - 1)]!; } @@ -158,7 +155,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; } this._length -= deleteCount; - this.onDeleteEmitter.fire({ index: start, amount: deleteCount }); + this.onDelete.fire({ index: start, amount: deleteCount }); } // Add items @@ -169,7 +166,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(start + i)] = items[i]; } if (items.length) { - this.onInsertEmitter.fire({ index: start, amount: items.length }); + this.onInsert.fire({ index: start, amount: items.length }); } // Adjust length as needed @@ -177,7 +174,7 @@ export class CircularList implements ICircularList { const countToTrim = (this._length + items.length) - this._maxLength; this._startIndex += countToTrim; this._length = this._maxLength; - this.onTrimEmitter.fire(countToTrim); + this.onTrim.fire(countToTrim); } else { this._length += items.length; } @@ -193,7 +190,7 @@ export class CircularList implements ICircularList { } this._startIndex += count; this._length -= count; - this.onTrimEmitter.fire(count); + this.onTrim.fire(count); } public shiftElements(start: number, count: number, offset: number): void { @@ -217,7 +214,7 @@ export class CircularList implements ICircularList { while (this._length > this._maxLength) { this._length--; this._startIndex++; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); } } } else { diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 2ad6f7351..dd6953bbe 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -29,7 +29,7 @@ import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/Buffe import { OptionsService } from 'common/services/OptionsService'; import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent, ScrollSource } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; -import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { UnicodeService } from 'common/services/UnicodeService'; import { CharsetService } from 'common/services/CharsetService'; @@ -59,16 +59,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - private readonly _onBinary = new EventEmitter(); - public readonly onBinary = this._onBinary.event; - private readonly _onData = new EventEmitter(); - public readonly onData = this._onData.event; - protected _onLineFeed = new EventEmitter(); - public readonly onLineFeed = this._onLineFeed.event; - private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public readonly onResize = this._onResize.event; - protected readonly _onWriteParsed = new EventEmitter(); - public readonly onWriteParsed = this._onWriteParsed.event; + public readonly onBinary = initEvent(); + public readonly onData = initEvent(); + public readonly onLineFeed = initEvent(); + public readonly onResize = initEvent<{ cols: number, rows: number }>(); + public readonly onWriteParsed = initEvent(); /** * Internally we track the source of the scroll but this is meaningless outside the library so @@ -122,13 +117,13 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Register input handler and handle/forward events this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); - this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); + this.register(forwardEvent(this._inputHandler.onLineFeed, this.onLineFeed)); this.register(this._inputHandler); // Setup listeners - this.register(forwardEvent(this._bufferService.onResize, this._onResize)); - this.register(forwardEvent(this.coreService.onData, this._onData)); - this.register(forwardEvent(this.coreService.onBinary, this._onBinary)); + this.register(forwardEvent(this._bufferService.onResize, this.onResize)); + this.register(forwardEvent(this.coreService.onData, this.onData)); + this.register(forwardEvent(this.coreService.onBinary, this.onBinary)); this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput())); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { @@ -142,7 +137,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup WriteBuffer this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); - this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); + this.register(forwardEvent(this._writeBuffer.onWriteParsed, this.onWriteParsed)); } public dispose(): void { diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 4684809f3..c0074bba3 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -14,11 +14,13 @@ export interface IEvent { } export interface IEventEmitter { - event: IEvent; fire(arg1: T, arg2: U): void; dispose(): void; } +export interface IEventWithEmitter extends IEventEmitter, IEvent { +} + export class EventEmitter implements IEventEmitter { private _listeners: IListener[] = []; private _event?: IEvent; @@ -64,6 +66,18 @@ export class EventEmitter implements IEventEmitter { } } +export function initEvent(): IEventWithEmitter { + const emitter = new EventEmitter(); + const event = emitter.event; + Object.defineProperty(event, 'fire', { + value: emitter.fire.bind(emitter) + }); + Object.defineProperty(event, 'dispose', { + value: emitter.dispose.bind(emitter) + }); + return event as any; +} + export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable { return from(e => to.fire(e)); } diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 7f0879efc..d4711c1f0 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -11,7 +11,7 @@ import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; @@ -132,33 +132,20 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; - private readonly _onRequestBell = new EventEmitter(); - public readonly onRequestBell = this._onRequestBell.event; - private readonly _onRequestRefreshRows = new EventEmitter(); - public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; - private readonly _onRequestReset = new EventEmitter(); - public readonly onRequestReset = this._onRequestReset.event; - private readonly _onRequestSendFocus = new EventEmitter(); - public readonly onRequestSendFocus = this._onRequestSendFocus.event; - private readonly _onRequestSyncScrollBar = new EventEmitter(); - public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; - private readonly _onRequestWindowsOptionsReport = new EventEmitter(); - public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; + public readonly onRequestBell = initEvent(); + public readonly onRequestRefreshRows = initEvent(); + public readonly onRequestReset = initEvent(); + public readonly onRequestSendFocus = initEvent(); + public readonly onRequestSyncScrollBar = initEvent(); + public readonly onRequestWindowsOptionsReport = initEvent(); - private readonly _onA11yChar = new EventEmitter(); - public readonly onA11yChar = this._onA11yChar.event; - private readonly _onA11yTab = new EventEmitter(); - public readonly onA11yTab = this._onA11yTab.event; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onLineFeed = new EventEmitter(); - public readonly onLineFeed = this._onLineFeed.event; - private readonly _onScroll = new EventEmitter(); - public readonly onScroll = this._onScroll.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onColor = new EventEmitter(); - public readonly onColor = this._onColor.event; + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); + public readonly onCursorMove = initEvent(); + public readonly onLineFeed = initEvent(); + public readonly onScroll = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onColor = initEvent(); private _parseStack: IParseStack = { paused: false, @@ -492,11 +479,11 @@ export class InputHandler extends Disposable implements IInputHandler { } if (this._activeBuffer.x !== cursorStartX || this._activeBuffer.y !== cursorStartY) { - this._onCursorMove.fire(); + this.onCursorMove.fire(); } // Refresh any dirty rows accumulated as part of parsing - this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); + this.onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); } public print(data: Uint32Array, start: number, end: number): void { @@ -535,7 +522,7 @@ export class InputHandler extends Disposable implements IInputHandler { } if (screenReaderMode) { - this._onA11yChar.fire(stringFromCodePoint(code)); + this.onA11yChar.fire(stringFromCodePoint(code)); } if (this._currentLinkId !== undefined) { this._oscLinkService.addLineToLink(this._currentLinkId, this._activeBuffer.ybase + this._activeBuffer.y); @@ -687,7 +674,7 @@ export class InputHandler extends Disposable implements IInputHandler { * and `ITerminalOptions.bellSound`. */ public bell(): boolean { - this._onRequestBell.fire(); + this.onRequestBell.fire(); return true; } @@ -719,7 +706,7 @@ export class InputHandler extends Disposable implements IInputHandler { } this._dirtyRowTracker.markDirty(this._activeBuffer.y); - this._onLineFeed.fire(); + this.onLineFeed.fire(); return true; } @@ -808,7 +795,7 @@ export class InputHandler extends Disposable implements IInputHandler { const originalX = this._activeBuffer.x; this._activeBuffer.x = this._activeBuffer.nextStop(); if (this._optionsService.rawOptions.screenReaderMode) { - this._onA11yTab.fire(this._activeBuffer.x - originalX); + this.onA11yTab.fire(this._activeBuffer.x - originalX); } return true; } @@ -1218,7 +1205,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0); this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0); // Force a scroll event to refresh viewport - this._onScroll.fire(0); + this.onScroll.fire(0); } break; } @@ -1849,7 +1836,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(132, this._bufferService.rows); - this._onRequestReset.fire(); + this.onRequestReset.fire(); } break; case 6: @@ -1868,7 +1855,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1890,7 +1877,7 @@ export class InputHandler extends Disposable implements IInputHandler { // focusin: ^[[I // focusout: ^[[O this._coreService.decPrivateModes.sendFocus = true; - this._onRequestSendFocus.fire(); + this.onRequestSendFocus.fire(); break; case 1005: // utf8 ext mode mouse - removed in #2507 this._logService.debug('DECSET 1005 not supported (see #2507)'); @@ -1917,8 +1904,8 @@ export class InputHandler extends Disposable implements IInputHandler { case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); this._coreService.isCursorInitialized = true; - this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._onRequestSyncScrollBar.fire(); + this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1); + this.onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = true; @@ -2087,7 +2074,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(80, this._bufferService.rows); - this._onRequestReset.fire(); + this.onRequestReset.fire(); } break; case 6: @@ -2106,7 +2093,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -2145,8 +2132,8 @@ export class InputHandler extends Disposable implements IInputHandler { this.restoreCursor(); } this._coreService.isCursorInitialized = true; - this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._onRequestSyncScrollBar.fire(); + this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1); + this.onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = false; @@ -2645,7 +2632,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public softReset(params: IParams): boolean { this._coreService.isCursorHidden = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); this._activeBuffer.scrollTop = 0; this._activeBuffer.scrollBottom = this._bufferService.rows - 1; this._curAttrData = DEFAULT_ATTR_DATA.clone(); @@ -2765,11 +2752,11 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (second !== 2) { - this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); + this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); } break; case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t - this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); + this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); break; case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t if (this._bufferService) { @@ -2859,7 +2846,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public setTitle(data: string): boolean { this._windowTitle = data; - this._onTitleChange.fire(data); + this.onTitleChange.fire(data); return true; } @@ -2901,7 +2888,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } if (event.length) { - this._onColor.fire(event); + this.onColor.fire(event); } return true; } @@ -2975,11 +2962,11 @@ export class InputHandler extends Disposable implements IInputHandler { for (let i = 0; i < slots.length; ++i, ++offset) { if (offset >= this._specialColors.length) break; if (slots[i] === '?') { - this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); + this.onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); } else { const color = parseColor(slots[i]); if (color) { - this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); + this.onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); } } } @@ -3040,7 +3027,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public restoreIndexedColor(data: string): boolean { if (!data) { - this._onColor.fire([{ type: ColorRequestType.RESTORE }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE }]); return true; } const event: IColorEvent = []; @@ -3054,7 +3041,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } if (event.length) { - this._onColor.fire(event); + this.onColor.fire(event); } return true; } @@ -3065,7 +3052,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color." */ public restoreFgColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); return true; } @@ -3075,7 +3062,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color." */ public restoreBgColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); return true; } @@ -3085,7 +3072,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color." */ public restoreCursorColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); return true; } @@ -3112,7 +3099,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadApplicationMode(): boolean { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); return true; } @@ -3124,7 +3111,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadNumericMode(): boolean { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); return true; } @@ -3238,7 +3225,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public fullReset(): boolean { this._parser.reset(); - this._onRequestReset.fire(); + this.onRequestReset.fire(); return true; } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index e302d3e4b..2ac70a5e8 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; @@ -17,8 +17,8 @@ export class MockBufferService implements IBufferService { public serviceBrand: any; public get buffer(): IBuffer { return this.buffers.active; } public buffers: IBufferSet = {} as any; - public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event; - public onScroll: IEvent = new EventEmitter().event; + public onResize: IEvent<{ cols: number, rows: number }> = initEvent<{ cols: number, rows: number }>(); + public onScroll: IEvent = initEvent(); public isUserScrolling: boolean = false; constructor( public cols: number, @@ -60,7 +60,7 @@ export class MockCoreMouseService implements ICoreMouseService { public addProtocol(name: string): void { } public reset(): void { } public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } - public onProtocolChange: IEvent = new EventEmitter().event; + public onProtocolChange: IEvent = initEvent(); public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } { throw new Error('Method not implemented.'); } @@ -92,9 +92,9 @@ export class MockCoreService implements ICoreService { sendFocus: false, wraparound: true }; - public onData: IEvent = new EventEmitter().event; - public onUserInput: IEvent = new EventEmitter().event; - public onBinary: IEvent = new EventEmitter().event; + public onData: IEvent = initEvent(); + public onUserInput: IEvent = initEvent(); + public onBinary: IEvent = initEvent(); public reset(): void { } public triggerDataEvent(data: string, wasUserInput?: boolean): void { } public triggerBinaryEvent(data: string): void { } @@ -113,7 +113,7 @@ export class MockOptionsService implements IOptionsService { public serviceBrand: any; public readonly rawOptions: Required = clone(DEFAULT_OPTIONS); public options: Required = this.rawOptions; - public onOptionChange: IEvent = new EventEmitter().event; + public onOptionChange: IEvent = initEvent(); constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { @@ -149,7 +149,7 @@ export class MockUnicodeService implements IUnicodeService { } public versions: string[] = []; public activeVersion: string = ''; - public onChange: IEvent = new EventEmitter().event; + public onChange: IEvent = initEvent(); public wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint); public getStringCellWidth(s: string): number { throw new Error('Method not implemented.'); @@ -159,8 +159,8 @@ export class MockUnicodeService implements IUnicodeService { export class MockDecorationService implements IDecorationService { public serviceBrand: any; public get decorations(): IterableIterator { return [].values(); } - public onDecorationRegistered = new EventEmitter().event; - public onDecorationRemoved = new EventEmitter().event; + public onDecorationRegistered = initEvent(); + public onDecorationRemoved = initEvent(); public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } public reset(): void { } public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index d44bb1977..6e6e93e7e 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -4,7 +4,7 @@ */ import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm'; -import { IEvent, IEventEmitter } from 'common/EventEmitter'; +import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; import { IParams } from 'common/parser/Types'; import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services'; @@ -71,12 +71,9 @@ export interface ICircularList { maxLength: number; isFull: boolean; - onDeleteEmitter: IEventEmitter; - onDelete: IEvent; - onInsertEmitter: IEventEmitter; - onInsert: IEvent; - onTrimEmitter: IEventEmitter; - onTrim: IEvent; + onDelete: IEventWithEmitter; + onInsert: IEventWithEmitter; + onTrim: IEventWithEmitter; get(index: number): T | undefined; set(index: number, value: T): void; diff --git a/src/common/buffer/Buffer.test.ts b/src/common/buffer/Buffer.test.ts index e5ea7f5ec..03297ba62 100644 --- a/src/common/buffer/Buffer.test.ts +++ b/src/common/buffer/Buffer.test.ts @@ -1071,7 +1071,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { @@ -1081,7 +1081,7 @@ describe('Buffer', () => { const marker = buffer.addMarker(0); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); }); @@ -1094,7 +1094,7 @@ describe('Buffer', () => { marker.onDispose(() => eventStack.push('disposed')); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); assert.deepEqual(eventStack, ['disposed']); diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index c8b0d1b25..ec3b9fcd5 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -463,12 +463,12 @@ export class Buffer implements IBuffer { let insertCountEmitted = 0; for (let i = insertEvents.length - 1; i >= 0; i--) { insertEvents[i].index += insertCountEmitted; - this.lines.onInsertEmitter.fire(insertEvents[i]); + this.lines.onInsert.fire(insertEvents[i]); insertCountEmitted += insertEvents[i].amount; } const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength); if (amountToTrim > 0) { - this.lines.onTrimEmitter.fire(amountToTrim); + this.lines.onTrim.fire(amountToTrim); } } } diff --git a/src/common/buffer/BufferReflow.ts b/src/common/buffer/BufferReflow.ts index ece9a96ed..e496cbbb6 100644 --- a/src/common/buffer/BufferReflow.ts +++ b/src/common/buffer/BufferReflow.ts @@ -118,7 +118,7 @@ export function reflowLargerCreateNewLayout(lines: CircularList, to const countToRemove = toRemove[++nextToRemoveIndex]; // Tell markers that there was a deletion - lines.onDeleteEmitter.fire({ + lines.onDelete.fire({ index: i - countRemovedSoFar, amount: countToRemove }); diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index 46fcb0979..1fa6fc275 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -6,7 +6,7 @@ import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { Disposable } from 'common/Lifecycle'; @@ -19,8 +19,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _alt!: Buffer; private _activeBuffer!: Buffer; - private readonly _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); - public readonly onBufferActivate = this._onBufferActivate.event; + public readonly onBufferActivate = this.register(initEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); /** * Create a new BufferSet for the given terminal. @@ -42,7 +41,7 @@ export class BufferSet extends Disposable implements IBufferSet { // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer this._alt = new Buffer(false, this._optionsService, this._bufferService); this._activeBuffer = this._normal; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -86,7 +85,7 @@ export class BufferSet extends Disposable implements IBufferSet { this._alt.clearAllMarkers(); this._alt.clear(); this._activeBuffer = this._normal; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -105,7 +104,7 @@ export class BufferSet extends Disposable implements IBufferSet { this._alt.x = this._normal.x; this._alt.y = this._normal.y; this._activeBuffer = this._alt; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._alt, inactiveBuffer: this._normal }); diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 56d64a726..958bd9edf 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IMarker } from 'common/Types'; @@ -15,8 +15,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } - private readonly _onDispose = new EventEmitter(); - public readonly onDispose = this._onDispose.event; + public readonly onDispose = initEvent(); constructor( public line: number @@ -31,7 +30,7 @@ export class Marker extends Disposable implements IMarker { this.isDisposed = true; this.line = -1; // Emit before super.dispose such that dispose listeners get a change to react - this._onDispose.fire(); + this.onDispose.fire(); super.dispose(); } } diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index bf72ae21c..b8efa54b2 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter'; declare const setTimeout: (handler: () => void, timeout?: number) => void; @@ -42,8 +42,7 @@ export class WriteBuffer { private _syncCalls = 0; private _didUserInput = false; - private readonly _onWriteParsed = new EventEmitter(); - public readonly onWriteParsed = this._onWriteParsed.event; + public readonly onWriteParsed = initEvent(); constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } @@ -237,6 +236,6 @@ export class WriteBuffer { this._pendingData = 0; this._bufferOffset = 0; } - this._onWriteParsed.fire(); + this.onWriteParsed.fire(); } } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index 033f5955d..a00962acd 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -5,20 +5,19 @@ import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm'; import { BufferApiView } from 'common/public/BufferApiView'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICoreTerminal } from 'common/Types'; export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; - private readonly _onBufferChange = new EventEmitter(); - public readonly onBufferChange = this._onBufferChange.event; + public readonly onBufferChange = initEvent(); constructor(private _core: ICoreTerminal) { this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate'); - this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active)); + this._core.buffers.onBufferActivate(() => this.onBufferChange.fire(this.active)); } public get active(): IBufferApi { if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 1bc93041c..3614fcfad 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,7 +6,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; -import { EventEmitter, IEventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types'; @@ -22,10 +22,8 @@ export class BufferService extends Disposable implements IBufferService { /** Whether the user is scrolling (locks the scroll position) */ public isUserScrolling: boolean = false; - private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public readonly onResize = this._onResize.event; - private readonly _onScroll = new EventEmitter(); - public readonly onScroll = this._onScroll.event; + public readonly onResize = initEvent<{ cols: number, rows: number }>(); + public readonly onScroll = initEvent(); public get buffer(): IBuffer { return this.buffers.active; } @@ -49,7 +47,7 @@ export class BufferService extends Disposable implements IBufferService { this.rows = rows; this.buffers.resize(cols, rows); this.buffers.setupTabStops(this.cols); - this._onResize.fire({ cols, rows }); + this.onResize.fire({ cols, rows }); } public reset(): void { @@ -118,7 +116,7 @@ export class BufferService extends Disposable implements IBufferService { buffer.ydisp = buffer.ybase; } - this._onScroll.fire(buffer.ydisp); + this.onScroll.fire(buffer.ydisp); } /** @@ -148,7 +146,7 @@ export class BufferService extends Disposable implements IBufferService { } if (!suppressScrollEvent) { - this._onScroll.fire(buffer.ydisp); + this.onScroll.fire(buffer.ydisp); } } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 8c2a24def..a5f1528a8 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -3,7 +3,7 @@ * @license MIT */ import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; /** @@ -172,8 +172,7 @@ export class CoreMouseService implements ICoreMouseService { private _activeEncoding: string = ''; private _lastEvent: ICoreMouseEvent | null = null; - private readonly _onProtocolChange = new EventEmitter(); - public readonly onProtocolChange = this._onProtocolChange.event; + public readonly onProtocolChange = initEvent(); constructor( @IBufferService private readonly _bufferService: IBufferService, @@ -207,7 +206,7 @@ export class CoreMouseService implements ICoreMouseService { throw new Error(`unknown protocol "${name}"`); } this._activeProtocol = name; - this._onProtocolChange.fire(this._protocols[name].events); + this.onProtocolChange.fire(this._protocols[name].events); } public get activeEncoding(): string { diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 9282197b3..35c759193 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -4,7 +4,7 @@ */ import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IDecPrivateModes, IModes } from 'common/Types'; import { clone } from 'common/Clone'; import { Disposable } from 'common/Lifecycle'; @@ -34,12 +34,9 @@ export class CoreService extends Disposable implements ICoreService { // Circular dependency, this must be unset or memory will leak after Terminal.dispose private _scrollToBottom: (() => void) | undefined; - private readonly _onData = this.register(new EventEmitter()); - public readonly onData = this._onData.event; - private readonly _onUserInput = this.register(new EventEmitter()); - public readonly onUserInput = this._onUserInput.event; - private readonly _onBinary = this.register(new EventEmitter()); - public readonly onBinary = this._onBinary.event; + public readonly onData = this.register(initEvent()); + public readonly onUserInput = this.register(initEvent()); + public readonly onBinary = this.register(initEvent()); constructor( // TODO: Move this into a service @@ -74,12 +71,12 @@ export class CoreService extends Disposable implements ICoreService { // Fire onUserInput so listeners can react as well (eg. clear selection) if (wasUserInput) { - this._onUserInput.fire(); + this.onUserInput.fire(); } // Fire onData API this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); - this._onData.fire(data); + this.onData.fire(data); } public triggerBinaryEvent(data: string): void { @@ -87,6 +84,6 @@ export class CoreService extends Disposable implements ICoreService { return; } this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); - this._onBinary.fire(data); + this.onBinary.fire(data); } } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 522b04dea..5efeb77d8 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -4,7 +4,7 @@ */ import { css } from 'common/Color'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { SortedList } from 'common/SortedList'; @@ -25,10 +25,8 @@ export class DecorationService extends Disposable implements IDecorationService */ private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); - private readonly _onDecorationRegistered = this.register(new EventEmitter()); - public readonly onDecorationRegistered = this._onDecorationRegistered.event; - private readonly _onDecorationRemoved = this.register(new EventEmitter()); - public readonly onDecorationRemoved = this._onDecorationRemoved.event; + public readonly onDecorationRegistered = this.register(initEvent()); + public readonly onDecorationRemoved = this.register(initEvent()); public get decorations(): IterableIterator { return this._decorations.values(); } @@ -42,13 +40,13 @@ export class DecorationService extends Disposable implements IDecorationService decoration.onDispose(() => { if (decoration) { if (this._decorations.delete(decoration)) { - this._onDecorationRemoved.fire(decoration); + this.onDecorationRemoved.fire(decoration); } markerDispose.dispose(); } }); this._decorations.insert(decoration); - this._onDecorationRegistered.fire(decoration); + this.onDecorationRegistered.fire(decoration); } return decoration; } @@ -84,7 +82,7 @@ export class DecorationService extends Disposable implements IDecorationService public dispose(): void { for (const d of this._decorations.values()) { - this._onDecorationRemoved.fire(d); + this.onDecorationRemoved.fire(d); } this.reset(); } @@ -95,10 +93,8 @@ class Decoration extends Disposable implements IInternalDecoration { public element: HTMLElement | undefined; public isDisposed: boolean = false; - public readonly onRenderEmitter = this.register(new EventEmitter()); - public readonly onRender = this.onRenderEmitter.event; - private readonly _onDispose = this.register(new EventEmitter()); - public readonly onDispose = this._onDispose.event; + public readonly onRender = this.register(initEvent()); + public readonly onDispose = this.register(initEvent()); private _cachedBg: IColor | undefined | null = null; public get backgroundColorRGB(): IColor | undefined { @@ -139,7 +135,7 @@ class Decoration extends Disposable implements IInternalDecoration { return; } this._isDisposed = true; - this._onDispose.fire(); + this.onDispose.fire(); super.dispose(); } } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 33aa3ee23..16beddbab 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -4,9 +4,9 @@ */ import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { CursorStyle } from 'common/Types'; +import { initEvent } from 'common/EventEmitter'; export const DEFAULT_OPTIONS: Readonly> = { cols: 80, @@ -57,8 +57,7 @@ export class OptionsService implements IOptionsService { public readonly rawOptions: Required; public options: Required; - private readonly _onOptionChange = new EventEmitter(); - public readonly onOptionChange = this._onOptionChange.event; + public readonly onOptionChange = initEvent(); constructor(options: Partial) { // set the default value of each option @@ -97,7 +96,7 @@ export class OptionsService implements IOptionsService { // Don't fire an option change event if they didn't change if (this.rawOptions[propName] !== value) { this.rawOptions[propName] = value; - this._onOptionChange.fire(propName); + this.onOptionChange.fire(propName); } }; diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e2b517cde..c47b1c2a9 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent, IEventEmitter } from 'common/EventEmitter'; +import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, IOscLinkData } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; @@ -318,5 +318,5 @@ export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; readonly backgroundColorRGB: IColor | undefined; readonly foregroundColorRGB: IColor | undefined; - readonly onRenderEmitter: IEventEmitter; + readonly onRender: IEventWithEmitter; } diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 239f4d622..7306db93f 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -3,7 +3,7 @@ * @license MIT */ import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { UnicodeV6 } from 'common/input/UnicodeV6'; @@ -14,8 +14,7 @@ export class UnicodeService implements IUnicodeService { private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - private readonly _onChange = new EventEmitter(); - public readonly onChange = this._onChange.event; + public readonly onChange = initEvent(); constructor() { const defaultProvider = new UnicodeV6(); @@ -38,7 +37,7 @@ export class UnicodeService implements IUnicodeService { } this._active = version; this._activeProvider = this._providers[version]; - this._onChange.fire(version); + this.onChange.fire(version); } public register(provider: IUnicodeVersionProvider): void { diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 639988ebd..45b8aa749 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -24,7 +24,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBuffer } from 'common/buffer/Types'; import { CoreTerminal } from 'common/CoreTerminal'; -import { EventEmitter, forwardEvent, IEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent, IEvent, initEvent } from 'common/EventEmitter'; import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; import { IMarker, ITerminalOptions, ScrollSource } from 'common/Types'; @@ -32,16 +32,11 @@ export class Terminal extends CoreTerminal { // TODO: We should remove options once components adopt optionsService public get options(): Required { return this.optionsService.options; } - private readonly _onBell = new EventEmitter(); - public readonly onBell = this._onBell.event; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onA11yCharEmitter = new EventEmitter(); - public readonly onA11yChar = this._onA11yCharEmitter.event; - private readonly _onA11yTabEmitter = new EventEmitter(); - public readonly onA11yTab = this._onA11yTabEmitter.event; + public readonly onBell = initEvent(); + public readonly onCursorMove = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); /** * Creates a new `Terminal` object. @@ -65,10 +60,10 @@ export class Terminal extends CoreTerminal { // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestReset(() => this.reset())); - this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); - this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); - this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); - this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); + this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab)); } public dispose(): void { @@ -111,7 +106,7 @@ export class Terminal extends CoreTerminal { } public bell(): void { - this._onBell.fire(); + this.onBell.fire(); } /** From 03fcf60aaf71bb2c5c0f830d2cef3303f5617d5f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:55:01 -0700 Subject: [PATCH 26/56] Docs --- src/common/EventEmitter.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index c0074bba3..979a64644 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -66,6 +66,19 @@ export class EventEmitter implements IEventEmitter { } } +/** + * Creates an object that implements both the {@link IEvent} and {@link IEmitter} interfaces. This + * allows more concise instantiation. The idea is to internally use the combined + * {@link IEventWithEmitter} interface and only expose {@link IEvent} externally. + * + * @example + * ```ts + * public readonly onFoo = initEvent(); + * // ... + * onFoo(e => handle(e)); + * onFoo.fire('bar'); + * ``` + */ export function initEvent(): IEventWithEmitter { const emitter = new EventEmitter(); const event = emitter.event; From 2e8b793ba0329dc84cda871155288a89fc4cb95d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:56:56 -0700 Subject: [PATCH 27/56] Format files --- addons/xterm-addon-webgl/src/WebglAddon.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 548be30c3..b26c565c3 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -20,7 +20,7 @@ export class WebglAddon implements ITerminalAddon { constructor( private _preserveDrawingBuffer?: boolean - ) {} + ) { } public activate(terminal: Terminal): void { if (!terminal.element) { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 7a4872428..8d8b24f71 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -54,8 +54,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _contextRestorationTimeout: number | undefined; public readonly onChangeTextureAtlas = initEvent(); - public readonly onRequestRedraw = initEvent(); - public readonly onContextLoss = initEvent(); + public readonly onRequestRedraw = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _terminal: Terminal, @@ -419,9 +419,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg && - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg && + this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) { continue; } From 2a602ef231abcbb431d240679a6d02f44a99c83b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 10:20:37 -0700 Subject: [PATCH 28/56] Ensure texture atlas comparison uses rgba not object Fixes #4128 --- addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts | 4 ++-- addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts index b0151e30d..fa4be1e53 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts @@ -45,8 +45,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.allowTransparency === b.allowTransparency && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground.rgba === b.colors.foreground.rgba && + a.colors.background.rgba === b.colors.background.rgba; } export function is256Color(colorCode: number): boolean { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 83f82fa76..dc503d756 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -70,8 +70,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.scaledCharHeight === b.scaledCharHeight && a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors && a.minimumContrastRatio === b.minimumContrastRatio && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground.rgba === b.colors.foreground.rgba && + a.colors.background.rgba === b.colors.background.rgba; } export function is256Color(colorCode: number): boolean { From 6fcc496547f7109d9aafe4f8e7362d58fa0885fd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:36:16 -0700 Subject: [PATCH 29/56] Move cache utils to core shared/ folder Part of #4065 --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 5 +- addons/xterm-addon-canvas/src/CanvasAddon.ts | 2 +- .../xterm-addon-canvas/src/CanvasRenderer.ts | 12 +++-- .../src/CursorRenderLayer.ts | 5 +- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 7 +-- .../src/SelectionRenderLayer.ts | 5 +- .../xterm-addon-canvas/src/TextRenderLayer.ts | 5 +- .../src/atlas/CharAtlasCache.ts | 25 ++++----- .../src/atlas/CharAtlasUtils.ts | 54 ------------------- .../src/atlas/DynamicCharAtlas.ts | 12 ++--- .../xterm-addon-canvas/src/atlas/Types.d.ts | 12 ----- .../src/atlas/CharAtlasCache.ts | 4 +- addons/xterm-addon-webgl/src/atlas/Types.d.ts | 22 -------- .../src/atlas/WebglCharAtlas.ts | 12 ++--- .../src/renderLayer/LinkRenderLayer.ts | 2 +- src/browser/ColorManager.ts | 10 ++-- .../renderer/shared}/CharAtlasUtils.ts | 13 ++--- src/browser/renderer/shared/README.md | 1 + src/browser/renderer/shared/Types.d.ts | 26 +++++++++ src/common/Color.ts | 5 ++ 20 files changed, 86 insertions(+), 153 deletions(-) delete mode 100644 addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts rename {addons/xterm-addon-webgl/src/atlas => src/browser/renderer/shared}/CharAtlasUtils.ts (92%) create mode 100644 src/browser/renderer/shared/README.md create mode 100644 src/browser/renderer/shared/Types.d.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index f37ea1b85..641a5ca42 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -20,6 +20,7 @@ import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/ import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; +import { Terminal } from 'xterm'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -53,12 +54,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { public get canvas(): HTMLCanvasElement { return this._canvas; } constructor( + private readonly _terminal: Terminal, private _container: HTMLElement, id: string, zIndex: number, private _alpha: boolean, protected _colors: IColorSet, - private _rendererId: number, protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, protected readonly _decorationService: IDecorationService, @@ -127,7 +128,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(this._optionsService.rawOptions, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 9fca00a64..372852b0e 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -29,7 +29,7 @@ export class CanvasAddon implements ITerminalAddon { const colors: IColorSet = (terminal as any)._core._colorManager.colors; const screenElement: HTMLElement = (terminal as any)._core.screenElement; const linkifier = (terminal as any)._core.linkifier2; - this._renderer = new CanvasRenderer(colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); + this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index fd9629c06..77c2177e8 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -16,6 +16,7 @@ import { IBufferService, IOptionsService, IDecorationService, ICoreService } fro import { removeTerminalFromCache } from './atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; +import { Terminal } from 'xterm'; let nextRendererId = 1; @@ -31,6 +32,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public readonly onRequestRedraw = this._onRequestRedraw.event; constructor( + private readonly _terminal: Terminal, private _colors: IColorSet, private readonly _screenElement: HTMLElement, linkifier2: ILinkifier2, @@ -45,10 +47,10 @@ export class CanvasRenderer extends Disposable implements IRenderer { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ - new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), - new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), - new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), - new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) + new TextRenderLayer(this._terminal, this._screenElement, 0, this._colors, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), + new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._colors, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), + new LinkRenderLayer(this._terminal, this._screenElement, 2, this._colors, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), + new CursorRenderLayer(this._terminal, this._screenElement, 3, this._colors, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) ]; this.dimensions = { scaledCharWidth: 0, @@ -77,7 +79,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { l.dispose(); } super.dispose(); - removeTerminalFromCache(this._id); + removeTerminalFromCache(this._terminal); } public onDevicePixelRatioChange(): void { diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 61e2d9341..bddac1958 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -11,6 +11,7 @@ import { IColorSet } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; +import { Terminal } from 'xterm'; interface ICursorState { x: number; @@ -32,10 +33,10 @@ export class CursorRenderLayer extends BaseRenderLayer { private _cell: ICellData = new CellData(); constructor( + terminal: Terminal, container: HTMLElement, zIndex: number, colors: IColorSet, - rendererId: number, private readonly _onRequestRedraw: IEventEmitter, bufferService: IBufferService, optionsService: IOptionsService, @@ -43,7 +44,7 @@ export class CursorRenderLayer extends BaseRenderLayer { coreBrowserService: ICoreBrowserService, decorationService: IDecorationService ) { - super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'cursor', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); this._state = { x: 0, y: 0, diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index c07329fd6..5e1e70620 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -7,25 +7,26 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; -import { is256Color } from './atlas/CharAtlasUtils'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; +import { Terminal } from 'xterm'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; constructor( + terminal: Terminal, container: HTMLElement, zIndex: number, colors: IColorSet, - rendererId: number, linkifier2: ILinkifier2, bufferService: IBufferService, optionsService: IOptionsService, decorationService: IDecorationService, coreBrowserService: ICoreBrowserService ) { - super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'link', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index e90007b72..dc506bad4 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -8,6 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { IColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICoreBrowserService } from 'browser/services/Services'; +import { Terminal } from 'xterm'; interface ISelectionState { start?: [number, number]; @@ -20,16 +21,16 @@ export class SelectionRenderLayer extends BaseRenderLayer { private _state!: ISelectionState; constructor( + terminal: Terminal, container: HTMLElement, zIndex: number, colors: IColorSet, - rendererId: number, bufferService: IBufferService, coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, optionsService: IOptionsService ) { - super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'selection', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); this._clearState(); } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 95f22fce7..eb004c9a8 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -15,6 +15,7 @@ import { IOptionsService, IBufferService, IDecorationService } from 'common/serv import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { color, css } from 'common/Color'; +import { Terminal } from 'xterm'; /** * This CharData looks like a null character, which will forc a clear and render @@ -31,18 +32,18 @@ export class TextRenderLayer extends BaseRenderLayer { private _workCell = new CellData(); constructor( + terminal: Terminal, container: HTMLElement, zIndex: number, colors: IColorSet, alpha: boolean, - rendererId: number, bufferService: IBufferService, optionsService: IOptionsService, private readonly _characterJoinerService: ICharacterJoinerService, decorationService: IDecorationService, coreBrowserService: ICoreBrowserService ) { - super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'text', zIndex, alpha, colors, bufferService, optionsService, decorationService, coreBrowserService); this._state = new GridCache(); } diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts index d9349286f..73d66f64f 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts @@ -3,19 +3,19 @@ * @license MIT */ -import { generateConfig, configEquals } from './CharAtlasUtils'; +import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; import { BaseCharAtlas } from './BaseCharAtlas'; import { DynamicCharAtlas } from './DynamicCharAtlas'; -import { ICharAtlasConfig } from './Types'; import { IColorSet } from 'browser/Types'; -import { ITerminalOptions } from 'xterm'; +import { Terminal } from 'xterm'; +import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. - ownedBy: number[]; + ownedBy: Terminal[]; } const charAtlasCache: ICharAtlasCacheEntry[] = []; @@ -25,19 +25,20 @@ const charAtlasCache: ICharAtlasCacheEntry[] = []; * one that is in use by another terminal. */ export function acquireCharAtlas( - options: Required, - rendererId: number, + terminal: Terminal, colors: IColorSet, + scaledCellWidth: number, + scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, devicePixelRatio: number ): BaseCharAtlas { - const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, options, colors, devicePixelRatio); + const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the renderer already owns this config for (let i = 0; i < charAtlasCache.length; i++) { const entry = charAtlasCache[i]; - const ownedByIndex = entry.ownedBy.indexOf(rendererId); + const ownedByIndex = entry.ownedBy.indexOf(terminal); if (ownedByIndex >= 0) { if (configEquals(entry.config, newConfig)) { return entry.atlas; @@ -58,7 +59,7 @@ export function acquireCharAtlas( const entry = charAtlasCache[i]; if (configEquals(entry.config, newConfig)) { // Add the renderer to the cache entry and return - entry.ownedBy.push(rendererId); + entry.ownedBy.push(terminal); return entry.atlas; } } @@ -69,7 +70,7 @@ export function acquireCharAtlas( newConfig ), config: newConfig, - ownedBy: [rendererId] + ownedBy: [terminal] }; charAtlasCache.push(newEntry); return newEntry.atlas; @@ -78,9 +79,9 @@ export function acquireCharAtlas( /** * Removes a terminal reference from the cache, allowing its memory to be freed. */ -export function removeTerminalFromCache(rendererId: number): void { +export function removeTerminalFromCache(terminal: Terminal): void { for (let i = 0; i < charAtlasCache.length; i++) { - const index = charAtlasCache[i].ownedBy.indexOf(rendererId); + const index = charAtlasCache[i].ownedBy.indexOf(terminal); if (index !== -1) { if (charAtlasCache[i].ownedBy.length === 1) { // Remove the cache entry if it's the only renderer diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts deleted file mode 100644 index b0151e30d..000000000 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ICharAtlasConfig } from './Types'; -import { DEFAULT_COLOR } from 'common/buffer/Constants'; -import { IColorSet, IPartialColorSet } from 'browser/Types'; -import { ITerminalOptions } from 'xterm'; - -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: Required, colors: IColorSet, devicePixelRatio: number): ICharAtlasConfig { - // null out some fields that don't matter - const clonedColors: IPartialColorSet = { - foreground: colors.foreground, - background: colors.background, - cursor: undefined, - cursorAccent: undefined, - selectionBackground: undefined, - ansi: colors.ansi.slice() - }; - return { - devicePixelRatio, - scaledCharWidth, - scaledCharHeight, - fontFamily: options.fontFamily, - fontSize: options.fontSize, - fontWeight: options.fontWeight, - fontWeightBold: options.fontWeightBold, - allowTransparency: options.allowTransparency, - colors: clonedColors - }; -} - -export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { - for (let i = 0; i < a.colors.ansi.length; i++) { - if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) { - return false; - } - } - return a.devicePixelRatio === b.devicePixelRatio && - a.fontFamily === b.fontFamily && - a.fontSize === b.fontSize && - a.fontWeight === b.fontWeight && - a.fontWeightBold === b.fontWeightBold && - a.allowTransparency === b.allowTransparency && - a.scaledCharWidth === b.scaledCharWidth && - a.scaledCharHeight === b.scaledCharHeight && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; -} - -export function is256Color(colorCode: number): boolean { - return colorCode < DEFAULT_COLOR; -} diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index 3098538dd..49d384b75 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -4,25 +4,21 @@ */ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants'; -import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; +import { IGlyphIdentifier } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'common/Types'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; -import { color } from 'common/Color'; +import { color, NULL_COLOR } from 'common/Color'; +import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. const TEXTURE_WIDTH = 1024; const TEXTURE_HEIGHT = 1024; -const TRANSPARENT_COLOR = { - css: 'rgba(0, 0, 0, 0)', - rgba: 0 -}; - // Drawing to the cache is expensive: If we have to draw more than this number of glyphs to the // cache in a single frame, give up on trying to cache anything else, and try to finish the current // frame ASAP. @@ -223,7 +219,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. - return TRANSPARENT_COLOR; + return NULL_COLOR; } let result: IColor; if (glyph.bg === INVERTED_DEFAULT_COLOR) { diff --git a/addons/xterm-addon-canvas/src/atlas/Types.d.ts b/addons/xterm-addon-canvas/src/atlas/Types.d.ts index d8bc54c19..cde8abec3 100644 --- a/addons/xterm-addon-canvas/src/atlas/Types.d.ts +++ b/addons/xterm-addon-canvas/src/atlas/Types.d.ts @@ -15,15 +15,3 @@ export interface IGlyphIdentifier { dim: boolean; italic: boolean; } - -export interface ICharAtlasConfig { - devicePixelRatio: number; - fontSize: number; - fontFamily: string; - fontWeight: FontWeight; - fontWeightBold: FontWeight; - scaledCharWidth: number; - scaledCharHeight: number; - allowTransparency: boolean; - colors: IPartialColorSet; -} diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts index 893362c86..e70a49954 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { generateConfig, configEquals } from './CharAtlasUtils'; import { WebglCharAtlas } from './WebglCharAtlas'; -import { ICharAtlasConfig } from './Types'; import { Terminal } from 'xterm'; import { IColorSet, ITerminal } from 'browser/Types'; +import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; +import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; interface ICharAtlasCacheEntry { atlas: WebglCharAtlas; diff --git a/addons/xterm-addon-webgl/src/atlas/Types.d.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts index 8d2870cde..fd5b596cb 100644 --- a/addons/xterm-addon-webgl/src/atlas/Types.d.ts +++ b/addons/xterm-addon-webgl/src/atlas/Types.d.ts @@ -3,9 +3,6 @@ * @license MIT */ -import { FontWeight } from 'xterm'; -import { IColorSet } from 'browser/Types'; - export interface IGlyphIdentifier { chars: string; code: number; @@ -15,22 +12,3 @@ export interface IGlyphIdentifier { dim: boolean; italic: boolean; } - -export interface ICharAtlasConfig { - customGlyphs: boolean; - devicePixelRatio: number; - letterSpacing: number; - lineHeight: number; - fontSize: number; - fontFamily: string; - fontWeight: FontWeight; - fontWeightBold: FontWeight; - scaledCellWidth: number; - scaledCellHeight: number; - scaledCharWidth: number; - scaledCharHeight: number; - allowTransparency: boolean; - drawBoldTextInBrightColors: boolean; - minimumContrastRatio: number; - colors: IColorSet; -} diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index dee1e02c5..cb72f3bc8 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/Constants'; import { IRasterizedGlyph, IBoundingBox } from '../Types'; import { DEFAULT_COLOR, Attributes, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; @@ -11,12 +10,13 @@ import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'common/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; -import { color, rgba } from 'common/Color'; +import { color, NULL_COLOR, rgba } from 'common/Color'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; +import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -29,12 +29,6 @@ const TEXTURE_HEIGHT = 1024; * this prevent juggling multiple textures in the GL context. */ const TEXTURE_CAPACITY = Math.floor(TEXTURE_HEIGHT * 0.8); - -const TRANSPARENT_COLOR = { - css: 'rgba(0, 0, 0, 0)', - rgba: 0 -}; - /** * A shared object which is used to draw nothing for a particular cell. */ @@ -202,7 +196,7 @@ export class WebglCharAtlas implements IDisposable { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. - return TRANSPARENT_COLOR; + return NULL_COLOR; } let result: IColor; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index e1ff08d81..9b9f8e2ad 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -6,10 +6,10 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; -import { is256Color } from '../atlas/CharAtlasUtils'; import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { ICoreBrowserService } from 'browser/services/Services'; +import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index a22a423fd..0eb726a65 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,7 +5,7 @@ import { IColorManager, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { channels, color, css } from 'common/Color'; +import { channels, color, css, NULL_COLOR } from 'common/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; import { ColorIndex, IColor } from 'common/Types'; @@ -124,12 +124,8 @@ export class ColorManager implements IColorManager { this.colors.selectionBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionBackgroundTransparent); this.colors.selectionInactiveBackgroundTransparent = this._parseColor(theme.selectionInactiveBackground, this.colors.selectionBackgroundTransparent); this.colors.selectionInactiveBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionInactiveBackgroundTransparent); - const nullColor: IColor = { - css: '', - rgba: 0 - }; - this.colors.selectionForeground = theme.selectionForeground ? this._parseColor(theme.selectionForeground, nullColor) : undefined; - if (this.colors.selectionForeground === nullColor) { + this.colors.selectionForeground = theme.selectionForeground ? this._parseColor(theme.selectionForeground, NULL_COLOR) : undefined; + if (this.colors.selectionForeground === NULL_COLOR) { this.colors.selectionForeground = undefined; } diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/src/browser/renderer/shared/CharAtlasUtils.ts similarity index 92% rename from addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts rename to src/browser/renderer/shared/CharAtlasUtils.ts index 83f82fa76..58c207e88 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/shared/CharAtlasUtils.ts @@ -5,14 +5,9 @@ import { ICharAtlasConfig } from './Types'; import { Attributes } from 'common/buffer/Constants'; -import { Terminal, FontWeight } from 'xterm'; +import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IColor } from 'common/Types'; - -const NULL_COLOR: IColor = { - css: '', - rgba: 0 -}; +import { NULL_COLOR } from 'common/Color'; export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet, devicePixelRatio: number): ICharAtlasConfig { // null out some fields that don't matter @@ -70,8 +65,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.scaledCharHeight === b.scaledCharHeight && a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors && a.minimumContrastRatio === b.minimumContrastRatio && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground.rgba === b.colors.foreground.rgba && + a.colors.background.rgba === b.colors.background.rgba; } export function is256Color(colorCode: number): boolean { diff --git a/src/browser/renderer/shared/README.md b/src/browser/renderer/shared/README.md new file mode 100644 index 000000000..580842356 --- /dev/null +++ b/src/browser/renderer/shared/README.md @@ -0,0 +1 @@ +This folder contains files that are shared between the renderer addons, but not necessarily bundled into the `xterm` module. diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts new file mode 100644 index 000000000..7559aa8f0 --- /dev/null +++ b/src/browser/renderer/shared/Types.d.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { FontWeight } from 'xterm'; +import { IColorSet } from 'browser/Types'; + +export interface ICharAtlasConfig { + customGlyphs: boolean; + devicePixelRatio: number; + letterSpacing: number; + lineHeight: number; + fontSize: number; + fontFamily: string; + fontWeight: FontWeight; + fontWeightBold: FontWeight; + scaledCellWidth: number; + scaledCellHeight: number; + scaledCharWidth: number; + scaledCharHeight: number; + allowTransparency: boolean; + drawBoldTextInBrightColors: boolean; + minimumContrastRatio: number; + colors: IColorSet; +} diff --git a/src/common/Color.ts b/src/common/Color.ts index 6e70671b5..d009a9788 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -11,6 +11,11 @@ let $g = 0; let $b = 0; let $a = 0; +export const NULL_COLOR: IColor = { + css: '#00000000', + rgba: 0 +}; + /** * Helper functions where the source type is "channels" (individual color channels as numbers). */ From c5f9eacbb11aacb51472f9b9fc9dd035c394e857 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:41:16 -0700 Subject: [PATCH 30/56] Move other render parts to shared folder --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 8 +-- .../xterm-addon-canvas/src/CanvasRenderer.ts | 4 +- .../src/CursorRenderLayer.ts | 2 +- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 4 +- .../src/SelectionRenderLayer.ts | 2 +- .../xterm-addon-canvas/src/TextRenderLayer.ts | 2 +- .../src/atlas/DynamicCharAtlas.ts | 4 +- .../src/RectangleRenderer.ts | 4 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 +- .../src/atlas/WebglCharAtlas.ts | 6 +-- .../src/renderLayer/BaseRenderLayer.ts | 4 +- .../src/renderLayer/LinkRenderLayer.ts | 4 +- src/browser/Terminal.ts | 2 +- src/browser/TestUtils.test.ts | 2 +- src/browser/renderer/dom/DomRenderer.ts | 4 +- .../renderer/dom/DomRendererRowFactory.ts | 4 +- .../renderer/{ => shared}/Constants.ts | 0 .../renderer/{ => shared}/CustomGlyphs.ts | 2 +- .../{ => shared}/DevicePixelObserver.ts | 0 .../renderer/{ => shared}/RendererUtils.ts | 0 src/browser/renderer/shared/Types.d.ts | 50 +++++++++++++++++++ src/browser/services/RenderService.ts | 2 +- 22 files changed, 82 insertions(+), 32 deletions(-) rename src/browser/renderer/{ => shared}/Constants.ts (100%) rename src/browser/renderer/{ => shared}/CustomGlyphs.ts (99%) rename src/browser/renderer/{ => shared}/DevicePixelObserver.ts (100%) rename src/browser/renderer/{ => shared}/RendererUtils.ts (100%) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 641a5ca42..0a977d9cc 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { IRenderLayer } from './Types'; import { ICellData, IColor } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from './atlas/Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { BaseCharAtlas } from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -16,10 +16,10 @@ import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICoreBrowserService } from 'browser/services/Services'; -import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; -import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; +import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { Terminal } from 'xterm'; export abstract class BaseRenderLayer implements IRenderLayer { diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 77c2177e8..0a00f6f80 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -6,7 +6,7 @@ import { TextRenderLayer } from './TextRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; -import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IRenderLayer } from './Types'; import { LinkRenderLayer } from './LinkRenderLayer'; import { Disposable } from 'common/Lifecycle'; @@ -15,7 +15,7 @@ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from ' import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from './atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; +import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; import { Terminal } from 'xterm'; let nextRendererId = 1; diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index bddac1958..78dc54322 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index 5e1e70620..db622b3fe 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index dc506bad4..6a803342a 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { IColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index eb004c9a8..b20765d86 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { CharData, ICellData } from 'common/Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index 49d384b75..c06842a7c 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -3,14 +3,14 @@ * @license MIT */ -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { IGlyphIdentifier } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'common/Types'; -import { throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { color, NULL_COLOR } from 'common/Color'; import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index dccc7f6a9..ea7c3836b 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -9,10 +9,10 @@ import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColor } from 'common/Types'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { DIM_OPACITY } from 'browser/renderer/Constants'; +import { DIM_OPACITY } from 'browser/renderer/shared/Constants'; const enum VertexAttribLocations { POSITION = 0, diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 58fbb6ed3..34f247eb1 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -15,8 +15,8 @@ import { Disposable } from 'common/Lifecycle'; import { Attributes, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; +import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index cb72f3bc8..b9a4088d3 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/Constants'; +import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { IRasterizedGlyph, IBoundingBox } from '../Types'; import { DEFAULT_COLOR, Attributes, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; import { throwIfFalsy } from '../WebglUtils'; @@ -11,8 +11,8 @@ import { IColor } from 'common/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; import { color, NULL_COLOR, rgba } from 'common/Color'; -import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; -import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; +import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; +import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/shared/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index e9c0cf413..2cba49dd4 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -7,9 +7,9 @@ import { IRenderLayer } from './Types'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { TEXT_BASELINE } from 'browser/renderer/Constants'; +import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { CellData } from 'common/buffer/CellData'; import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; import { throwIfFalsy } from '../WebglUtils'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 9b9f8e2ad..dbf2fdcff 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -5,9 +5,9 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9c10fc16e..be1f67778 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -22,7 +22,7 @@ */ import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IViewport, ILinkifier2, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; -import { IRenderer } from 'browser/renderer/Types'; +import { IRenderer } from 'browser/renderer/shared/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 0b5e00c17..1717c2048 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -6,7 +6,7 @@ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 223e24705..77e977adf 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index b6ee7bf17..bce72ec35 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -4,7 +4,7 @@ */ import { IBufferLine, ICellData, IColor } from 'common/Types'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -12,7 +12,7 @@ import { color, rgba } from 'common/Color'; import { IColorSet } from 'browser/Types'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; -import { excludeFromContrastRatioDemands } from 'browser/renderer/RendererUtils'; +import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils'; import { AttributeData } from 'common/buffer/AttributeData'; export const BOLD_CLASS = 'xterm-bold'; diff --git a/src/browser/renderer/Constants.ts b/src/browser/renderer/shared/Constants.ts similarity index 100% rename from src/browser/renderer/Constants.ts rename to src/browser/renderer/shared/Constants.ts diff --git a/src/browser/renderer/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts similarity index 99% rename from src/browser/renderer/CustomGlyphs.ts rename to src/browser/renderer/shared/CustomGlyphs.ts index 32256cf4b..b87256855 100644 --- a/src/browser/renderer/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; interface IBlockVector { x: number; diff --git a/src/browser/renderer/DevicePixelObserver.ts b/src/browser/renderer/shared/DevicePixelObserver.ts similarity index 100% rename from src/browser/renderer/DevicePixelObserver.ts rename to src/browser/renderer/shared/DevicePixelObserver.ts diff --git a/src/browser/renderer/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts similarity index 100% rename from src/browser/renderer/RendererUtils.ts rename to src/browser/renderer/shared/RendererUtils.ts diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 7559aa8f0..ccfcae433 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -5,6 +5,8 @@ import { FontWeight } from 'xterm'; import { IColorSet } from 'browser/Types'; +import { IDisposable } from 'common/Types'; +import { IEvent } from 'common/EventEmitter'; export interface ICharAtlasConfig { customGlyphs: boolean; @@ -24,3 +26,51 @@ export interface ICharAtlasConfig { minimumContrastRatio: number; colors: IColorSet; } + +export interface IRenderDimensions { + scaledCharWidth: number; + scaledCharHeight: number; + scaledCellWidth: number; + scaledCellHeight: number; + scaledCharLeft: number; + scaledCharTop: number; + scaledCanvasWidth: number; + scaledCanvasHeight: number; + canvasWidth: number; + canvasHeight: number; + actualCellWidth: number; + actualCellHeight: number; +} + +export interface IRequestRedrawEvent { + start: number; + end: number; +} + +/** + * Note that IRenderer implementations should emit the refresh event after + * rendering rows to the screen. + */ +export interface IRenderer extends IDisposable { + readonly dimensions: IRenderDimensions; + + /** + * Fires when the renderer is requesting to be redrawn on the next animation + * frame but is _not_ a result of content changing (eg. selection changes). + */ + readonly onRequestRedraw: IEvent; + + dispose(): void; + setColors(colors: IColorSet): void; + onDevicePixelRatioChange(): void; + onResize(cols: number, rows: number): void; + onCharSizeChanged(): void; + onBlur(): void; + onFocus(): void; + onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + onCursorMove(): void; + onOptionsChanged(): void; + clear(): void; + renderRows(start: number, end: number): void; + clearTextureAtlas?(): void; +} diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 66dbb7aa2..c81357cd9 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderer, IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions } from 'browser/renderer/shared/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; From d669c6e9d62f6661c84cd2c6c9806c084f1b4e29 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 18:03:43 -0700 Subject: [PATCH 31/56] Fix remaining references --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 2 +- .../src/renderLayer/CursorRenderLayer.ts | 2 +- .../src/renderLayer/Types.ts | 2 +- src/browser/Viewport.ts | 2 +- src/browser/renderer/Types.d.ts | 56 ------------------- src/browser/services/Services.ts | 2 +- test/api/InputHandler.api.ts | 2 +- 7 files changed, 6 insertions(+), 62 deletions(-) delete mode 100644 src/browser/renderer/Types.d.ts diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 689b847a2..b19c8cb18 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -10,7 +10,7 @@ import { fill } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { Disposable, toDisposable } from 'common/Lifecycle'; interface IVertices { diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 2b274516d..522f6fb62 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; import { ICoreService } from 'common/services/Services'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts index 70acff340..993304a2f 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/Types.ts @@ -5,7 +5,7 @@ import { IDisposable, Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; export interface IRenderLayer extends IDisposable { /** diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index f95e0bb48..453733533 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -9,7 +9,7 @@ import { IColorSet, IViewport } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { IBuffer } from 'common/buffer/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; const FALLBACK_SCROLL_BAR_WIDTH = 15; diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts deleted file mode 100644 index cb1a85b46..000000000 --- a/src/browser/renderer/Types.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from 'common/Types'; -import { IColorSet } from 'browser/Types'; -import { IEvent } from 'common/EventEmitter'; - -export interface IRenderDimensions { - scaledCharWidth: number; - scaledCharHeight: number; - scaledCellWidth: number; - scaledCellHeight: number; - scaledCharLeft: number; - scaledCharTop: number; - scaledCanvasWidth: number; - scaledCanvasHeight: number; - canvasWidth: number; - canvasHeight: number; - actualCellWidth: number; - actualCellHeight: number; -} - -export interface IRequestRedrawEvent { - start: number; - end: number; -} - -/** - * Note that IRenderer implementations should emit the refresh event after - * rendering rows to the screen. - */ -export interface IRenderer extends IDisposable { - readonly dimensions: IRenderDimensions; - - /** - * Fires when the renderer is requesting to be redrawn on the next animation - * frame but is _not_ a result of content changing (eg. selection changes). - */ - readonly onRequestRedraw: IEvent; - - dispose(): void; - setColors(colors: IColorSet): void; - onDevicePixelRatioChange(): void; - onResize(cols: number, rows: number): void; - onCharSizeChanged(): void; - onBlur(): void; - onFocus(): void; - onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; - onCursorMove(): void; - onOptionsChanged(): void; - clear(): void; - renderRows(start: number, end: number): void; - clearTextureAtlas?(): void; -} diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index ab91f8a32..9eea5c760 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -4,7 +4,7 @@ */ import { IEvent } from 'common/EventEmitter'; -import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; +import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index c8cbbf665..8192bfed3 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { pollFor, openTerminal, getBrowserType, launchBrowser, writeSync } from './TestUtils'; import { Browser, Page } from 'playwright'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; const APP = 'http://127.0.0.1:3001/test'; From f087bd121ea7f1006bad7564eb4e95cf06490d87 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 21:38:01 -0700 Subject: [PATCH 32/56] Add willReadFrequently to canvas renderer too --- addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index 3098538dd..b809378e6 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -96,7 +96,10 @@ export class DynamicCharAtlas extends BaseCharAtlas { const tmpCanvas = document.createElement('canvas'); tmpCanvas.width = this._config.scaledCharWidth; tmpCanvas.height = this._config.scaledCharHeight; - this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency })); + this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { + alpha: this._config.allowTransparency, + willReadFrequently: true + })); this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth); this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight); From af6f03600f9e4f7db56dddd6ec4ae856a44ba364 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:10:37 -0700 Subject: [PATCH 33/56] Show canvas texture atlas on demo --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 1 + addons/xterm-addon-canvas/src/CanvasAddon.ts | 9 +++++++++ addons/xterm-addon-canvas/src/CanvasRenderer.ts | 12 +++++++----- addons/xterm-addon-canvas/src/Types.d.ts | 1 + .../xterm-addon-canvas/src/atlas/BaseCharAtlas.ts | 1 + .../src/atlas/DynamicCharAtlas.ts | 8 ++++++++ .../typings/xterm-addon-canvas.d.ts | 5 +++++ demo/client.ts | 14 ++++++++++++-- 8 files changed, 44 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 0a977d9cc..7241347f3 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -52,6 +52,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { }; public get canvas(): HTMLCanvasElement { return this._canvas; } + public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.cacheCanvas!; } constructor( private readonly _terminal: Terminal, diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 372852b0e..10b8cdbb0 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -8,11 +8,15 @@ import { IColorSet } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; export class CanvasAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: CanvasRenderer; + private readonly _onChangeTextureAtlas = new EventEmitter(); + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; + public activate(terminal: Terminal): void { if (!terminal.element) { throw new Error('Cannot activate CanvasAddon before Terminal.open'); @@ -30,6 +34,7 @@ export class CanvasAddon implements ITerminalAddon { const screenElement: HTMLElement = (terminal as any)._core.screenElement; const linkifier = (terminal as any)._core.linkifier2; this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); + forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); } @@ -44,4 +49,8 @@ export class CanvasAddon implements ITerminalAddon { this._renderer?.dispose(); this._renderer = undefined; } + + public get textureAtlas(): HTMLCanvasElement | undefined { + return this._renderer?.textureAtlas; + } } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 0a00f6f80..14f7faf61 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -14,15 +14,11 @@ import { IColorSet, ILinkifier2 } from 'browser/Types'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from './atlas/CharAtlasCache'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter } from 'common/EventEmitter'; import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; import { Terminal } from 'xterm'; -let nextRendererId = 1; - export class CanvasRenderer extends Disposable implements IRenderer { - private _id = nextRendererId++; - private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; @@ -30,6 +26,8 @@ export class CanvasRenderer extends Disposable implements IRenderer { private readonly _onRequestRedraw = new EventEmitter(); public readonly onRequestRedraw = this._onRequestRedraw.event; + private readonly _onChangeTextureAtlas = new EventEmitter(); + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; constructor( private readonly _terminal: Terminal, @@ -82,6 +80,10 @@ export class CanvasRenderer extends Disposable implements IRenderer { removeTerminalFromCache(this._terminal); } + public get textureAtlas(): HTMLCanvasElement | undefined { + return this._renderLayers[0].cacheCanvas; + } + public onDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index dda6052ae..0527eae9d 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -58,6 +58,7 @@ export interface IRenderer extends IDisposable { export interface IRenderLayer extends IDisposable { readonly canvas: HTMLCanvasElement; + readonly cacheCanvas: HTMLCanvasElement; /** * Called when the terminal loses focus. diff --git a/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts index 03cf0285f..466e2ec8c 100644 --- a/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts @@ -8,6 +8,7 @@ import { IDisposable } from 'common/Types'; export abstract class BaseCharAtlas implements IDisposable { private _didWarmUp: boolean = false; + public abstract readonly cacheCanvas: HTMLCanvasElement; public dispose(): void { } diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index c06842a7c..e52e3a409 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -111,6 +111,10 @@ export class DynamicCharAtlas extends BaseCharAtlas { } } + public override get cacheCanvas(): HTMLCanvasElement { + return this._cacheCanvas!; + } + public beginFrame(): void { this._drawToCacheCount = 0; } @@ -374,6 +378,10 @@ export class NoneCharAtlas extends BaseCharAtlas { super(); } + public override get cacheCanvas(): HTMLCanvasElement { + return null!; + } + public draw( ctx: CanvasRenderingContext2D, glyph: IGlyphIdentifier, diff --git a/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts b/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts index 7bbbd63db..6a2b98d4a 100644 --- a/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts +++ b/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts @@ -12,6 +12,11 @@ declare module 'xterm-addon-canvas' { export class CanvasAddon implements ITerminalAddon { public textureAtlas?: HTMLCanvasElement; + /** + * An event that is fired when the texture atlas of the renderer changes. + */ + public readonly onChangeTextureAtlas: IEvent; + constructor(); /** diff --git a/demo/client.ts b/demo/client.ts index 00b10b571..a1e3c97cf 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -545,7 +545,15 @@ function initAddons(term: TerminalType): void { try { term.loadAddon(addon.instance); if (name === 'webgl') { - (addon.instance as WebglAddon).onChangeTextureAtlas(e => addTextureAtlas(e)); + setTimeout(() => { + addTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + }, 0); + } else if (name === 'canvas') { + setTimeout(() => { + addTextureAtlas(addons.canvas.instance.textureAtlas); + addons.canvas.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + }, 0); } else if (name === 'unicode11') { term.unicode.activeVersion = '11'; } else if (name === 'search') { @@ -559,7 +567,9 @@ function initAddons(term: TerminalType): void { } } else { if (name === 'webgl') { - (addon.instance as WebglAddon).textureAtlas.remove(); + addons.webgl.instance.textureAtlas.remove(); + } else if (name === 'canvas') { + addons.canvas.instance.textureAtlas.remove(); } else if (name === 'unicode11') { term.unicode.activeVersion = '6'; } From 53d029789802d0669ba8436d23e0b5925a50e68a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:15:12 -0700 Subject: [PATCH 34/56] Lint client.ts --- .eslintrc.json | 1 + demo/client.ts | 124 +++++++++++++++++++++++++------------------------ 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 9510fc6b9..aa6e6417c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,6 +7,7 @@ "parser": "@typescript-eslint/parser", "parserOptions": { "project": [ + "demo/tsconfig.json", "src/browser/tsconfig.json", "src/common/tsconfig.json", "src/headless/tsconfig.json", diff --git a/demo/client.ts b/demo/client.ts index a1e3c97cf..8bff46907 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -36,15 +36,15 @@ import { Terminal as TerminalType, ITerminalOptions } from 'xterm'; export interface IWindowWithTerminal extends Window { term: TerminalType; - Terminal?: typeof TerminalType; - AttachAddon?: typeof AttachAddon; - FitAddon?: typeof FitAddon; - SearchAddon?: typeof SearchAddon; - SerializeAddon?: typeof SerializeAddon; - WebLinksAddon?: typeof WebLinksAddon; - WebglAddon?: typeof WebglAddon; - Unicode11Addon?: typeof Unicode11Addon; - LigaturesAddon?: typeof LigaturesAddon; + Terminal?: typeof TerminalType; // eslint-disable-line @typescript-eslint/naming-convention + AttachAddon?: typeof AttachAddon; // eslint-disable-line @typescript-eslint/naming-convention + FitAddon?: typeof FitAddon; // eslint-disable-line @typescript-eslint/naming-convention + SearchAddon?: typeof SearchAddon; // eslint-disable-line @typescript-eslint/naming-convention + SerializeAddon?: typeof SerializeAddon; // eslint-disable-line @typescript-eslint/naming-convention + WebLinksAddon?: typeof WebLinksAddon; // eslint-disable-line @typescript-eslint/naming-convention + WebglAddon?: typeof WebglAddon; // eslint-disable-line @typescript-eslint/naming-convention + Unicode11Addon?: typeof Unicode11Addon; // eslint-disable-line @typescript-eslint/naming-convention + LigaturesAddon?: typeof LigaturesAddon; // eslint-disable-line @typescript-eslint/naming-convention } declare let window: IWindowWithTerminal; @@ -59,27 +59,29 @@ type AddonType = 'attach' | 'canvas' | 'fit' | 'search' | 'serialize' | 'unicode interface IDemoAddon { name: T; canChange: boolean; - ctor: + ctor: ( T extends 'attach' ? typeof AttachAddon : - T extends 'canvas' ? typeof CanvasAddon : - T extends 'fit' ? typeof FitAddon : - T extends 'search' ? typeof SearchAddon : - T extends 'serialize' ? typeof SerializeAddon : - T extends 'web-links' ? typeof WebLinksAddon : - T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'ligatures' ? typeof LigaturesAddon : - typeof WebglAddon; - instance?: + T extends 'canvas' ? typeof CanvasAddon : + T extends 'fit' ? typeof FitAddon : + T extends 'search' ? typeof SearchAddon : + T extends 'serialize' ? typeof SerializeAddon : + T extends 'web-links' ? typeof WebLinksAddon : + T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'ligatures' ? typeof LigaturesAddon : + typeof WebglAddon + ); + instance?: ( T extends 'attach' ? AttachAddon : - T extends 'canvas' ? CanvasAddon : - T extends 'fit' ? FitAddon : - T extends 'search' ? SearchAddon : - T extends 'serialize' ? SerializeAddon : - T extends 'web-links' ? WebLinksAddon : - T extends 'webgl' ? WebglAddon : - T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'ligatures' ? typeof LigaturesAddon : - never; + T extends 'canvas' ? CanvasAddon : + T extends 'fit' ? FitAddon : + T extends 'search' ? SearchAddon : + T extends 'serialize' ? SerializeAddon : + T extends 'web-links' ? WebLinksAddon : + T extends 'webgl' ? WebglAddon : + T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'ligatures' ? typeof LigaturesAddon : + never + ); } const addons: { [T in AddonType]: IDemoAddon } = { @@ -96,12 +98,12 @@ const addons: { [T in AddonType]: IDemoAddon } = { let terminalContainer = document.getElementById('terminal-container'); const actionElements = { - find: document.querySelector('#find'), - findNext: document.querySelector('#find-next'), - findPrevious: document.querySelector('#find-previous'), + find: document.querySelector('#find') as HTMLInputElement, + findNext: document.querySelector('#find-next') as HTMLInputElement, + findPrevious: document.querySelector('#find-previous') as HTMLInputElement, findResults: document.querySelector('#find-results') }; -const paddingElement = document.getElementById('padding'); +const paddingElement = document.getElementById('padding') as HTMLInputElement; const xtermjsTheme = { foreground: '#F8F8F8', @@ -146,7 +148,7 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { }; } -const disposeRecreateButtonHandler = () => { +const disposeRecreateButtonHandler: () => void = () => { // If the terminal exists dispose of it, otherwise recreate it if (term) { term.dispose(); @@ -169,7 +171,7 @@ const disposeRecreateButtonHandler = () => { } }; -const createNewWindowButtonHandler = () => { +const createNewWindowButtonHandler: () => void = () => { if (term) { disposeRecreateButtonHandler(); } @@ -196,7 +198,7 @@ const createNewWindowButtonHandler = () => { } }); } -} +}; if (document.location.pathname === '/test') { window.Terminal = Terminal; @@ -300,8 +302,8 @@ function createTerminal(): void { setTimeout(() => { initOptions(term); // TODO: Clean this up, opt-cols/rows doesn't exist anymore - (document.getElementById(`opt-cols`)).value = term.cols; - (document.getElementById(`opt-rows`)).value = term.rows; + (document.getElementById(`opt-cols`) as HTMLInputElement).value = term.cols; + (document.getElementById(`opt-rows`) as HTMLInputElement).value = term.rows; paddingElement.value = '0'; // Set terminal size again to set the specific dimensions on the demo @@ -427,14 +429,14 @@ function initOptions(term: TerminalType): void { // Attach listeners booleanOptions.forEach(o => { - const input = document.getElementById(`opt-${o}`); + const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.checked); term.options[o] = input.checked; }); }); numberOptions.forEach(o => { - const input = document.getElementById(`opt-${o}`); + const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.value); if (o === 'rows') { @@ -456,7 +458,7 @@ function initOptions(term: TerminalType): void { }); }); Object.keys(stringOptions).forEach(o => { - const input = document.getElementById(`opt-${o}`); + const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.value); let value: any = input.value; @@ -489,7 +491,7 @@ function initOptions(term: TerminalType): void { magenta: '#b168df', red: '#da6771', white: '#efefef', - yellow: '#fff099', + yellow: '#fff099' }; break; case 'light': @@ -513,7 +515,7 @@ function initOptions(term: TerminalType): void { magenta: '#bc05bc', red: '#cd3131', white: '#555555', - yellow: '#949800', + yellow: '#949800' }; break; } @@ -594,7 +596,7 @@ function initAddons(term: TerminalType): void { container.appendChild(fragment); } -function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined) { +function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined): void { let content: string; if (e === undefined) { content = 'undefined'; @@ -610,8 +612,8 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a } function updateTerminalSize(): void { - const cols = parseInt((document.getElementById(`opt-cols`)).value, 10); - const rows = parseInt((document.getElementById(`opt-rows`)).value, 10); + const cols = parseInt((document.getElementById(`opt-cols`) as HTMLInputElement).value, 10); + const rows = parseInt((document.getElementById(`opt-rows`) as HTMLInputElement).value, 10); const width = (cols * term._core._renderService.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px'; const height = (rows * term._core._renderService.dimensions.actualCellHeight).toString() + 'px'; terminalContainer.style.width = width; @@ -635,21 +637,21 @@ function htmlSerializeButtonHandler(): void { document.getElementById('htmlserialize-output').innerText = output; // Deprecated, but the most supported for now. - function listener(e: any) { - e.clipboardData.setData("text/html", output); + function listener(e: any): void { + e.clipboardData.setData('text/html', output); e.preventDefault(); } - document.addEventListener("copy", listener); - document.execCommand("copy"); - document.removeEventListener("copy", listener); - document.getElementById("htmlserialize-output-result").innerText = "Copied to clipboard"; + document.addEventListener('copy', listener); + document.execCommand('copy'); + document.removeEventListener('copy', listener); + document.getElementById('htmlserialize-output-result').innerText = 'Copied to clipboard'; } -function addTextureAtlas(e: HTMLCanvasElement) { +function addTextureAtlas(e: HTMLCanvasElement): void { document.querySelector('#texture-atlas').replaceChildren(e); } -function writeCustomGlyphHandler() { +function writeCustomGlyphHandler(): void { term.write('\n\r'); term.write('\n\r'); term.write('Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐\n\r'); @@ -694,7 +696,7 @@ function writeCustomGlyphHandler() { window.scrollTo(0, 0); } -function loadTest() { +function loadTest(): void { const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : 'dom'; const testData = []; let byteCount = 0; @@ -727,7 +729,7 @@ function loadTest() { }); } -function powerlineSymbolTest() { +function powerlineSymbolTest(): void { function s(char: string): string { return `${char} \x1b[7m${char}\x1b[0m `; } @@ -801,7 +803,7 @@ function powerlineSymbolTest() { term.writeln('nf-mdi-github_face (\\uFbd9) \ufbd9'); } -function underlineTest() { +function underlineTest(): void { function u(style: number): string { return `\x1b[4:${style}m`; } @@ -811,7 +813,7 @@ function underlineTest() { term.write('\n\n\r'); term.writeln('Underline styles:'); term.writeln(''); - function showSequence(id: number, name: string) { + function showSequence(id: number, name: string): string { let alphabet = ''; for (let i = 97; i < 123; i++) { alphabet += String.fromCharCode(i); @@ -858,7 +860,7 @@ function underlineTest() { term.write('\x1b[0m\n\r'); } -function ansiColorsTest() { +function ansiColorsTest(): void { term.writeln(`\x1b[0m\n\n\rStandard colors: Bright colors:`); for (let i = 0; i < 16; i++) { term.write(`\x1b[48;5;${i}m ${i.toString().padEnd(2, ' ').padStart(3, ' ')} \x1b[0m`); @@ -883,7 +885,7 @@ function ansiColorsTest() { } } -function addAnsiHyperlink() { +function addAnsiHyperlink(): void { term.write('\n\n\r'); term.writeln(`Regular link with no id:`); term.writeln('\x1b]8;;https://github.com\x07GitHub\x1b]8;;\x07'); @@ -903,7 +905,7 @@ function addAnsiHyperlink() { term.write('\x1b[3A\x1b[1C\x1b]8;;https://xtermjs.org\x07xter\x1b[B\x1b[4Dm.js\x1b]8;;\x07\x1b[2B\x1b[5D'); } -function addDecoration() { +function addDecoration(): void { term.options['overviewRulerWidth'] = 15; const marker = term.registerMarker(1); const decoration = term.registerDecoration({ @@ -918,7 +920,7 @@ function addDecoration() { }); } -function addOverviewRuler() { +function addOverviewRuler(): void { term.options['overviewRulerWidth'] = 15; term.registerDecoration({ marker: term.registerMarker(1), overviewRulerOptions: { color: '#ef2929' } }); term.registerDecoration({ marker: term.registerMarker(3), overviewRulerOptions: { color: '#8ae234' } }); From 8752c4fa8ecd9628e58a35985afedbe90e78a03f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:19:57 -0700 Subject: [PATCH 35/56] Remove canvas char atlas base class --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 4 +- .../src/atlas/BaseCharAtlas.ts | 59 ------------------- .../src/atlas/CharAtlasCache.ts | 5 +- .../src/atlas/DynamicCharAtlas.ts | 44 ++++++-------- 4 files changed, 22 insertions(+), 90 deletions(-) delete mode 100644 addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 7241347f3..052008610 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -9,7 +9,6 @@ import { ICellData, IColor } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from './atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { BaseCharAtlas } from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; @@ -21,6 +20,7 @@ import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { Terminal } from 'xterm'; +import { DynamicCharAtlas } from 'atlas/DynamicCharAtlas'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -36,7 +36,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _charAtlas: BaseCharAtlas | undefined; + protected _charAtlas: DynamicCharAtlas | undefined; /** * An object that's reused when drawing glyphs in order to reduce GC. diff --git a/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts deleted file mode 100644 index 466e2ec8c..000000000 --- a/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IGlyphIdentifier } from './Types'; -import { IDisposable } from 'common/Types'; - -export abstract class BaseCharAtlas implements IDisposable { - private _didWarmUp: boolean = false; - public abstract readonly cacheCanvas: HTMLCanvasElement; - - public dispose(): void { } - - /** - * Perform any work needed to warm the cache before it can be used. May be called multiple times. - * Implement _doWarmUp instead if you only want to get called once. - */ - public warmUp(): void { - if (!this._didWarmUp) { - this._doWarmUp(); - this._didWarmUp = true; - } - } - - /** - * Perform any work needed to warm the cache before it can be used. Used by the default - * implementation of warmUp(), and will only be called once. - */ - private _doWarmUp(): void { } - - public clear(): void { } - - /** - * Called when we start drawing a new frame. - * - * TODO: We rely on this getting called by TextRenderLayer. This should really be called by - * Renderer instead, but we need to make Renderer the source-of-truth for the char atlas, instead - * of BaseRenderLayer. - */ - public beginFrame(): void { } - - /** - * May be called before warmUp finishes, however it is okay for the implementation to - * do nothing and return false in that case. - * - * @param ctx Where to draw the character onto. - * @param glyph Information about what to draw - * @param x The position on the context to start drawing at - * @param y The position on the context to start drawing at - * @returns The success state. True if we drew the character. - */ - public abstract draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean; -} diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts index 73d66f64f..f729b7f00 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts @@ -4,14 +4,13 @@ */ import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; -import { BaseCharAtlas } from './BaseCharAtlas'; import { DynamicCharAtlas } from './DynamicCharAtlas'; import { IColorSet } from 'browser/Types'; import { Terminal } from 'xterm'; import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; interface ICharAtlasCacheEntry { - atlas: BaseCharAtlas; + atlas: DynamicCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. @@ -32,7 +31,7 @@ export function acquireCharAtlas( scaledCharWidth: number, scaledCharHeight: number, devicePixelRatio: number -): BaseCharAtlas { +): DynamicCharAtlas { const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the renderer already owns this config diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index e52e3a409..54ad6f6ea 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -5,7 +5,6 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { IGlyphIdentifier } from './Types'; -import { BaseCharAtlas } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; @@ -52,14 +51,16 @@ export function getGlyphCacheKey(glyph: IGlyphIdentifier): number { return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); } -export class DynamicCharAtlas extends BaseCharAtlas { +export class DynamicCharAtlas { // An ordered map that we're using to keep track of where each glyph is in the atlas texture. // It's ordered so that we can determine when to remove the old entries. private _cacheMap: LRUMap; // The texture that the atlas is drawn to private _cacheCanvas: HTMLCanvasElement; + public get cacheCanvas(): HTMLCanvasElement { return this._cacheCanvas; } private _cacheCtx: CanvasRenderingContext2D; + private _didWarmUp: boolean = false; // A temporary context that glyphs are drawn to before being transfered to the atlas. private _tmpCtx: CanvasRenderingContext2D; @@ -80,7 +81,6 @@ export class DynamicCharAtlas extends BaseCharAtlas { private _bitmap: ImageBitmap | null = null; constructor(document: Document, private _config: ICharAtlasConfig) { - super(); this._cacheCanvas = document.createElement('canvas'); this._cacheCanvas.width = TEXTURE_WIDTH; this._cacheCanvas.height = TEXTURE_HEIGHT; @@ -111,10 +111,23 @@ export class DynamicCharAtlas extends BaseCharAtlas { } } - public override get cacheCanvas(): HTMLCanvasElement { - return this._cacheCanvas!; + /** + * Perform any work needed to warm the cache before it can be used. May be called multiple times. + * Implement _doWarmUp instead if you only want to get called once. + */ + public warmUp(): void { + if (!this._didWarmUp) { + this._doWarmUp(); + this._didWarmUp = true; + } } + /** + * Perform any work needed to warm the cache before it can be used. Used by the default + * implementation of warmUp(), and will only be called once. + */ + private _doWarmUp(): void { } + public beginFrame(): void { this._drawToCacheCount = 0; } @@ -371,27 +384,6 @@ export class DynamicCharAtlas extends BaseCharAtlas { } } -// This is used for debugging the renderer, just swap out `new DynamicCharAtlas` with -// `new NoneCharAtlas`. -export class NoneCharAtlas extends BaseCharAtlas { - constructor(document: Document, config: ICharAtlasConfig) { - super(); - } - - public override get cacheCanvas(): HTMLCanvasElement { - return null!; - } - - public draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean { - return false; - } -} - /** * Makes a particular rgb color and colors that are nearly the same in an ImageData completely * transparent. From dece3cc41cd1106d2aad139ee93dbc9e99a5019c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:20:55 -0700 Subject: [PATCH 36/56] DynamicCharAtlas -> CanvasCharAtlas --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 4 ++-- .../src/atlas/{DynamicCharAtlas.ts => CanvasCharAtlas.ts} | 2 +- addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) rename addons/xterm-addon-canvas/src/atlas/{DynamicCharAtlas.ts => CanvasCharAtlas.ts} (99%) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 052008610..d22088f24 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -20,7 +20,7 @@ import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { Terminal } from 'xterm'; -import { DynamicCharAtlas } from 'atlas/DynamicCharAtlas'; +import { CanvasCharAtlas } from 'atlas/CanvasCharAtlas'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -36,7 +36,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _charAtlas: DynamicCharAtlas | undefined; + protected _charAtlas: CanvasCharAtlas | undefined; /** * An object that's reused when drawing glyphs in order to reduce GC. diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts similarity index 99% rename from addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts rename to addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts index 54ad6f6ea..f3be7f46b 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts @@ -51,7 +51,7 @@ export function getGlyphCacheKey(glyph: IGlyphIdentifier): number { return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); } -export class DynamicCharAtlas { +export class CanvasCharAtlas { // An ordered map that we're using to keep track of where each glyph is in the atlas texture. // It's ordered so that we can determine when to remove the old entries. private _cacheMap: LRUMap; diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts index f729b7f00..f5995cf8e 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts @@ -4,13 +4,13 @@ */ import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; -import { DynamicCharAtlas } from './DynamicCharAtlas'; +import { CanvasCharAtlas } from './CanvasCharAtlas'; import { IColorSet } from 'browser/Types'; import { Terminal } from 'xterm'; import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; interface ICharAtlasCacheEntry { - atlas: DynamicCharAtlas; + atlas: CanvasCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. @@ -31,7 +31,7 @@ export function acquireCharAtlas( scaledCharWidth: number, scaledCharHeight: number, devicePixelRatio: number -): DynamicCharAtlas { +): CanvasCharAtlas { const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the renderer already owns this config @@ -64,7 +64,7 @@ export function acquireCharAtlas( } const newEntry: ICharAtlasCacheEntry = { - atlas: new DynamicCharAtlas( + atlas: new CanvasCharAtlas( document, newConfig ), From 333b0e28ac7775299a56228dd375a096627728a5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:48:43 -0700 Subject: [PATCH 37/56] Add ITextureAtlas and use that instead of WebglCharAtlas --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 7 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 5 +- .../src/atlas/CharAtlasCache.ts | 6 +- .../src/atlas/WebglCharAtlas.ts | 4 +- .../src/renderLayer/BaseRenderLayer.ts | 5 +- src/browser/renderer/shared/Types.d.ts | 58 +++++++++++++++++++ 6 files changed, 70 insertions(+), 15 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index b19c8cb18..c198c9bad 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -4,13 +4,12 @@ */ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; -import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { fill } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/shared/Types'; +import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { Disposable, toDisposable } from 'common/Lifecycle'; interface IVertices { @@ -77,7 +76,7 @@ let $leftCellPadding = 0; let $clippedPixels = 0; export class GlyphRenderer extends Disposable { - private _atlas: WebglCharAtlas | undefined; + private _atlas: ITextureAtlas | undefined; private _program: WebGLProgram; private _vertexArrayObject: IWebGLVertexArrayObject; @@ -321,7 +320,7 @@ export class GlyphRenderer extends Disposable { gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL); } - public setAtlas(atlas: WebglCharAtlas): void { + public setAtlas(atlas: ITextureAtlas): void { const gl = this._gl; this._atlas = atlas; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 34f247eb1..b3a3ed023 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -7,7 +7,6 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; import { acquireCharAtlas, removeTerminalFromCache } from './atlas/CharAtlasCache'; -import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; @@ -15,7 +14,7 @@ import { Disposable } from 'common/Lifecycle'; import { Attributes, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; @@ -35,7 +34,7 @@ let $isSelected = false; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; - private _charAtlas: WebglCharAtlas | undefined; + private _charAtlas: ITextureAtlas | undefined; private _devicePixelRatio: number; private _model: RenderModel = new RenderModel(); diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts index e70a49954..741de2556 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts @@ -6,11 +6,11 @@ import { WebglCharAtlas } from './WebglCharAtlas'; import { Terminal } from 'xterm'; import { IColorSet, ITerminal } from 'browser/Types'; -import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; +import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; interface ICharAtlasCacheEntry { - atlas: WebglCharAtlas; + atlas: ITextureAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. @@ -33,7 +33,7 @@ export function acquireCharAtlas( scaledCharWidth: number, scaledCharHeight: number, devicePixelRatio: number -): WebglCharAtlas { +): ITextureAtlas { const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the terminal already owns this config diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b9a4088d3..8782e123e 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -16,7 +16,7 @@ import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlin import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; -import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; +import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -51,7 +51,7 @@ interface ICharAtlasActiveRow { // Work variables to avoid garbage collection let $glyph = undefined; -export class WebglCharAtlas implements IDisposable { +export class WebglCharAtlas implements ITextureAtlas { private _didWarmUp: boolean = false; private _cacheMap: FourKeyMap = new FourKeyMap(); diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 2cba49dd4..1321c246b 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -9,9 +9,8 @@ import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; -import { IRenderDimensions } from 'browser/renderer/shared/Types'; +import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { CellData } from 'common/buffer/CellData'; -import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; import { throwIfFalsy } from '../WebglUtils'; export abstract class BaseRenderLayer implements IRenderLayer { @@ -24,7 +23,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - protected _charAtlas: WebglCharAtlas | undefined; + protected _charAtlas: ITextureAtlas | undefined; constructor( private _container: HTMLElement, diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index ccfcae433..8fde3ae84 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -74,3 +74,61 @@ export interface IRenderer extends IDisposable { renderRows(start: number, end: number): void; clearTextureAtlas?(): void; } + +export interface ITextureAtlas extends IDisposable { + readonly cacheCanvas: HTMLCanvasElement; + // TODO: Add endFrame and make this readonly + hasCanvasChanged: boolean; + + /** + * Warm up the texture atlas, adding common glyphs to avoid slowing early frame. + */ + warmUp(): void; + + /** + * Call when a frame is being drawn, this will return true if the atlas was cleared to make room + * for a new set of glyphs. + */ + beginFrame(): boolean; + + /** + * Clear all glyphs from the texture atlas. + */ + clearTexture(): void; + getRasterizedGlyph(code: number, bg: number, fg: number, ext: number): IRasterizedGlyph; + getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number): IRasterizedGlyph; +} + +/** + * Represents a rasterized glyph within a texture atlas. Some numbers are + * tracked in CSS pixels as well in order to reduce calculations during the + * render loop. + */ +export interface IRasterizedGlyph { + /** + * The x and y offset between the glyph's top/left and the top/left of a cell + * in pixels. + */ + offset: IVector; + /** + * the x and y position of the glyph in the texture in pixels. + */ + texturePosition: IVector; + /** + * the x and y position of the glyph in the texture in clip space coordinates. + */ + texturePositionClipSpace: IVector; + /** + * The width and height of the glyph in the texture in pixels. + */ + size: IVector; + /** + * The width and height of the glyph in the texture in clip space coordinates. + */ + sizeClipSpace: IVector; +} + +export interface IVector { + x: number; + y: number; +} From 26c974f9b62386a4e9ed8f39724e5780d63e3e59 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 08:36:24 -0700 Subject: [PATCH 38/56] Shuffle some utils --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 7 ++-- .../src/RectangleRenderer.ts | 3 +- addons/xterm-addon-webgl/src/Types.d.ts | 34 ------------------- addons/xterm-addon-webgl/src/WebglUtils.ts | 9 ++--- .../src/atlas/WebglCharAtlas.ts | 8 ++--- .../src/renderLayer/BaseRenderLayer.ts | 2 +- 6 files changed, 12 insertions(+), 51 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index c198c9bad..f27ef29ff 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -3,14 +3,15 @@ * @license MIT */ -import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; -import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; +import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; +import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { Disposable, toDisposable } from 'common/Lifecycle'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; interface IVertices { attributes: Float32Array; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ea7c3836b..50a8cd58b 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; +import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext } from './Types'; import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; @@ -13,6 +13,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { DIM_OPACITY } from 'browser/renderer/shared/Constants'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; const enum VertexAttribLocations { POSITION = 0, diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index bcfa11c8d..7add33a3a 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -3,40 +3,6 @@ * @license MIT */ -/** - * Represents a rasterized glyph within a texture atlas. Some numbers are - * tracked in CSS pixels as well in order to reduce calculations during the - * render loop. - */ -export interface IRasterizedGlyph { - /** - * The x and y offset between the glyph's top/left and the top/left of a cell - * in pixels. - */ - offset: IVector; - /** - * the x and y position of the glyph in the texture in pixels. - */ - texturePosition: IVector; - /** - * the x and y position of the glyph in the texture in clip space coordinates. - */ - texturePositionClipSpace: IVector; - /** - * The width and height of the glyph in the texture in pixels. - */ - size: IVector; - /** - * The width and height of the glyph in the texture in clip space coordinates. - */ - sizeClipSpace: IVector; -} - -export interface IVector { - x: number; - y: number; -} - export interface IBoundingBox { top: number; left: number; diff --git a/addons/xterm-addon-webgl/src/WebglUtils.ts b/addons/xterm-addon-webgl/src/WebglUtils.ts index 841ad067b..51a273777 100644 --- a/addons/xterm-addon-webgl/src/WebglUtils.ts +++ b/addons/xterm-addon-webgl/src/WebglUtils.ts @@ -3,6 +3,8 @@ * @license MIT */ +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; + /** * A matrix that when multiplies will translate 0-1 coordinates (left to right, * top to bottom) to clip space. @@ -49,10 +51,3 @@ export function expandFloat32Array(source: Float32Array, max: number): Float32Ar } return newArray; } - -export function throwIfFalsy(value: T | undefined | null): T { - if (!value) { - throw new Error('value must not be falsy'); - } - return value; -} diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 8782e123e..3d9bdf160 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -4,19 +4,17 @@ */ import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { IRasterizedGlyph, IBoundingBox } from '../Types'; +import { IBoundingBox } from '../Types'; import { DEFAULT_COLOR, Attributes, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; -import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'common/Types'; -import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; import { color, NULL_COLOR, rgba } from 'common/Color'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/shared/RendererUtils'; +import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; -import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 1321c246b..9290bb79a 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -11,7 +11,7 @@ import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { CellData } from 'common/buffer/CellData'; -import { throwIfFalsy } from '../WebglUtils'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; From f38a49b4d57fc6d7d5f7ad644abddd03ac349bc3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 08:48:55 -0700 Subject: [PATCH 39/56] Move remaining webgl atlas to shared --- addons/xterm-addon-webgl/src/Types.d.ts | 7 ------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 ++-- addons/xterm-addon-webgl/src/atlas/Types.d.ts | 14 -------------- .../src/renderLayer/BaseRenderLayer.ts | 4 ++-- .../browser/renderer/shared}/CharAtlasCache.ts | 12 ++++++------ .../browser/renderer/shared/TextureAtlas.ts | 5 ++--- src/browser/renderer/shared/Types.d.ts | 7 +++++++ 7 files changed, 19 insertions(+), 34 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/atlas/Types.d.ts rename {addons/xterm-addon-webgl/src/atlas => src/browser/renderer/shared}/CharAtlasCache.ts (90%) rename addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts => src/browser/renderer/shared/TextureAtlas.ts (99%) diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index 7add33a3a..d09f43e9e 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -3,13 +3,6 @@ * @license MIT */ -export interface IBoundingBox { - top: number; - left: number; - right: number; - bottom: number; -} - export interface IRenderModel { cells: Uint32Array; lineLengths: Uint32Array; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b3a3ed023..977d42d70 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -6,7 +6,7 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; -import { acquireCharAtlas, removeTerminalFromCache } from './atlas/CharAtlasCache'; +import { acquireTextureAtlas, removeTerminalFromCache } from '../../../src/browser/renderer/shared/CharAtlasCache'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; @@ -277,7 +277,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, this._coreBrowserService.dpr); + const atlas = acquireTextureAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, this._coreBrowserService.dpr); if (!('getRasterizedGlyph' in atlas)) { throw new Error('The webgl renderer only works with the webgl char atlas'); } diff --git a/addons/xterm-addon-webgl/src/atlas/Types.d.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts deleted file mode 100644 index fd5b596cb..000000000 --- a/addons/xterm-addon-webgl/src/atlas/Types.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -export interface IGlyphIdentifier { - chars: string; - code: number; - bg: number; - fg: number; - bold: boolean; - dim: boolean; - italic: boolean; -} diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 9290bb79a..25871c24b 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,7 +4,7 @@ */ import { IRenderLayer } from './Types'; -import { acquireCharAtlas } from '../atlas/CharAtlasCache'; +import { acquireTextureAtlas } from '../../../../src/browser/renderer/shared/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; @@ -94,7 +94,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + this._charAtlas = acquireTextureAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts b/src/browser/renderer/shared/CharAtlasCache.ts similarity index 90% rename from addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts rename to src/browser/renderer/shared/CharAtlasCache.ts index 741de2556..ee88c7089 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts +++ b/src/browser/renderer/shared/CharAtlasCache.ts @@ -3,13 +3,13 @@ * @license MIT */ -import { WebglCharAtlas } from './WebglCharAtlas'; +import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; import { Terminal } from 'xterm'; import { IColorSet, ITerminal } from 'browser/Types'; import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; -interface ICharAtlasCacheEntry { +interface ITextureAtlasCacheEntry { atlas: ITextureAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so @@ -17,7 +17,7 @@ interface ICharAtlasCacheEntry { ownedBy: Terminal[]; } -const charAtlasCache: ICharAtlasCacheEntry[] = []; +const charAtlasCache: ITextureAtlasCacheEntry[] = []; /** * Acquires a char atlas, either generating a new one or returning an existing @@ -25,7 +25,7 @@ const charAtlasCache: ICharAtlasCacheEntry[] = []; * @param terminal The terminal. * @param colors The colors to use. */ -export function acquireCharAtlas( +export function acquireTextureAtlas( terminal: Terminal, colors: IColorSet, scaledCellWidth: number, @@ -66,8 +66,8 @@ export function acquireCharAtlas( } const core: ITerminal = (terminal as any)._core; - const newEntry: ICharAtlasCacheEntry = { - atlas: new WebglCharAtlas(document, newConfig, core.unicodeService), + const newEntry: ITextureAtlasCacheEntry = { + atlas: new TextureAtlas(document, newConfig, core.unicodeService), config: newConfig, ownedBy: [terminal] }; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts similarity index 99% rename from addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts rename to src/browser/renderer/shared/TextureAtlas.ts index 3d9bdf160..0840b035d 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -4,7 +4,6 @@ */ import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { IBoundingBox } from '../Types'; import { DEFAULT_COLOR, Attributes, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; import { IColor } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -14,7 +13,7 @@ import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlin import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; -import { ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -49,7 +48,7 @@ interface ICharAtlasActiveRow { // Work variables to avoid garbage collection let $glyph = undefined; -export class WebglCharAtlas implements ITextureAtlas { +export class TextureAtlas implements ITextureAtlas { private _didWarmUp: boolean = false; private _cacheMap: FourKeyMap = new FourKeyMap(); diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 8fde3ae84..20c1357aa 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -132,3 +132,10 @@ export interface IVector { x: number; y: number; } + +export interface IBoundingBox { + top: number; + left: number; + right: number; + bottom: number; +} From 5b785d29a19e851330fcce6a89058f203b81a716 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 09:09:05 -0700 Subject: [PATCH 40/56] Expose _listeners for debug/test --- src/common/EventEmitter.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 979a64644..29e931d22 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -22,7 +22,7 @@ export interface IEventWithEmitter extends IEventEmitter, IEv } export class EventEmitter implements IEventEmitter { - private _listeners: IListener[] = []; + private readonly _listeners: IListener[] = []; private _event?: IEvent; private _disposed: boolean = false; @@ -82,6 +82,9 @@ export class EventEmitter implements IEventEmitter { export function initEvent(): IEventWithEmitter { const emitter = new EventEmitter(); const event = emitter.event; + Object.defineProperty(event, '_listeners', { + value: (emitter as any)._listeners + }); Object.defineProperty(event, 'fire', { value: emitter.fire.bind(emitter) }); From 0bc5ca8f8052826380fd6f028c0f868f85985cce Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:08:06 -0700 Subject: [PATCH 41/56] Fix imports --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 977d42d70..43b15e0c9 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -6,7 +6,7 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; -import { acquireTextureAtlas, removeTerminalFromCache } from '../../../src/browser/renderer/shared/CharAtlasCache'; +import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 25871c24b..07eca0bfb 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,7 +4,7 @@ */ import { IRenderLayer } from './Types'; -import { acquireTextureAtlas } from '../../../../src/browser/renderer/shared/CharAtlasCache'; +import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; From c2bf87ee343820d3d1abb758b76d63662a8830eb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:38:46 -0700 Subject: [PATCH 42/56] Initial working shared atlas --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index d22088f24..8752b55f2 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,13 +3,12 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/shared/Types'; +import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { IRenderLayer } from './Types'; import { ICellData, IColor } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from './atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; @@ -20,7 +19,7 @@ import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { Terminal } from 'xterm'; -import { CanvasCharAtlas } from 'atlas/CanvasCharAtlas'; +import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -36,7 +35,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _charAtlas: CanvasCharAtlas | undefined; + protected _charAtlas: ITextureAtlas | undefined; /** * An object that's reused when drawing glyphs in order to reduce GC. @@ -129,7 +128,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + // this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } @@ -156,7 +156,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public abstract reset(): void; public clearTextureAtlas(): void { - this._charAtlas?.clear(); + this._charAtlas?.clearTexture(); } /** @@ -418,13 +418,34 @@ export abstract class BaseRenderLayer implements IRenderLayer { } }); - const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); - - if (!atlasDidDraw) { + if (this._charAtlas) { + // const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + const glyph = this._charAtlas.getRasterizedGlyph(this._currentGlyphIdentifier.code, this._currentGlyphIdentifier.bg, this._currentGlyphIdentifier.fg, 0); + this._drawGlyph(glyph, x, y); + } else { this._drawUncachedChars(cell, x, y); } } + // Does fg override work? + private _drawGlyph(glyph: IRasterizedGlyph, x: number, y: number): void { + this._ctx.save(); + this._clipRow(y); + this._ctx.drawImage( + this._charAtlas!.cacheCanvas, + glyph.texturePosition.x, + glyph.texturePosition.y, + glyph.size.x, + glyph.size.y, + x * this._scaledCellWidth - glyph.offset.x, + y * this._scaledCellHeight - glyph.offset.y, + glyph.size.x, + glyph.size.y + ); + this._ctx.restore(); + // TODO: Bitmap optimizations? + } + /** * Draws one or more characters at one or more cells. The character(s) will be * clipped to ensure that they fit with the cell(s), including the cell to the From dbef85003912077ceab6ad564828e156773153e1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 13:57:35 -0700 Subject: [PATCH 43/56] Hook up attributes to new texture atlas --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 298 ++---------------- .../xterm-addon-canvas/src/TextRenderLayer.ts | 73 +---- src/browser/renderer/shared/TextureAtlas.ts | 2 + 3 files changed, 21 insertions(+), 352 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index e05614942..3ab8ea5af 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,23 +3,21 @@ * @license MIT */ -import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; -import { IRenderLayer } from './Types'; -import { ICellData, IColor } from 'common/Types'; -import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; -import { IGlyphIdentifier } from './atlas/Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { AttributeData } from 'common/buffer/AttributeData'; +import { removeElementFromParent } from 'browser/Dom'; +import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; +import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; +import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; +import { BgFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ICoreBrowserService } from 'browser/services/Services'; -import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { channels, color, rgba } from 'common/Color'; -import { removeElementFromParent } from 'browser/Dom'; -import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; +import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; -import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; +import { IGlyphIdentifier } from './atlas/Types'; +import { IRenderLayer } from './Types'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -35,7 +33,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _charAtlas: ITextureAtlas | undefined; + protected _charAtlas!: ITextureAtlas; /** * An object that's reused when drawing glyphs in order to reduce GC. @@ -70,6 +68,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); this._container.appendChild(this._canvas); + this._refreshCharAtlas(this._colors); } public dispose(): void { @@ -367,70 +366,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { /** * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. - * @param chars The character or characters. - * @param code The character code. - * @param width The width of the characters. - * @param x The column to draw at. - * @param y The row to draw at. - * @param fg The foreground color, in the format stored within the attributes. - * @param bg The background color, in the format stored within the attributes. - * This is used to validate whether a cached image can be used. - * @param bold Whether the text is bold. */ protected _drawChars(cell: ICellData, x: number, y: number): void { - const contrastColor = this._getContrastColor(cell, x, y); - - // skip cache right away if we draw in RGB - // Note: to avoid bad runtime JoinedCellData will be skipped - // in the cache handler itself (atlasDidDraw == false) and - // fall through to uncached later down below - if (contrastColor || cell.isFgRGB() || cell.isBgRGB()) { - this._drawUncachedChars(cell, x, y, contrastColor); - return; - } - - let fg; - let bg; - if (cell.isInverse()) { - fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor(); - bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor(); - } else { - bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor(); - fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); - } - - const drawInBrightColor = this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8; - - fg += drawInBrightColor ? 8 : 0; - this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR; - this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE; - this._currentGlyphIdentifier.bg = bg; - this._currentGlyphIdentifier.fg = fg; - this._currentGlyphIdentifier.bold = !!cell.isBold(); - this._currentGlyphIdentifier.dim = !!cell.isDim(); - this._currentGlyphIdentifier.italic = !!cell.isItalic(); - - // Don't try cache the glyph if it uses any decoration foreground/background override. - let hasOverrides = false; - this._decorationService.forEachDecorationAtCell(x, y, undefined, d => { - if (d.backgroundColorRGB || d.foregroundColorRGB) { - hasOverrides = true; - } - }); - - if (this._charAtlas) { - // const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); - const contrastColor = this._getContrastColor(cell, x, y); - console.log('contrast color', contrastColor); - const glyph = this._charAtlas.getRasterizedGlyph(this._currentGlyphIdentifier.code, this._currentGlyphIdentifier.bg, this._currentGlyphIdentifier.fg, 0); - this._drawGlyph(glyph, x, y); - } else { - this._drawUncachedChars(cell, x, y); - } - } - - // Does fg override work? - private _drawGlyph(glyph: IRasterizedGlyph, x: number, y: number): void { + const glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); this._ctx.save(); this._clipRow(y); this._ctx.drawImage( @@ -445,80 +383,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { glyph.size.y ); this._ctx.restore(); - // TODO: Bitmap optimizations? - // TODO: Glyph background not correct? + // TODO: Verify selection + // TODO: Verify fg override + // TODO: Verify bg override + // TODO: Verify min contrast ratio } - /** - * Draws one or more characters at one or more cells. The character(s) will be - * clipped to ensure that they fit with the cell(s), including the cell to the - * right if the last character is a wide character. - * @param chars The character. - * @param width The width of the character. - * @param fg The foreground color, in the format stored within the attributes. - * @param x The column to draw at. - * @param y The row to draw at. - */ - private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void { - this._ctx.save(); - this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic()); - this._ctx.textBaseline = TEXT_BASELINE; - - if (cell.isInverse()) { - if (fgOverride) { - this._ctx.fillStyle = fgOverride.css; - } else if (cell.isBgDefault()) { - this._ctx.fillStyle = color.opaque(this._colors.background).css; - } else if (cell.isBgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; - } else { - let bg = cell.getBgColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { - bg += 8; - } - this._ctx.fillStyle = this._colors.ansi[bg].css; - } - } else { - if (fgOverride) { - this._ctx.fillStyle = fgOverride.css; - } else if (cell.isFgDefault()) { - this._ctx.fillStyle = this._colors.foreground.css; - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else { - let fg = cell.getFgColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { - fg += 8; - } - this._ctx.fillStyle = this._colors.ansi[fg].css; - } - } - - this._clipRow(y); - - // Apply alpha to dim the character - if (cell.isDim()) { - this._ctx.globalAlpha = DIM_OPACITY; - } - - // Draw custom characters if applicable - let drawSuccess = false; - if (this._optionsService.rawOptions.customGlyphs !== false) { - drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight, this._optionsService.rawOptions.fontSize, this._coreBrowserService.dpr); - } - - // Draw the character - if (!drawSuccess) { - this._ctx.fillText( - cell.getChars(), - x * this._scaledCellWidth + this._scaledCharLeft, - y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight); - } - - this._ctx.restore(); - } - - /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. * @param y The row to clip. @@ -543,137 +413,5 @@ export abstract class BaseRenderLayer implements IRenderLayer { return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * this._coreBrowserService.dpr}px ${this._optionsService.rawOptions.fontFamily}`; } - - private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined { - // Get any decoration foreground/background overrides, this must be fetched before the early - // exist but applied after inverse - let bgOverride: number | undefined; - let fgOverride: number | undefined; - let isTop = false; - this._decorationService.forEachDecorationAtCell(x, y, undefined, d => { - if (d.options.layer !== 'top' && isTop) { - return; - } - if (d.backgroundColorRGB) { - bgOverride = d.backgroundColorRGB.rgba; - } - if (d.foregroundColorRGB) { - fgOverride = d.foregroundColorRGB.rgba; - } - isTop = d.options.layer === 'top'; - }); - - // Apply selection foreground if applicable - if (!isTop) { - if (this._colors.selectionForeground && this._isCellInSelection(x, y)) { - fgOverride = this._colors.selectionForeground.rgba; - } - } - - if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode()))) { - return undefined; - } - - if (!bgOverride && !fgOverride) { - // Try get from cache - const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg); - if (adjustedColor !== undefined) { - return adjustedColor || undefined; - } - } - - let fgColor = cell.getFgColor(); - let fgColorMode = cell.getFgColorMode(); - let bgColor = cell.getBgColor(); - let bgColorMode = cell.getBgColorMode(); - const isInverse = !!cell.isInverse(); - const isBold = !!cell.isInverse(); - if (isInverse) { - const temp = fgColor; - fgColor = bgColor; - bgColor = temp; - const temp2 = fgColorMode; - fgColorMode = bgColorMode; - bgColorMode = temp2; - } - - const bgRgba = this._resolveBackgroundRgba(bgOverride !== undefined ? Attributes.CM_RGB : bgColorMode, bgOverride ?? bgColor, isInverse); - const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold); - let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio); - - if (!result) { - if (!fgOverride) { - this._colors.contrastCache.setColor(cell.bg, cell.fg, null); - return undefined; - } - // If it was an override and there was no contrast change, set as the result - result = fgOverride; - } - - const color: IColor = { - css: channels.toCss( - (result >> 24) & 0xFF, - (result >> 16) & 0xFF, - (result >> 8) & 0xFF - ), - rgba: result - }; - if (!bgOverride && !fgOverride) { - this._colors.contrastCache.setColor(cell.bg, cell.fg, color); - } - - return color; - } - - private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number { - switch (bgColorMode) { - case Attributes.CM_P16: - case Attributes.CM_P256: - return this._colors.ansi[bgColor].rgba; - case Attributes.CM_RGB: - return bgColor << 8; - case Attributes.CM_DEFAULT: - default: - if (inverse) { - return this._colors.foreground.rgba; - } - return this._colors.background.rgba; - } - } - - private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { - switch (fgColorMode) { - case Attributes.CM_P16: - case Attributes.CM_P256: - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && bold && fgColor < 8) { - fgColor += 8; - } - return this._colors.ansi[fgColor].rgba; - case Attributes.CM_RGB: - return fgColor << 8; - case Attributes.CM_DEFAULT: - default: - if (inverse) { - return this._colors.background.rgba; - } - return this._colors.foreground.rgba; - } - } - - private _isCellInSelection(x: number, y: number): boolean { - const start = this._selectionStart; - const end = this._selectionEnd; - if (!start || !end) { - return false; - } - if (this._columnSelectMode) { - return x >= start[0] && y >= start[1] && - x < end[0] && y < end[1]; - } - return (y > start[1] && y < end[1]) || - (start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) || - (start[1] < end[1] && y === end[1] && x < end[0]) || - (start[1] < end[1] && y === start[1] && x >= start[0]); - } } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index b20765d86..8d80514b4 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -233,78 +233,7 @@ export class TextRenderLayer extends BaseRenderLayer { } private _drawForeground(firstRow: number, lastRow: number): void { - this._forEachCell(firstRow, lastRow, (cell, x, y) => { - if (cell.isInvisible()) { - return; - } - this._drawChars(cell, x, y); - if (cell.isUnderline() || cell.isStrikethrough()) { - this._ctx.save(); - - if (cell.isInverse()) { - if (cell.isBgDefault()) { - this._ctx.fillStyle = this._colors.background.css; - } else if (cell.isBgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; - } else { - let bg = cell.getBgColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { - bg += 8; - } - this._ctx.fillStyle = this._colors.ansi[bg].css; - } - } else { - if (cell.isFgDefault()) { - this._ctx.fillStyle = this._colors.foreground.css; - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else { - let fg = cell.getFgColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { - fg += 8; - } - this._ctx.fillStyle = this._colors.ansi[fg].css; - } - } - - if (cell.isStrikethrough()) { - this._fillMiddleLineAtCells(x, y, cell.getWidth()); - } - if (cell.isUnderline()) { - if (!cell.isUnderlineColorDefault()) { - if (cell.isUnderlineColorRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getUnderlineColor()).join(',')})`; - } else { - let fg = cell.getUnderlineColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { - fg += 8; - } - this._ctx.fillStyle = this._colors.ansi[fg].css; - } - } - switch (cell.extended.underlineStyle) { - case UnderlineStyle.DOUBLE: - this._fillBottomLineAtCells(x, y, cell.getWidth(), -this._coreBrowserService.dpr); - this._fillBottomLineAtCells(x, y, cell.getWidth(), this._coreBrowserService.dpr); - break; - case UnderlineStyle.CURLY: - this._curlyUnderlineAtCell(x, y, cell.getWidth()); - break; - case UnderlineStyle.DOTTED: - this._dottedUnderlineAtCell(x, y, cell.getWidth()); - break; - case UnderlineStyle.DASHED: - this._dashedUnderlineAtCell(x, y, cell.getWidth()); - break; - case UnderlineStyle.SINGLE: - default: - this._fillBottomLineAtCells(x, y, cell.getWidth()); - break; - } - } - this._ctx.restore(); - } - }); + this._forEachCell(firstRow, lastRow, (cell, x, y) => this._drawChars(cell, x, y)); } public onGridChanged(firstRow: number, lastRow: number): void { diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 0840b035d..478254d7f 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -336,6 +336,8 @@ export class TextureAtlas implements ITextureAtlas { private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; + console.log(`draw to cache "${chars}"`, bg, fg, ext); + this.hasCanvasChanged = true; // Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used From f2ff3a6a7ab72c4d8dcaa220d8bb0b6c0621af49 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 14:21:06 -0700 Subject: [PATCH 44/56] Add sgr attr test --- demo/client.ts | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ demo/index.html | 1 + 2 files changed, 61 insertions(+) diff --git a/demo/client.ts b/demo/client.ts index 8bff46907..4c3019a2e 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -222,6 +222,7 @@ if (document.location.pathname === '/test') { document.getElementById('underline-test').addEventListener('click', underlineTest); document.getElementById('ansi-colors').addEventListener('click', ansiColorsTest); document.getElementById('osc-hyperlinks').addEventListener('click', addAnsiHyperlink); + document.getElementById('sgr-test').addEventListener('click', sgrTest); document.getElementById('add-decoration').addEventListener('click', addDecoration); document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); } @@ -885,6 +886,65 @@ function ansiColorsTest(): void { } } +function writeTestString(): string { + let alphabet = ''; + for (let i = 97; i < 123; i++) { + alphabet += String.fromCharCode(i); + } + let numbers = ''; + for (let i = 0; i < 10; i++) { + numbers += i.toString(); + } + return `${alphabet} ${numbers} 汉语 한국어 👽`; +} +const testString = writeTestString(); + +function sgrTest(): void { + term.write('\n\n\r'); + term.writeln(`Character Attributes (SGR, Select Graphic Rendition)`); + const entries: { ps: number, name: string }[] = [ + { ps: 0, name: 'Normal' }, + { ps: 1, name: 'Bold' }, + { ps: 2, name: 'Faint/dim' }, + { ps: 3, name: 'Italicized' }, + { ps: 4, name: 'Underlined' }, + { ps: 5, name: 'Blink' }, + { ps: 7, name: 'Inverse' }, + { ps: 8, name: 'Invisible' }, + { ps: 9, name: 'Crossed-out characters' }, + { ps: 21, name: 'Doubly-underlined' }, + { ps: 22, name: 'Normal' }, + { ps: 23, name: 'Not italicized' }, + { ps: 24, name: 'Not underlined' }, + { ps: 25, name: 'Steady (not blink)' }, + { ps: 27, name: 'Positive (not inverse)' }, + { ps: 28, name: 'Visible (not hidden)' }, + { ps: 29, name: 'Not crossed-out' }, + { ps: 30, name: 'Foreground Black' }, + { ps: 31, name: 'Foreground Red' }, + { ps: 32, name: 'Foreground Green' }, + { ps: 33, name: 'Foreground Yellow' }, + { ps: 34, name: 'Foreground Blue' }, + { ps: 35, name: 'Foreground Magenta' }, + { ps: 36, name: 'Foreground Cyan' }, + { ps: 37, name: 'Foreground White' }, + { ps: 39, name: 'Foreground default' }, + { ps: 40, name: 'Background Black' }, + { ps: 41, name: 'Background Red' }, + { ps: 42, name: 'Background Green' }, + { ps: 43, name: 'Background Yellow' }, + { ps: 44, name: 'Background Blue' }, + { ps: 45, name: 'Background Magenta' }, + { ps: 46, name: 'Background Cyan' }, + { ps: 47, name: 'Background White' }, + { ps: 49, name: 'Background default' } + ]; + const maxNameLength = entries.reduce((p, c) => Math.max(c.name.length, p), 0); + for (const e of entries) { + term.writeln(`\x1b[0m\x1b[${e.ps}m ${e.name.padEnd(maxNameLength, ' ')} - ${testString}\x1b[0m`); + } +} + function addAnsiHyperlink(): void { term.write('\n\n\r'); term.writeln(`Regular link with no id:`); diff --git a/demo/index.html b/demo/index.html index 412892210..a65b8bbc2 100644 --- a/demo/index.html +++ b/demo/index.html @@ -79,6 +79,7 @@
+
From c741c13409246ce721130a5d8613d8fa890fb165 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 14:25:08 -0700 Subject: [PATCH 45/56] Add ps to output string --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 1 + demo/client.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 3ab8ea5af..4c92bca4c 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -387,6 +387,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Verify fg override // TODO: Verify bg override // TODO: Verify min contrast ratio + // TODO: Verify emoji } /** diff --git a/demo/client.ts b/demo/client.ts index 4c3019a2e..593b0da6d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -941,7 +941,7 @@ function sgrTest(): void { ]; const maxNameLength = entries.reduce((p, c) => Math.max(c.name.length, p), 0); for (const e of entries) { - term.writeln(`\x1b[0m\x1b[${e.ps}m ${e.name.padEnd(maxNameLength, ' ')} - ${testString}\x1b[0m`); + term.writeln(`\x1b[0m\x1b[${e.ps}m ${e.ps.toString().padEnd(2, ' ')} ${e.name.padEnd(maxNameLength, ' ')} - ${testString}\x1b[0m`); } } From 9259ec258fbbd90a025d2824780f8cae4f09b2a2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 14:27:43 -0700 Subject: [PATCH 46/56] Fix combined chars in canvas --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 4c92bca4c..42303f059 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -8,7 +8,7 @@ import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; @@ -368,7 +368,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { * the character atlas to reduce draw time. */ protected _drawChars(cell: ICellData, x: number, y: number): void { - const glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + const chars = cell.getChars(); + let glyph: IRasterizedGlyph; + if (chars && chars.length > 1) { + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + } else { + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + } this._ctx.save(); this._clipRow(y); this._ctx.drawImage( @@ -387,7 +393,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Verify fg override // TODO: Verify bg override // TODO: Verify min contrast ratio - // TODO: Verify emoji } /** From 6316eee67296d7e2f7a21b53437c447051886e02 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 21:32:24 -0700 Subject: [PATCH 47/56] Comment out debug log --- src/browser/renderer/shared/TextureAtlas.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 478254d7f..c4bffe8d3 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -336,7 +336,8 @@ export class TextureAtlas implements ITextureAtlas { private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; - console.log(`draw to cache "${chars}"`, bg, fg, ext); + // Uncomment for debugging + // console.log(`draw to cache "${chars}"`, bg, fg, ext); this.hasCanvasChanged = true; From 03e3105c55fb202939a2f81f59e707f0041d2130 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:10:27 -0700 Subject: [PATCH 48/56] Fix fg/bg overrides in canvas --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 207 ++++++++++++++++-- addons/xterm-addon-webgl/src/RenderModel.ts | 3 +- addons/xterm-addon-webgl/src/Types.d.ts | 13 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 - src/browser/renderer/shared/Types.d.ts | 11 + 5 files changed, 200 insertions(+), 37 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 42303f059..586be1675 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -8,17 +8,24 @@ import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { BgFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { Attributes, BgFlags, FgFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { IGlyphIdentifier } from './atlas/Types'; import { IRenderLayer } from './Types'; +// Work variables to avoid garbage collection +let $fg = 0; +let $bg = 0; +let $hasFg = false; +let $hasBg = false; +let $isSelected = false; + export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; @@ -32,22 +39,19 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionStart: [number, number] | undefined; protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; + protected _selectionModel: ISelectionRenderModel = { + hasSelection: false, + columnSelectMode: false, + viewportStartRow: 0, + viewportEndRow: 0, + viewportCappedStartRow: 0, + viewportCappedEndRow: 0, + startCol: 0, + endCol: 0 + }; protected _charAtlas!: ITextureAtlas; - /** - * An object that's reused when drawing glyphs in order to reduce GC. - */ - private _currentGlyphIdentifier: IGlyphIdentifier = { - chars: '', - code: 0, - bg: 0, - fg: 0, - bold: false, - dim: false, - italic: false - }; - public get canvas(): HTMLCanvasElement { return this._canvas; } public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.cacheCanvas!; } @@ -91,9 +95,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onGridChanged(startRow: number, endRow: number): void {} public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + // TODO: Remove these other variables in favor of the selection model this._selectionStart = start; this._selectionEnd = end; this._columnSelectMode = columnSelectMode; + this._updateSelectionModel(start, end, columnSelectMode); } public setColors(colorSet: IColorSet): void { @@ -127,7 +133,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - // this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } @@ -363,17 +368,20 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } + private _workColors: { fg: number, bg: number, ext: number } = { fg: 0, bg: 0, ext: 0 }; + /** * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. */ protected _drawChars(cell: ICellData, x: number, y: number): void { const chars = cell.getChars(); + this._loadColorsForCell(x, y, cell, this._workColors); let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._workColors.bg, this._workColors.fg, this._workColors.ext); } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._workColors.bg, this._workColors.fg, this._workColors.ext); } this._ctx.save(); this._clipRow(y); @@ -389,10 +397,165 @@ export abstract class BaseRenderLayer implements IRenderLayer { glyph.size.y ); this._ctx.restore(); - // TODO: Verify selection - // TODO: Verify fg override - // TODO: Verify bg override - // TODO: Verify min contrast ratio + // TODO: Move both renderers to use shared load color code + } + + /** + * Loads colors for the cell into the work colors object. This resolves overrides/inverse if + * necessary which is why the work cell object is not used. + */ + private _loadColorsForCell(x: number, y: number, cell: ICellData, workColors: { fg: number, bg: number, ext: number }): void { + workColors.bg = cell.bg; + workColors.fg = cell.fg; + workColors.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0; + // Get any foreground/background overrides, this happens on the model to avoid spreading + // override logic throughout the different sub-renderers + + // Reset overrides work variables + $bg = 0; + $fg = 0; + $hasBg = false; + $hasFg = false; + $isSelected = false; + + // Apply decorations on the bottom layer + this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { + if (d.backgroundColorRGB) { + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; + } + if (d.foregroundColorRGB) { + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + }); + + // TODO: Selection? + // Apply the selection color if needed + $isSelected = this._isCellSelected(x, y); + if ($isSelected) { + $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + $hasBg = true; + if (this._colors.selectionForeground) { + $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + } + + // Apply decorations on the top layer + this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { + if (d.backgroundColorRGB) { + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; + } + if (d.foregroundColorRGB) { + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + }); + + // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag + // ahead of time in order to use the correct cache key + if ($hasBg) { + if ($isSelected) { + // Non-RGB attributes from model + force non-dim + override + force RGB color mode + $bg = (cell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; + } else { + // Non-RGB attributes from model + override + force RGB color mode + $bg = (cell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; + } + } + if ($hasFg) { + // Non-RGB attributes from model + force disable inverse + override + force RGB color mode + $fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; + } + + // Handle case where inverse was specified by only one of bg override or fg override was set, + // resolving the other inverse color and setting the inverse flag if needed. + if (workColors.fg & FgFlags.INVERSE) { + if ($hasBg && !$hasFg) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + $fg = (workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + $fg = (workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + $hasFg = true; + } + if (!$hasBg && $hasFg) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + $bg = (workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + $bg = (workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + $hasBg = true; + } + } + + // Use the override if it exists + workColors.bg = $hasBg ? $bg : workColors.bg; + workColors.fg = $hasFg ? $fg : workColors.fg; + } + + private _isCellSelected(x: number, y: number): boolean { + if (!this._selectionStart) { + return false; + } + y -= this._terminal.buffer.active.viewportY; + if (this._selectionModel.columnSelectMode) { + if (this._selectionModel.startCol <= this._selectionModel.endCol) { + return x >= this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && + x < this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; + } + return x < this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && + x >= this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; + } + return (y > this._selectionModel.viewportStartRow && y < this._selectionModel.viewportEndRow) || + (this._selectionModel.viewportStartRow === this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol && x < this._selectionModel.endCol) || + (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportEndRow && x < this._selectionModel.endCol) || + (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol); + } + + private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + const terminal = this._terminal; + + // Selection does not exist + if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { + this._clearSelectionMoidel(); + return; + } + + // Translate from buffer position to viewport position + const viewportStartRow = start[1] - terminal.buffer.active.viewportY; + const viewportEndRow = end[1] - terminal.buffer.active.viewportY; + const viewportCappedStartRow = Math.max(viewportStartRow, 0); + const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + + // No need to draw the selection + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + this._clearSelectionMoidel(); + return; + } + + this._selectionModel.hasSelection = true; + this._selectionModel.columnSelectMode = columnSelectMode; + this._selectionModel.viewportStartRow = viewportStartRow; + this._selectionModel.viewportEndRow = viewportEndRow; + this._selectionModel.viewportCappedStartRow = viewportCappedStartRow; + this._selectionModel.viewportCappedEndRow = viewportCappedEndRow; + this._selectionModel.startCol = start[0]; + this._selectionModel.endCol = end[0]; + } + + private _clearSelectionMoidel(): void { + this._selectionModel.hasSelection = false; + this._selectionModel.viewportStartRow = 0; + this._selectionModel.viewportEndRow = 0; + this._selectionModel.viewportCappedStartRow = 0; + this._selectionModel.viewportCappedEndRow = 0; + this._selectionModel.startCol = 0; + this._selectionModel.endCol = 0; } /** diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index 2969a6d17..ecb25aa9c 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -3,8 +3,9 @@ * @license MIT */ -import { IRenderModel, ISelectionRenderModel } from './Types'; +import { IRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; export const RENDER_MODEL_BG_OFFSET = 1; diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index d09f43e9e..6e3a18fc5 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -3,23 +3,14 @@ * @license MIT */ +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; + export interface IRenderModel { cells: Uint32Array; lineLengths: Uint32Array; selection: ISelectionRenderModel; } -export interface ISelectionRenderModel { - hasSelection: boolean; - columnSelectMode: boolean; - viewportStartRow: number; - viewportEndRow: number; - viewportCappedStartRow: number; - viewportCappedEndRow: number; - startCol: number; - endCol: number; -} - export interface IWebGL2RenderingContext extends WebGLRenderingContext { vertexAttribDivisor(index: number, divisor: number): void; createVertexArray(): IWebGLVertexArrayObject; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index e3e38575d..f0ee105c4 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -275,9 +275,6 @@ export class WebglRenderer extends Disposable implements IRenderer { } const atlas = acquireTextureAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, this._coreBrowserService.dpr); - if (!('getRasterizedGlyph' in atlas)) { - throw new Error('The webgl renderer only works with the webgl char atlas'); - } if (this._charAtlas !== atlas) { this.onChangeTextureAtlas.fire(atlas.cacheCanvas); } diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 20c1357aa..81a025f2f 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -139,3 +139,14 @@ export interface IBoundingBox { right: number; bottom: number; } + +export interface ISelectionRenderModel { + hasSelection: boolean; + columnSelectMode: boolean; + viewportStartRow: number; + viewportEndRow: number; + viewportCappedStartRow: number; + viewportCappedEndRow: number; + startCol: number; + endCol: number; +} From 83c09c34dbdd4a9419031ae9daf82ff2eef2fec4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:23:09 -0700 Subject: [PATCH 49/56] Share selection render model code --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 78 +---------------- addons/xterm-addon-webgl/src/RenderModel.ts | 22 +---- addons/xterm-addon-webgl/src/WebglRenderer.ts | 56 +----------- .../renderer/shared/SelectionRenderModel.ts | 85 +++++++++++++++++++ src/browser/renderer/shared/Types.d.ts | 21 +++-- 5 files changed, 106 insertions(+), 156 deletions(-) create mode 100644 src/browser/renderer/shared/SelectionRenderModel.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 586be1675..c0e20abd5 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -9,6 +9,7 @@ import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; @@ -16,7 +17,6 @@ import { Attributes, BgFlags, FgFlags, WHITESPACE_CELL_CODE } from 'common/buffe import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; -import { IGlyphIdentifier } from './atlas/Types'; import { IRenderLayer } from './Types'; // Work variables to avoid garbage collection @@ -39,16 +39,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionStart: [number, number] | undefined; protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _selectionModel: ISelectionRenderModel = { - hasSelection: false, - columnSelectMode: false, - viewportStartRow: 0, - viewportEndRow: 0, - viewportCappedStartRow: 0, - viewportCappedEndRow: 0, - startCol: 0, - endCol: 0 - }; + protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); protected _charAtlas!: ITextureAtlas; @@ -99,7 +90,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._selectionStart = start; this._selectionEnd = end; this._columnSelectMode = columnSelectMode; - this._updateSelectionModel(start, end, columnSelectMode); + this._selectionModel.update(this._terminal, start, end, columnSelectMode); } public setColors(colorSet: IColorSet): void { @@ -430,9 +421,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { } }); - // TODO: Selection? // Apply the selection color if needed - $isSelected = this._isCellSelected(x, y); + $isSelected = this._selectionModel.isCellSelected(this._terminal, x, y); if ($isSelected) { $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; $hasBg = true; @@ -498,66 +488,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { workColors.fg = $hasFg ? $fg : workColors.fg; } - private _isCellSelected(x: number, y: number): boolean { - if (!this._selectionStart) { - return false; - } - y -= this._terminal.buffer.active.viewportY; - if (this._selectionModel.columnSelectMode) { - if (this._selectionModel.startCol <= this._selectionModel.endCol) { - return x >= this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && - x < this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; - } - return x < this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && - x >= this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; - } - return (y > this._selectionModel.viewportStartRow && y < this._selectionModel.viewportEndRow) || - (this._selectionModel.viewportStartRow === this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol && x < this._selectionModel.endCol) || - (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportEndRow && x < this._selectionModel.endCol) || - (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol); - } - - private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - const terminal = this._terminal; - - // Selection does not exist - if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { - this._clearSelectionMoidel(); - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.active.viewportY; - const viewportEndRow = end[1] - terminal.buffer.active.viewportY; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { - this._clearSelectionMoidel(); - return; - } - - this._selectionModel.hasSelection = true; - this._selectionModel.columnSelectMode = columnSelectMode; - this._selectionModel.viewportStartRow = viewportStartRow; - this._selectionModel.viewportEndRow = viewportEndRow; - this._selectionModel.viewportCappedStartRow = viewportCappedStartRow; - this._selectionModel.viewportCappedEndRow = viewportCappedEndRow; - this._selectionModel.startCol = start[0]; - this._selectionModel.endCol = end[0]; - } - - private _clearSelectionMoidel(): void { - this._selectionModel.hasSelection = false; - this._selectionModel.viewportStartRow = 0; - this._selectionModel.viewportEndRow = 0; - this._selectionModel.viewportCappedStartRow = 0; - this._selectionModel.viewportCappedEndRow = 0; - this._selectionModel.startCol = 0; - this._selectionModel.endCol = 0; - } - /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. * @param y The row to clip. diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index ecb25aa9c..b1542d984 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -6,6 +6,7 @@ import { IRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; export const RENDER_MODEL_BG_OFFSET = 1; @@ -22,16 +23,7 @@ export class RenderModel implements IRenderModel { constructor() { this.cells = new Uint32Array(0); this.lineLengths = new Uint32Array(0); - this.selection = { - hasSelection: false, - columnSelectMode: false, - viewportStartRow: 0, - viewportEndRow: 0, - viewportCappedStartRow: 0, - viewportCappedEndRow: 0, - startCol: 0, - endCol: 0 - }; + this.selection = createSelectionRenderModel(); } public resize(cols: number, rows: number): void { @@ -46,14 +38,4 @@ export class RenderModel implements IRenderModel { fill(this.cells, 0, 0); fill(this.lineLengths, 0, 0); } - - public clearSelection(): void { - this.selection.hasSelection = false; - this.selection.viewportStartRow = 0; - this.selection.viewportEndRow = 0; - this.selection.viewportCappedStartRow = 0; - this.selection.viewportCappedEndRow = 0; - this.selection.startCol = 0; - this.selection.endCol = 0; - } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index f0ee105c4..d75129089 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -229,7 +229,7 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.onSelectionChanged(this._terminal, start, end, columnSelectMode); } - this._updateSelectionModel(start, end, columnSelectMode); + this._model.selection.update(this._terminal, start, end, columnSelectMode); this._requestRedrawViewport(); } @@ -336,7 +336,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Tell renderer the frame is beginning if (this._glyphRenderer.beginFrame()) { this._clearModel(true); - this._updateSelectionModel(undefined, undefined); + this._model.selection.clear(); } // Update model to reflect what's drawn @@ -484,7 +484,7 @@ export class WebglRenderer extends Disposable implements IRenderer { }); // Apply the selection color if needed - $isSelected = this._isCellSelected(x, y); + $isSelected = this._model.selection.isCellSelected(this._terminal, x, y); if ($isSelected) { $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; $hasBg = true; @@ -550,56 +550,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.fg = $hasFg ? $fg : this._workColors.fg; } - private _isCellSelected(x: number, y: number): boolean { - if (!this._model.selection.hasSelection) { - return false; - } - y -= this._terminal.buffer.active.viewportY; - if (this._model.selection.columnSelectMode) { - if (this._model.selection.startCol <= this._model.selection.endCol) { - return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow && - x < this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow; - } - return x < this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow && - x >= this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow; - } - return (y > this._model.selection.viewportStartRow && y < this._model.selection.viewportEndRow) || - (this._model.selection.viewportStartRow === this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol && x < this._model.selection.endCol) || - (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportEndRow && x < this._model.selection.endCol) || - (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol); - } - - private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - const terminal = this._terminal; - - // Selection does not exist - if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { - this._model.clearSelection(); - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.active.viewportY; - const viewportEndRow = end[1] - terminal.buffer.active.viewportY; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { - this._model.clearSelection(); - return; - } - - this._model.selection.hasSelection = true; - this._model.selection.columnSelectMode = columnSelectMode; - this._model.selection.viewportStartRow = viewportStartRow; - this._model.selection.viewportEndRow = viewportEndRow; - this._model.selection.viewportCappedStartRow = viewportCappedStartRow; - this._model.selection.viewportCappedEndRow = viewportCappedEndRow; - this._model.selection.startCol = start[0]; - this._model.selection.endCol = end[0]; - } - /** * Recalculates the character and canvas dimensions. */ diff --git a/src/browser/renderer/shared/SelectionRenderModel.ts b/src/browser/renderer/shared/SelectionRenderModel.ts new file mode 100644 index 000000000..8a6e8b857 --- /dev/null +++ b/src/browser/renderer/shared/SelectionRenderModel.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { Terminal } from 'xterm'; + +class SelectionRenderModel implements ISelectionRenderModel { + public hasSelection!: boolean; + public columnSelectMode!: boolean; + public viewportStartRow!: number; + public viewportEndRow!: number; + public viewportCappedStartRow!: number; + public viewportCappedEndRow!: number; + public startCol!: number; + public endCol!: number; + + constructor() { + this.clear(); + } + + public clear(): void { + this.hasSelection = false; + this.columnSelectMode = false; + this.viewportStartRow = 0; + this.viewportEndRow = 0; + this.viewportCappedStartRow = 0; + this.viewportCappedEndRow = 0; + this.startCol = 0; + this.endCol = 0; + } + + public update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + // Selection does not exist + if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { + this.clear(); + return; + } + + // Translate from buffer position to viewport position + const viewportStartRow = start[1] - terminal.buffer.active.viewportY; + const viewportEndRow = end[1] - terminal.buffer.active.viewportY; + const viewportCappedStartRow = Math.max(viewportStartRow, 0); + const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + + // No need to draw the selection + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + this.clear(); + return; + } + + this.hasSelection = true; + this.columnSelectMode = columnSelectMode; + this.viewportStartRow = viewportStartRow; + this.viewportEndRow = viewportEndRow; + this.viewportCappedStartRow = viewportCappedStartRow; + this.viewportCappedEndRow = viewportCappedEndRow; + this.startCol = start[0]; + this.endCol = end[0]; + } + + public isCellSelected(terminal: Terminal, x: number, y: number): boolean { + if (!this.hasSelection) { + return false; + } + y -= terminal.buffer.active.viewportY; + if (this.columnSelectMode) { + if (this.startCol <= this.endCol) { + return x >= this.startCol && y >= this.viewportCappedStartRow && + x < this.endCol && y <= this.viewportCappedEndRow; + } + return x < this.startCol && y >= this.viewportCappedStartRow && + x >= this.endCol && y <= this.viewportCappedEndRow; + } + return (y > this.viewportStartRow && y < this.viewportEndRow) || + (this.viewportStartRow === this.viewportEndRow && y === this.viewportStartRow && x >= this.startCol && x < this.endCol) || + (this.viewportStartRow < this.viewportEndRow && y === this.viewportEndRow && x < this.endCol) || + (this.viewportStartRow < this.viewportEndRow && y === this.viewportStartRow && x >= this.startCol); + } +} + +export function createSelectionRenderModel(): ISelectionRenderModel { + return new SelectionRenderModel(); +} diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 81a025f2f..5e29411ae 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { FontWeight } from 'xterm'; +import { FontWeight, Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { IDisposable } from 'common/Types'; import { IEvent } from 'common/EventEmitter'; @@ -141,12 +141,15 @@ export interface IBoundingBox { } export interface ISelectionRenderModel { - hasSelection: boolean; - columnSelectMode: boolean; - viewportStartRow: number; - viewportEndRow: number; - viewportCappedStartRow: number; - viewportCappedEndRow: number; - startCol: number; - endCol: number; + readonly hasSelection: boolean; + readonly columnSelectMode: boolean; + readonly viewportStartRow: number; + readonly viewportEndRow: number; + readonly viewportCappedStartRow: number; + readonly viewportCappedEndRow: number; + readonly startCol: number; + readonly endCol: number; + clear(): void; + update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void; + isCellSelected(terminal: Terminal, x: number, y: number): boolean; } From 0d7e18d236a85df88b60e9b528863f1a4be5b4fc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:41:27 -0700 Subject: [PATCH 50/56] Add CellColorResolver to share load color code --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 108 +---------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 170 ++++-------------- .../renderer/shared/CellColorResolver.ts | 136 ++++++++++++++ 3 files changed, 177 insertions(+), 237 deletions(-) create mode 100644 src/browser/renderer/shared/CellColorResolver.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index c0e20abd5..ffea9f0ee 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -18,6 +18,7 @@ import { IBufferService, IDecorationService, IOptionsService } from 'common/serv import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; +import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; // Work variables to avoid garbage collection let $fg = 0; @@ -40,6 +41,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); + private _cellColorResolver: CellColorResolver; protected _charAtlas!: ITextureAtlas; @@ -58,6 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected readonly _decorationService: IDecorationService, protected readonly _coreBrowserService: ICoreBrowserService ) { + this._cellColorResolver = new CellColorResolver(this._terminal, this._colors, this._selectionModel, this._decorationService, this._coreBrowserService); this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); @@ -359,20 +362,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } - private _workColors: { fg: number, bg: number, ext: number } = { fg: 0, bg: 0, ext: 0 }; - /** * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. */ protected _drawChars(cell: ICellData, x: number, y: number): void { const chars = cell.getChars(); - this._loadColorsForCell(x, y, cell, this._workColors); + this._cellColorResolver.resolve(cell, x, y); let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._workColors.bg, this._workColors.fg, this._workColors.ext); + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext); } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._workColors.bg, this._workColors.fg, this._workColors.ext); + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext); } this._ctx.save(); this._clipRow(y); @@ -391,103 +392,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Move both renderers to use shared load color code } - /** - * Loads colors for the cell into the work colors object. This resolves overrides/inverse if - * necessary which is why the work cell object is not used. - */ - private _loadColorsForCell(x: number, y: number, cell: ICellData, workColors: { fg: number, bg: number, ext: number }): void { - workColors.bg = cell.bg; - workColors.fg = cell.fg; - workColors.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0; - // Get any foreground/background overrides, this happens on the model to avoid spreading - // override logic throughout the different sub-renderers - - // Reset overrides work variables - $bg = 0; - $fg = 0; - $hasBg = false; - $hasFg = false; - $isSelected = false; - - // Apply decorations on the bottom layer - this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { - if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasBg = true; - } - if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - }); - - // Apply the selection color if needed - $isSelected = this._selectionModel.isCellSelected(this._terminal, x, y); - if ($isSelected) { - $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; - $hasBg = true; - if (this._colors.selectionForeground) { - $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - } - - // Apply decorations on the top layer - this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { - if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasBg = true; - } - if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - }); - - // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag - // ahead of time in order to use the correct cache key - if ($hasBg) { - if ($isSelected) { - // Non-RGB attributes from model + force non-dim + override + force RGB color mode - $bg = (cell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; - } else { - // Non-RGB attributes from model + override + force RGB color mode - $bg = (cell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; - } - } - if ($hasFg) { - // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - $fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; - } - - // Handle case where inverse was specified by only one of bg override or fg override was set, - // resolving the other inverse color and setting the inverse flag if needed. - if (workColors.fg & FgFlags.INVERSE) { - if ($hasBg && !$hasFg) { - // Resolve bg color type (default color has a different meaning in fg vs bg) - if ((workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $fg = (workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; - } else { - $fg = (workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); - } - $hasFg = true; - } - if (!$hasBg && $hasFg) { - // Resolve bg color type (default color has a different meaning in fg vs bg) - if ((workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $bg = (workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; - } else { - $bg = (workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); - } - $hasBg = true; - } - } - - // Use the override if it exists - workColors.bg = $hasBg ? $bg : workColors.bg; - workColors.fg = $hasFg ? $fg : workColors.fg; - } - /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. * @param y The row to clip. diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d75129089..c75119467 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -3,34 +3,28 @@ * @license MIT */ -import { GlyphRenderer } from './GlyphRenderer'; -import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; -import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; -import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; -import { RectangleRenderer } from './RectangleRenderer'; -import { IWebGL2RenderingContext } from './Types'; -import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; -import { Disposable } from 'common/Lifecycle'; -import { Attributes, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Terminal, IEvent } from 'xterm'; -import { IRenderLayer } from './renderLayer/Types'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; -import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; -import { ITerminal, IColorSet } from 'browser/Types'; -import { EventEmitter, initEvent } from 'common/EventEmitter'; -import { CellData } from 'common/buffer/CellData'; import { addDisposableDomListener } from 'browser/Lifecycle'; +import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; +import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; +import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; -import { CharData, IBufferLine, ICellData } from 'common/Types'; +import { IColorSet, ITerminal } from 'browser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; +import { CellData } from 'common/buffer/CellData'; +import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; +import { initEvent } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; import { ICoreService, IDecorationService } from 'common/services/Services'; - -// Work variables to avoid garbage collection -let $fg = 0; -let $bg = 0; -let $hasFg = false; -let $hasBg = false; -let $isSelected = false; +import { CharData, IBufferLine, ICellData } from 'common/Types'; +import { Terminal } from 'xterm'; +import { GlyphRenderer } from './GlyphRenderer'; +import { RectangleRenderer } from './RectangleRenderer'; +import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; +import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; +import { IRenderLayer } from './renderLayer/Types'; +import { COMBINED_CHAR_BIT_MASK, RenderModel, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; +import { IWebGL2RenderingContext } from './Types'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -39,7 +33,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _model: RenderModel = new RenderModel(); private _workCell: CellData = new CellData(); - private _workColors: { fg: number, bg: number, ext: number } = { fg: 0, bg: 0, ext: 0 }; + private _cellColorResolver: CellColorResolver; private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; @@ -67,6 +61,8 @@ export class WebglRenderer extends Disposable implements IRenderer { ) { super(); + this._cellColorResolver = new CellColorResolver(this._terminal, this._colors, this._model.selection, this._decorationService, this._coreBrowserService); + this._core = (this._terminal as any)._core; this._renderLayers = [ @@ -155,6 +151,7 @@ export class WebglRenderer extends Disposable implements IRenderer { l.reset(this._terminal); } + this._cellColorResolver.setColors(colors); this._rectangleRenderer.setColors(); this._refreshCharAtlas(); @@ -372,11 +369,11 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.lineLengths[y] = 0; joinedRanges = this._characterJoinerService.getJoinedCharacters(row); for (x = 0; x < terminal.cols; x++) { - lastBg = this._workColors.bg; + lastBg = this._cellColorResolver.result.bg; line.loadCell(x, cell); if (x === 0) { - lastBg = this._workColors.bg; + lastBg = this._cellColorResolver.result.bg; } // If true, indicates that the current character(s) to draw were joined. @@ -407,7 +404,7 @@ export class WebglRenderer extends Disposable implements IRenderer { i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; // Load colors/resolve overrides into work colors - this._loadColorsForCell(x, row); + this._cellColorResolver.resolve(cell, x, row); if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; @@ -415,9 +412,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg && - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg && + this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) { continue; } @@ -428,11 +425,11 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workColors.bg; - this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workColors.fg; - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._workColors.ext; + this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; + this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer.updateCell(x, y, code, this._workColors.bg, this._workColors.fg, this._workColors.ext, chars, lastBg); + this._glyphRenderer.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); if (isJoined) { // Restore work cell @@ -443,9 +440,9 @@ export class WebglRenderer extends Disposable implements IRenderer { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); this._model.cells[j] = NULL_CELL_CODE; - this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workColors.bg; - this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workColors.fg; - this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._workColors.ext; + this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; + this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; + this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; } } } @@ -453,103 +450,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.updateBackgrounds(this._model); } - /** - * Loads colors for the cell into the work colors object. This resolves overrides/inverse if - * necessary which is why the work cell object is not used. - */ - private _loadColorsForCell(x: number, y: number): void { - this._workColors.bg = this._workCell.bg; - this._workColors.fg = this._workCell.fg; - this._workColors.ext = this._workCell.bg & BgFlags.HAS_EXTENDED ? this._workCell.extended.ext : 0; - // Get any foreground/background overrides, this happens on the model to avoid spreading - // override logic throughout the different sub-renderers - - // Reset overrides work variables - $bg = 0; - $fg = 0; - $hasBg = false; - $hasFg = false; - $isSelected = false; - - // Apply decorations on the bottom layer - this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { - if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasBg = true; - } - if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - }); - - // Apply the selection color if needed - $isSelected = this._model.selection.isCellSelected(this._terminal, x, y); - if ($isSelected) { - $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; - $hasBg = true; - if (this._colors.selectionForeground) { - $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - } - - // Apply decorations on the top layer - this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { - if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasBg = true; - } - if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - }); - - // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag - // ahead of time in order to use the correct cache key - if ($hasBg) { - if ($isSelected) { - // Non-RGB attributes from model + force non-dim + override + force RGB color mode - $bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; - } else { - // Non-RGB attributes from model + override + force RGB color mode - $bg = (this._workCell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; - } - } - if ($hasFg) { - // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - $fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; - } - - // Handle case where inverse was specified by only one of bg override or fg override was set, - // resolving the other inverse color and setting the inverse flag if needed. - if (this._workColors.fg & FgFlags.INVERSE) { - if ($hasBg && !$hasFg) { - // Resolve bg color type (default color has a different meaning in fg vs bg) - if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; - } else { - $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); - } - $hasFg = true; - } - if (!$hasBg && $hasFg) { - // Resolve bg color type (default color has a different meaning in fg vs bg) - if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; - } else { - $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); - } - $hasBg = true; - } - } - - // Use the override if it exists - this._workColors.bg = $hasBg ? $bg : this._workColors.bg; - this._workColors.fg = $hasFg ? $fg : this._workColors.fg; - } - /** * Recalculates the character and canvas dimensions. */ diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts new file mode 100644 index 000000000..b29475b94 --- /dev/null +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -0,0 +1,136 @@ +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { ICoreBrowserService } from 'browser/services/Services'; +import { IColorSet } from 'browser/Types'; +import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; +import { IDecorationService } from 'common/services/Services'; +import { ICellData } from 'common/Types'; +import { Terminal } from 'xterm'; + +// Work variables to avoid garbage collection +let $fg = 0; +let $bg = 0; +let $hasFg = false; +let $hasBg = false; +let $isSelected = false; + +export class CellColorResolver { + /** + * The shared result of the {@link resolve} call. This is only safe to use immediately after as + * any other calls will share object. + */ + public readonly result: { fg: number, bg: number, ext: number } = { + fg: 0, + bg: 0, + ext: 0 + }; + + constructor( + private readonly _terminal: Terminal, + private _colors: IColorSet, + private readonly _selectionRenderModel: ISelectionRenderModel, + private readonly _decorationService: IDecorationService, + private readonly _coreBrowserService: ICoreBrowserService + ) { + } + + public setColors(colors: IColorSet): void { + this._colors = colors; + } + + /** + * Resolves colors for the cell, putting the result into the shared {@link result}. This resolves + * overrides, inverse and selection for the cell which can then be used to feed into the renderer. + */ + public resolve(cell: ICellData, x: number, y: number): void { + this.result.bg = cell.bg; + this.result.fg = cell.fg; + this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0; + // Get any foreground/background overrides, this happens on the model to avoid spreading + // override logic throughout the different sub-renderers + + // Reset overrides work variables + $bg = 0; + $fg = 0; + $hasBg = false; + $hasFg = false; + $isSelected = false; + + // Apply decorations on the bottom layer + this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { + if (d.backgroundColorRGB) { + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; + } + if (d.foregroundColorRGB) { + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + }); + + // Apply the selection color if needed + $isSelected = this._selectionRenderModel.isCellSelected(this._terminal, x, y); + if ($isSelected) { + $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + $hasBg = true; + if (this._colors.selectionForeground) { + $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + } + + // Apply decorations on the top layer + this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { + if (d.backgroundColorRGB) { + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; + } + if (d.foregroundColorRGB) { + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + }); + + // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag + // ahead of time in order to use the correct cache key + if ($hasBg) { + if ($isSelected) { + // Non-RGB attributes from model + force non-dim + override + force RGB color mode + $bg = (cell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; + } else { + // Non-RGB attributes from model + override + force RGB color mode + $bg = (cell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; + } + } + if ($hasFg) { + // Non-RGB attributes from model + force disable inverse + override + force RGB color mode + $fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; + } + + // Handle case where inverse was specified by only one of bg override or fg override was set, + // resolving the other inverse color and setting the inverse flag if needed. + if (this.result.fg & FgFlags.INVERSE) { + if ($hasBg && !$hasFg) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this.result.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + $hasFg = true; + } + if (!$hasBg && $hasFg) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((this.result.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this.result.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + $hasBg = true; + } + } + + // Use the override if it exists + this.result.bg = $hasBg ? $bg : this.result.bg; + this.result.fg = $hasFg ? $fg : this.result.fg; + } +} From 622acaaad75a776f317bacc8d623169699a3327d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:45:19 -0700 Subject: [PATCH 51/56] Remove old selection start/end values --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 16 +--------------- .../src/SelectionRenderLayer.ts | 8 ++++---- .../renderer/shared/SelectionRenderModel.ts | 6 ++++++ src/browser/renderer/shared/Types.d.ts | 2 ++ 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ffea9f0ee..899752410 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -13,20 +13,13 @@ import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRen import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { Attributes, BgFlags, FgFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; -// Work variables to avoid garbage collection -let $fg = 0; -let $bg = 0; -let $hasFg = false; -let $hasBg = false; -let $isSelected = false; - export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; @@ -37,9 +30,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - protected _selectionStart: [number, number] | undefined; - protected _selectionEnd: [number, number] | undefined; - protected _columnSelectMode: boolean = false; protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); private _cellColorResolver: CellColorResolver; @@ -89,10 +79,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onGridChanged(startRow: number, endRow: number): void {} public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - // TODO: Remove these other variables in favor of the selection model - this._selectionStart = start; - this._selectionEnd = end; - this._columnSelectMode = columnSelectMode; this._selectionModel.update(this._terminal, start, end, columnSelectMode); } diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 6a803342a..759b0a123 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -47,8 +47,8 @@ export class SelectionRenderLayer extends BaseRenderLayer { super.resize(dim); // On resize use the base render layer's cached selection values since resize clears _state // inside reset. - if (this._selectionStart && this._selectionEnd) { - this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode); + if (this._selectionModel.selectionStart && this._selectionModel.selectionEnd) { + this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); } } @@ -61,12 +61,12 @@ export class SelectionRenderLayer extends BaseRenderLayer { public onBlur(): void { this.reset(); - this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode); + this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); } public onFocus(): void { this.reset(); - this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode); + this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); } public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { diff --git a/src/browser/renderer/shared/SelectionRenderModel.ts b/src/browser/renderer/shared/SelectionRenderModel.ts index 8a6e8b857..db3757788 100644 --- a/src/browser/renderer/shared/SelectionRenderModel.ts +++ b/src/browser/renderer/shared/SelectionRenderModel.ts @@ -15,6 +15,8 @@ class SelectionRenderModel implements ISelectionRenderModel { public viewportCappedEndRow!: number; public startCol!: number; public endCol!: number; + public selectionStart: [number, number] | undefined; + public selectionEnd: [number, number] | undefined; constructor() { this.clear(); @@ -29,9 +31,13 @@ class SelectionRenderModel implements ISelectionRenderModel { this.viewportCappedEndRow = 0; this.startCol = 0; this.endCol = 0; + this.selectionStart = undefined; + this.selectionEnd = undefined; } public update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + this.selectionStart = start; + this.selectionEnd = end; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { this.clear(); diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 5e29411ae..7410b1758 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -149,6 +149,8 @@ export interface ISelectionRenderModel { readonly viewportCappedEndRow: number; readonly startCol: number; readonly endCol: number; + readonly selectionStart: [number, number] | undefined; + readonly selectionEnd: [number, number] | undefined; clear(): void; update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void; isCellSelected(terminal: Terminal, x: number, y: number): boolean; From 3b260a219302eaab0e590724253528234aab4cf8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:46:00 -0700 Subject: [PATCH 52/56] Fix resolved todo --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 899752410..ba385449a 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -375,7 +375,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { glyph.size.y ); this._ctx.restore(); - // TODO: Move both renderers to use shared load color code } /** From e0324e32a8313a33b1333c6f61b9246e9ace1cda Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:24:10 -0700 Subject: [PATCH 53/56] Bring back ImageBitmap optimization in canvas renderer It's more simple than it used to be, it will clear and debounce all glyphs when any change is made. --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 59 ++++++++++++++++++- src/browser/renderer/shared/Types.d.ts | 1 - 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ba385449a..2a9e09bac 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -32,6 +32,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); private _cellColorResolver: CellColorResolver; + private _bitmapGenerator?: BitmapGenerator; protected _charAtlas!: ITextureAtlas; @@ -115,6 +116,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); + this._bitmapGenerator = new BitmapGenerator(this._charAtlas.cacheCanvas); } public resize(dim: IRenderDimensions): void { @@ -353,6 +355,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { * the character atlas to reduce draw time. */ protected _drawChars(cell: ICellData, x: number, y: number): void { + if (this._charAtlas.hasCanvasChanged) { + this._bitmapGenerator?.refresh(); + this._charAtlas.hasCanvasChanged = false; + } const chars = cell.getChars(); this._cellColorResolver.resolve(cell, x, y); let glyph: IRasterizedGlyph; @@ -363,8 +369,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { } this._ctx.save(); this._clipRow(y); + // Draw the image, use the bitmap if it's available this._ctx.drawImage( - this._charAtlas!.cacheCanvas, + this._bitmapGenerator?.bitmap || this._charAtlas!.cacheCanvas, glyph.texturePosition.x, glyph.texturePosition.y, glyph.size.x, @@ -403,3 +410,53 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } +/** + * The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch + * the operation as window.createImageBitmap is asynchronous. + */ +const GLYPH_BITMAP_COMMIT_DELAY = 100; + +const enum BitmapGeneratorState { + IDLE = 0, + GENERATING = 1, + GENERATING_INVALID = 2 +} + +class BitmapGenerator { + private _state: BitmapGeneratorState = BitmapGeneratorState.IDLE; + private _commitTimeout: number | undefined = undefined; + private _bitmap: ImageBitmap | undefined = undefined; + public get bitmap(): ImageBitmap | undefined { return this._bitmap; } + + constructor(private readonly _canvas: HTMLCanvasElement) { + } + + public refresh(): void { + // Clear the bitmap immediately as it's stale + this._bitmap = undefined; + if (this._commitTimeout === undefined) { + this._commitTimeout = window.setTimeout(() => this._generate(), GLYPH_BITMAP_COMMIT_DELAY); + } + if (this._state === BitmapGeneratorState.GENERATING) { + this._state = BitmapGeneratorState.GENERATING_INVALID; + } + } + + private _generate(): void { + if (this._state === BitmapGeneratorState.IDLE) { + this._bitmap = undefined; + this._state = BitmapGeneratorState.GENERATING; + window.createImageBitmap(this._canvas).then(bitmap => { + if (this._state === BitmapGeneratorState.GENERATING_INVALID) { + this.refresh(); + } else { + this._bitmap = bitmap; + } + this._state = BitmapGeneratorState.IDLE; + }); + if (this._commitTimeout) { + this._commitTimeout = undefined; + } + } + } +} diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 7410b1758..7e2badcbc 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -77,7 +77,6 @@ export interface IRenderer extends IDisposable { export interface ITextureAtlas extends IDisposable { readonly cacheCanvas: HTMLCanvasElement; - // TODO: Add endFrame and make this readonly hasCanvasChanged: boolean; /** From ab0082b73a6cea83649d351ce144b3bbc9beb76c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 6 Oct 2022 09:14:50 -0700 Subject: [PATCH 54/56] Bump copyright year --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c4d2084e..c78ae76ed 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,6 @@ Do you use xterm.js in your application as well? Please [open a Pull Request](ht If you contribute code to this project, you implicitly allow your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. -Copyright (c) 2017-2019, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)
+Copyright (c) 2017-2022, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) From 4d97729010538e9de4e1a1ee0655495f18aa0697 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 6 Oct 2022 09:48:54 -0700 Subject: [PATCH 55/56] Revert "Merge pull request #4166 from Tyriar/event_with_emitter" This reverts commit d22f7c9edd46f5ae0bed05489c915bbde4b35759, reversing changes made to 1f8e6f095266a5236899a1ebe151983ad527b9da. --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 7 +- .../xterm-addon-canvas/src/CanvasRenderer.ts | 36 +++--- addons/xterm-addon-search/src/SearchAddon.ts | 11 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 14 ++- addons/xterm-addon-webgl/src/WebglRenderer.ts | 25 +++-- src/browser/Linkifier2.ts | 10 +- src/browser/Terminal.ts | 55 ++++++---- src/browser/TestUtils.test.ts | 20 ++-- .../decorations/BufferDecorationRenderer.ts | 4 +- src/browser/renderer/dom/DomRenderer.ts | 4 +- src/browser/services/CharSizeService.ts | 7 +- src/browser/services/RenderService.ts | 20 ++-- src/browser/services/SelectionService.ts | 28 +++-- src/common/CircularList.ts | 25 +++-- src/common/CoreTerminal.ts | 27 +++-- src/common/EventEmitter.ts | 34 +----- src/common/InputHandler.ts | 103 ++++++++++-------- src/common/TestUtils.test.ts | 22 ++-- src/common/Types.d.ts | 11 +- src/common/buffer/Buffer.test.ts | 6 +- src/common/buffer/Buffer.ts | 4 +- src/common/buffer/BufferReflow.ts | 2 +- src/common/buffer/BufferSet.ts | 11 +- src/common/buffer/Marker.ts | 7 +- src/common/input/WriteBuffer.ts | 7 +- src/common/public/BufferNamespaceApi.ts | 7 +- src/common/services/BufferService.ts | 14 ++- src/common/services/CoreMouseService.ts | 7 +- src/common/services/CoreService.ts | 17 +-- src/common/services/DecorationService.ts | 22 ++-- src/common/services/OptionsService.ts | 7 +- src/common/services/Services.ts | 4 +- src/common/services/UnicodeService.ts | 7 +- src/headless/Terminal.ts | 27 +++-- 34 files changed, 329 insertions(+), 283 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 1f214767b..36fa453d0 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -8,13 +8,14 @@ import { IColorSet } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; -import { forwardEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; export class CanvasAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: CanvasRenderer; - public readonly onChangeTextureAtlas = initEvent(); + private readonly _onChangeTextureAtlas = new EventEmitter(); + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; public activate(terminal: Terminal): void { if (!terminal.element) { @@ -33,7 +34,7 @@ export class CanvasAddon implements ITerminalAddon { const screenElement: HTMLElement = (terminal as any)._core.screenElement; const linkifier = (terminal as any)._core.linkifier2; this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); - forwardEvent(this._renderer.onChangeTextureAtlas, this.onChangeTextureAtlas); + forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index b7abab763..34e75c016 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -3,20 +3,20 @@ * @license MIT */ -import { TextRenderLayer } from './TextRenderLayer'; -import { SelectionRenderLayer } from './SelectionRenderLayer'; -import { CursorRenderLayer } from './CursorRenderLayer'; -import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; -import { IRenderLayer } from './Types'; -import { LinkRenderLayer } from './LinkRenderLayer'; -import { Disposable } from 'common/Lifecycle'; -import { IColorSet, ILinkifier2 } from 'browser/Types'; -import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; -import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services'; -import { removeTerminalFromCache } from './atlas/CharAtlasCache'; import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; +import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; +import { IColorSet, ILinkifier2 } from 'browser/Types'; +import { EventEmitter } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; +import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { Terminal } from 'xterm'; -import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter'; +import { removeTerminalFromCache } from './atlas/CharAtlasCache'; +import { CursorRenderLayer } from './CursorRenderLayer'; +import { LinkRenderLayer } from './LinkRenderLayer'; +import { SelectionRenderLayer } from './SelectionRenderLayer'; +import { TextRenderLayer } from './TextRenderLayer'; +import { IRenderLayer } from './Types'; export class CanvasRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -24,8 +24,10 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - public readonly onRequestRedraw = initEvent(); - public readonly onChangeTextureAtlas = initEvent(); + private readonly _onRequestRedraw = new EventEmitter(); + public readonly onRequestRedraw = this._onRequestRedraw.event; + private readonly _onChangeTextureAtlas = new EventEmitter(); + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; constructor( private readonly _terminal: Terminal, @@ -46,7 +48,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { new TextRenderLayer(this._terminal, this._screenElement, 0, this._colors, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._colors, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), new LinkRenderLayer(this._terminal, this._screenElement, 2, this._colors, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), - new CursorRenderLayer(this._terminal, this._screenElement, 3, this._colors, this.onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) + new CursorRenderLayer(this._terminal, this._screenElement, 3, this._colors, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) ]; this.dimensions = { scaledCharWidth: 0, @@ -130,7 +132,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode)); // Selection foreground requires a full re-render if (this._colors.selectionForeground) { - this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } @@ -203,6 +205,6 @@ export class CanvasRenderer extends Disposable implements IRenderer { } private _requestRedrawViewport(): void { - this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index d14e1fe76..689899ef1 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -4,7 +4,7 @@ */ import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm'; -import { initEvent } from 'common/EventEmitter'; +import { EventEmitter } from 'common/EventEmitter'; export interface ISearchOptions { regex?: boolean; @@ -72,7 +72,8 @@ export class SearchAddon implements ITerminalAddon { private _resultIndex: number | undefined; - public readonly onDidChangeResults = initEvent<{ resultIndex: number, resultCount: number } | undefined>(); + private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>(); + public readonly onDidChangeResults = this._onDidChangeResults.event; public activate(terminal: Terminal): void { this._terminal = terminal; @@ -88,7 +89,7 @@ export class SearchAddon implements ITerminalAddon { this._highlightTimeout = setTimeout(() => { this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true }); this._resultIndex = this._searchResults ? this._searchResults.size - 1 : -1; - this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 }); + this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 }); }, 200); } } @@ -324,9 +325,9 @@ export class SearchAddon implements ITerminalAddon { private _fireResults(term: string, found: boolean, searchOptions?: ISearchOptions): boolean { if (searchOptions?.decorations) { if (this._resultIndex !== undefined && this._searchResults?.size !== undefined) { - this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); + this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); } else { - this.onDidChangeResults.fire(undefined); + this._onDidChangeResults.fire(undefined); } } this._cachedSearchTerm = term; diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index b26c565c3..45858e8c8 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -7,7 +7,7 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; -import { EventEmitter, forwardEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { isSafari } from 'common/Platform'; import { ICoreService, IDecorationService } from 'common/services/Services'; @@ -15,12 +15,14 @@ export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - public readonly onChangeTextureAtlas = initEvent(); - public readonly onContextLoss = initEvent(); + private readonly _onChangeTextureAtlas = new EventEmitter(); + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; + private readonly _onContextLoss = new EventEmitter(); + public readonly onContextLoss = this._onContextLoss.event; constructor( private _preserveDrawingBuffer?: boolean - ) { } + ) {} public activate(terminal: Terminal): void { if (!terminal.element) { @@ -37,8 +39,8 @@ export class WebglAddon implements ITerminalAddon { const decorationService: IDecorationService = (terminal as any)._core._decorationService; const colors: IColorSet = (terminal as any)._core._colorManager.colors; this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer); - forwardEvent(this._renderer.onContextLoss, this.onContextLoss); - forwardEvent(this._renderer.onChangeTextureAtlas, this.onChangeTextureAtlas); + forwardEvent(this._renderer.onContextLoss, this._onContextLoss); + forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); renderService.setRenderer(this._renderer); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index c75119467..b22f3d5e1 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -13,7 +13,7 @@ import { IColorSet, ITerminal } from 'browser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { initEvent } from 'common/EventEmitter'; +import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { ICoreService, IDecorationService } from 'common/services/Services'; import { CharData, IBufferLine, ICellData } from 'common/Types'; @@ -46,9 +46,12 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - public readonly onChangeTextureAtlas = initEvent(); - public readonly onRequestRedraw = initEvent(); - public readonly onContextLoss = initEvent(); + private readonly _onChangeTextureAtlas = new EventEmitter(); + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; + private readonly _onRequestRedraw = new EventEmitter(); + public readonly onRequestRedraw = this._onRequestRedraw.event; + private readonly _onContextLoss = new EventEmitter(); + public readonly onContextLoss = this._onContextLoss.event; constructor( private _terminal: Terminal, @@ -67,7 +70,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers = [ new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core, this._coreBrowserService), - new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this.onRequestRedraw, this._coreBrowserService, coreService) + new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._onRequestRedraw, this._coreBrowserService, coreService) ]; this.dimensions = { scaledCharWidth: 0, @@ -107,7 +110,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._contextRestorationTimeout = setTimeout(() => { this._contextRestorationTimeout = undefined; console.warn('webgl context not restored; firing onContextLoss'); - this.onContextLoss.fire(e); + this._onContextLoss.fire(e); }, 3000 /* ms */); })); this.register(addDisposableDomListener(this._canvas, 'webglcontextrestored', (e) => { @@ -273,7 +276,7 @@ export class WebglRenderer extends Disposable implements IRenderer { const atlas = acquireTextureAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, this._coreBrowserService.dpr); if (this._charAtlas !== atlas) { - this.onChangeTextureAtlas.fire(atlas.cacheCanvas); + this._onChangeTextureAtlas.fire(atlas.cacheCanvas); } this._charAtlas = atlas; this._charAtlas.warmUp(); @@ -412,9 +415,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg && - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg && + this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) { continue; } @@ -519,7 +522,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _requestRedrawViewport(): void { - this.onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } } diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 844b64f82..cf5b9dd2e 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -7,7 +7,7 @@ import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent import { IDisposable } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -26,8 +26,10 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeProviderReplies: Map | undefined; private _activeLine: number = -1; - public readonly onShowLinkUnderline = this.register(initEvent()); - public readonly onHideLinkUnderline = this.register(initEvent()); + private readonly _onShowLinkUnderline = this.register(new EventEmitter()); + public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; + private readonly _onHideLinkUnderline = this.register(new EventEmitter()); + public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; constructor( @IBufferService private readonly _bufferService: IBufferService @@ -341,7 +343,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { const range = link.range; const scrollOffset = this._bufferService.buffer.ydisp; const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined); - const emitter = showEvent ? this.onShowLinkUnderline : this.onHideLinkUnderline; + const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline; emitter.fire(event); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index bc8b19cf9..be1f67778 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -37,7 +37,7 @@ import { ITheme, IMarker, IDisposable, ILinkProvider, IDecorationOptions, IDecor import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; @@ -122,16 +122,27 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; - public readonly onCursorMove = initEvent(); - public readonly onKey = initEvent<{ key: string, domEvent: KeyboardEvent }>(); - public readonly onRender = initEvent<{ start: number, end: number }>(); - public readonly onSelectionChange = initEvent(); - public readonly onTitleChange = initEvent(); - public readonly onBell = initEvent(); - public readonly onFocus = initEvent(); - public readonly onBlur = initEvent(); - public readonly onA11yChar = initEvent(); - public readonly onA11yTab = initEvent(); + private readonly _onCursorMove = new EventEmitter(); + public readonly onCursorMove = this._onCursorMove.event; + private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); + public readonly onKey = this._onKey.event; + private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); + public readonly onRender = this._onRender.event; + private readonly _onSelectionChange = new EventEmitter(); + public readonly onSelectionChange = this._onSelectionChange.event; + private readonly _onTitleChange = new EventEmitter(); + public readonly onTitleChange = this._onTitleChange.event; + private readonly _onBell = new EventEmitter(); + public readonly onBell = this._onBell.event; + + private readonly _onFocus = new EventEmitter(); + public readonly onFocus = this._onFocus.event; + private readonly _onBlur = new EventEmitter(); + public readonly onBlur = this._onBlur.event; + private readonly _onA11yCharEmitter = new EventEmitter(); + public readonly onA11yChar = this._onA11yCharEmitter.event; + private readonly _onA11yTabEmitter = new EventEmitter(); + public readonly onA11yTab = this._onA11yTabEmitter.event; /** * Creates a new `Terminal` object. @@ -158,16 +169,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this._instantiationService.setService(IDecorationService, this._decorationService); // Setup InputHandler listeners - this.register(this._inputHandler.onRequestBell(() => this.onBell.fire())); + this.register(this._inputHandler.onRequestBell(() => this._onBell.fire())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event))); - this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove)); - this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange)); - this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar)); - this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab)); + this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); // Setup listeners this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); @@ -315,7 +326,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.updateCursorStyle(ev); this.element!.classList.add('focus'); this._showCursor(); - this.onFocus.fire(); + this._onFocus.fire(); } /** @@ -338,7 +349,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.coreService.triggerDataEvent(C0.ESC + '[O'); } this.element!.classList.remove('focus'); - this.onBlur.fire(); + this._onBlur.fire(); } private _syncTextArea(): void { @@ -501,7 +512,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const renderer = this._createRenderer(); this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement)); this._instantiationService.setService(IRenderService, this._renderService); - this.register(this._renderService.onRenderedViewportChange(e => this.onRender.fire(e))); + this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); this._compositionView = document.createElement('div'); @@ -541,7 +552,7 @@ export class Terminal extends CoreTerminal implements ITerminal { )); this._instantiationService.setService(ISelectionService, this._selectionService); this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent))); - this.register(this._selectionService.onSelectionChange(() => this.onSelectionChange.fire())); + this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire())); this.register(this._selectionService.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode))); this.register(this._selectionService.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it @@ -1090,7 +1101,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.textarea!.value = ''; } - this.onKey.fire({ key: result.key, domEvent: event }); + this._onKey.fire({ key: result.key, domEvent: event }); this._showCursor(); this.coreService.triggerDataEvent(result.key, true); @@ -1173,7 +1184,7 @@ export class Terminal extends CoreTerminal implements ITerminal { key = String.fromCharCode(key); - this.onKey.fire({ key, domEvent: ev }); + this._onKey.fire({ key, domEvent: ev }); this._showCursor(); this.coreService.triggerDataEvent(key, true); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 18c51864a..1717c2048 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; -import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; @@ -352,7 +352,7 @@ export class MockCoreBrowserService implements ICoreBrowserService { export class MockCharSizeService implements ICharSizeService { public serviceBrand: undefined; public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - public onCharSizeChange: IEvent = initEvent(); + public onCharSizeChange: IEvent = new EventEmitter().event; constructor(public width: number, public height: number) {} public measure(): void {} } @@ -370,10 +370,10 @@ export class MockMouseService implements IMouseService { export class MockRenderService implements IRenderService { public serviceBrand: undefined; - public onDimensionsChange: IEvent = initEvent(); - public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>(); - public onRender: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>(); - public onRefreshRequest: IEvent<{ start: number, end: number}, void> = initEvent<{ start: number, end: number }>(); + public onDimensionsChange: IEvent = new EventEmitter().event; + public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; + public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; + public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event; public dimensions: IRenderDimensions = { scaledCharWidth: 0, scaledCharHeight: 0, @@ -457,10 +457,10 @@ export class MockSelectionService implements ISelectionService { public hasSelection: boolean = false; public selectionStart: [number, number] | undefined; public selectionEnd: [number, number] | undefined; - public onLinuxMouseSelection = initEvent(); - public onRequestRedraw = initEvent(); - public onRequestScrollLines = initEvent(); - public onSelectionChange = initEvent(); + public onLinuxMouseSelection = new EventEmitter().event; + public onRequestRedraw = new EventEmitter().event; + public onRequestScrollLines = new EventEmitter().event; + public onSelectionChange = new EventEmitter().event; public disable(): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 23ba174f8..7fcc5ea95 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -95,7 +95,7 @@ export class BufferDecorationRenderer extends Disposable { // outside of viewport if (decoration.element) { decoration.element.style.display = 'none'; - decoration.onRender.fire(decoration.element); + decoration.onRenderEmitter.fire(decoration.element); } } else { let element = this._decorationElements.get(decoration); @@ -108,7 +108,7 @@ export class BufferDecorationRenderer extends Disposable { } element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`; element.style.display = this._altBufferIsActive ? 'none' : 'block'; - decoration.onRender.fire(element); + decoration.onRenderEmitter.fire(element); } } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 1a1f10138..6a70ccec4 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - public readonly onRequestRedraw = initEvent(); + public readonly onRequestRedraw = new EventEmitter().event; constructor( private _colors: IColorSet, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 583006d61..7062deece 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -4,7 +4,7 @@ */ import { IOptionsService } from 'common/services/Services'; -import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; export class CharSizeService implements ICharSizeService { @@ -16,7 +16,8 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - public readonly onCharSizeChange = initEvent(); + private readonly _onCharSizeChange = new EventEmitter(); + public readonly onCharSizeChange = this._onCharSizeChange.event; constructor( document: Document, @@ -31,7 +32,7 @@ export class CharSizeService implements ICharSizeService { if (result.width !== this.width || result.height !== this.height) { this.width = result.width; this.height = result.height; - this.onCharSizeChange.fire(); + this._onCharSizeChange.fire(); } } } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 73850bdb8..c81357cd9 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -5,7 +5,7 @@ import { IRenderer, IRenderDimensions } from 'browser/renderer/shared/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -39,10 +39,14 @@ export class RenderService extends Disposable implements IRenderService { columnSelectMode: false }; - public readonly onDimensionsChange = initEvent(); - public readonly onRenderedViewportChange = initEvent<{ start: number, end: number }>(); - public readonly onRender = initEvent<{ start: number, end: number }>(); - public readonly onRefreshRequest = initEvent<{ start: number, end: number }>(); + private readonly _onDimensionsChange = new EventEmitter(); + public readonly onDimensionsChange = this._onDimensionsChange.event; + private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); + public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; + private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); + public readonly onRender = this._onRender.event; + private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); + public readonly onRefreshRequest = this._onRefreshRequest.event; public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } @@ -131,9 +135,9 @@ export class RenderService extends Disposable implements IRenderService { // Fire render event only if it was not a redraw if (!this._isNextRenderRedrawOnly) { - this.onRenderedViewportChange.fire({ start, end }); + this._onRenderedViewportChange.fire({ start, end }); } - this.onRender.fire({ start, end }); + this._onRender.fire({ start, end }); this._isNextRenderRedrawOnly = true; } @@ -153,7 +157,7 @@ export class RenderService extends Disposable implements IRenderService { if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) { return; } - this.onDimensionsChange.fire(this._renderer.dimensions); + this._onDimensionsChange.fire(this._renderer.dimensions); } public dispose(): void { diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 5da197b13..3780c7ee8 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -9,7 +9,7 @@ import { IBufferLine, IDisposable } from 'common/Types'; import * as Browser from 'common/Platform'; import { SelectionModel } from 'browser/selection/SelectionModel'; import { CellData } from 'common/buffer/CellData'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IMouseService, ISelectionService, IRenderService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferRange, ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; @@ -111,10 +111,14 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionStart: [number, number] | undefined = undefined; private _oldSelectionEnd: [number, number] | undefined = undefined; - public readonly onLinuxMouseSelection = this.register(initEvent()); - public readonly onRequestRedraw = this.register(initEvent()); - public readonly onSelectionChange = this.register(initEvent()); - public readonly onRequestScrollLines = this.register(initEvent()); + private readonly _onLinuxMouseSelection = this.register(new EventEmitter()); + public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; + private readonly _onRedrawRequest = this.register(new EventEmitter()); + public readonly onRequestRedraw = this._onRedrawRequest.event; + private readonly _onSelectionChange = this.register(new EventEmitter()); + public readonly onSelectionChange = this._onSelectionChange.event; + private readonly _onRequestScrollLines = this.register(new EventEmitter()); + public readonly onRequestScrollLines = this._onRequestScrollLines.event; constructor( private readonly _element: HTMLElement, @@ -256,7 +260,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.clearSelection(); this._removeMouseDownListeners(); this.refresh(); - this.onSelectionChange.fire(); + this._onSelectionChange.fire(); } /** @@ -275,7 +279,7 @@ export class SelectionService extends Disposable implements ISelectionService { if (Browser.isLinux && isLinuxMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { - this.onLinuxMouseSelection.fire(this.selectionText); + this._onLinuxMouseSelection.fire(this.selectionText); } } } @@ -286,7 +290,7 @@ export class SelectionService extends Disposable implements ISelectionService { */ private _refresh(): void { this._refreshAnimationFrame = undefined; - this.onRequestRedraw.fire({ + this._onRedrawRequest.fire({ start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd, columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN @@ -354,7 +358,7 @@ export class SelectionService extends Disposable implements ISelectionService { public selectAll(): void { this._model.isSelectAllActive = true; this.refresh(); - this.onSelectionChange.fire(); + this._onSelectionChange.fire(); } public selectLines(start: number, end: number): void { @@ -364,7 +368,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.selectionStart = [0, start]; this._model.selectionEnd = [this._bufferService.cols, end]; this.refresh(); - this.onSelectionChange.fire(); + this._onSelectionChange.fire(); } /** @@ -661,7 +665,7 @@ export class SelectionService extends Disposable implements ISelectionService { return; } if (this._dragScrollAmount) { - this.onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false }); + this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false }); // Re-evaluate selection // If the cursor was above or below the viewport, make sure it's at the // start or end of the viewport respectively. This should only happen when @@ -739,7 +743,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._oldSelectionStart = start; this._oldSelectionEnd = end; this._oldHasSelection = hasSelection; - this.onSelectionChange.fire(); + this._onSelectionChange.fire(); } private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void { diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 537434494..599db1aba 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -4,7 +4,7 @@ */ import { ICircularList } from 'common/Types'; -import { initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; export interface IInsertEvent { index: number; @@ -25,9 +25,12 @@ export class CircularList implements ICircularList { private _startIndex: number; private _length: number; - public readonly onDelete = initEvent(); - public readonly onInsert = initEvent(); - public readonly onTrim = initEvent(); + public readonly onDeleteEmitter = new EventEmitter(); + public readonly onDelete = this.onDeleteEmitter.event; + public readonly onInsertEmitter = new EventEmitter(); + public readonly onInsert = this.onInsertEmitter.event; + public readonly onTrimEmitter = new EventEmitter(); + public readonly onTrim = this.onTrimEmitter.event; constructor( private _maxLength: number @@ -104,7 +107,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(this._length)] = value; if (this._length === this._maxLength) { this._startIndex = ++this._startIndex % this._maxLength; - this.onTrim.fire(1); + this.onTrimEmitter.fire(1); } else { this._length++; } @@ -120,7 +123,7 @@ export class CircularList implements ICircularList { throw new Error('Can only recycle when the buffer is full'); } this._startIndex = ++this._startIndex % this._maxLength; - this.onTrim.fire(1); + this.onTrimEmitter.fire(1); return this._array[this._getCyclicIndex(this._length - 1)]!; } @@ -155,7 +158,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; } this._length -= deleteCount; - this.onDelete.fire({ index: start, amount: deleteCount }); + this.onDeleteEmitter.fire({ index: start, amount: deleteCount }); } // Add items @@ -166,7 +169,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(start + i)] = items[i]; } if (items.length) { - this.onInsert.fire({ index: start, amount: items.length }); + this.onInsertEmitter.fire({ index: start, amount: items.length }); } // Adjust length as needed @@ -174,7 +177,7 @@ export class CircularList implements ICircularList { const countToTrim = (this._length + items.length) - this._maxLength; this._startIndex += countToTrim; this._length = this._maxLength; - this.onTrim.fire(countToTrim); + this.onTrimEmitter.fire(countToTrim); } else { this._length += items.length; } @@ -190,7 +193,7 @@ export class CircularList implements ICircularList { } this._startIndex += count; this._length -= count; - this.onTrim.fire(count); + this.onTrimEmitter.fire(count); } public shiftElements(start: number, count: number, offset: number): void { @@ -214,7 +217,7 @@ export class CircularList implements ICircularList { while (this._length > this._maxLength) { this._length--; this._startIndex++; - this.onTrim.fire(1); + this.onTrimEmitter.fire(1); } } } else { diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index dd6953bbe..2ad6f7351 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -29,7 +29,7 @@ import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/Buffe import { OptionsService } from 'common/services/OptionsService'; import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent, ScrollSource } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; -import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { UnicodeService } from 'common/services/UnicodeService'; import { CharsetService } from 'common/services/CharsetService'; @@ -59,11 +59,16 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - public readonly onBinary = initEvent(); - public readonly onData = initEvent(); - public readonly onLineFeed = initEvent(); - public readonly onResize = initEvent<{ cols: number, rows: number }>(); - public readonly onWriteParsed = initEvent(); + private readonly _onBinary = new EventEmitter(); + public readonly onBinary = this._onBinary.event; + private readonly _onData = new EventEmitter(); + public readonly onData = this._onData.event; + protected _onLineFeed = new EventEmitter(); + public readonly onLineFeed = this._onLineFeed.event; + private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); + public readonly onResize = this._onResize.event; + protected readonly _onWriteParsed = new EventEmitter(); + public readonly onWriteParsed = this._onWriteParsed.event; /** * Internally we track the source of the scroll but this is meaningless outside the library so @@ -117,13 +122,13 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Register input handler and handle/forward events this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); - this.register(forwardEvent(this._inputHandler.onLineFeed, this.onLineFeed)); + this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); this.register(this._inputHandler); // Setup listeners - this.register(forwardEvent(this._bufferService.onResize, this.onResize)); - this.register(forwardEvent(this.coreService.onData, this.onData)); - this.register(forwardEvent(this.coreService.onBinary, this.onBinary)); + this.register(forwardEvent(this._bufferService.onResize, this._onResize)); + this.register(forwardEvent(this.coreService.onData, this._onData)); + this.register(forwardEvent(this.coreService.onBinary, this._onBinary)); this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput())); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { @@ -137,7 +142,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup WriteBuffer this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); - this.register(forwardEvent(this._writeBuffer.onWriteParsed, this.onWriteParsed)); + this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); } public dispose(): void { diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 29e931d22..4684809f3 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -14,15 +14,13 @@ export interface IEvent { } export interface IEventEmitter { + event: IEvent; fire(arg1: T, arg2: U): void; dispose(): void; } -export interface IEventWithEmitter extends IEventEmitter, IEvent { -} - export class EventEmitter implements IEventEmitter { - private readonly _listeners: IListener[] = []; + private _listeners: IListener[] = []; private _event?: IEvent; private _disposed: boolean = false; @@ -66,34 +64,6 @@ export class EventEmitter implements IEventEmitter { } } -/** - * Creates an object that implements both the {@link IEvent} and {@link IEmitter} interfaces. This - * allows more concise instantiation. The idea is to internally use the combined - * {@link IEventWithEmitter} interface and only expose {@link IEvent} externally. - * - * @example - * ```ts - * public readonly onFoo = initEvent(); - * // ... - * onFoo(e => handle(e)); - * onFoo.fire('bar'); - * ``` - */ -export function initEvent(): IEventWithEmitter { - const emitter = new EventEmitter(); - const event = emitter.event; - Object.defineProperty(event, '_listeners', { - value: (emitter as any)._listeners - }); - Object.defineProperty(event, 'fire', { - value: emitter.fire.bind(emitter) - }); - Object.defineProperty(event, 'dispose', { - value: emitter.dispose.bind(emitter) - }); - return event as any; -} - export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable { return from(e => to.fire(e)); } diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index d4711c1f0..7f0879efc 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -11,7 +11,7 @@ import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; @@ -132,20 +132,33 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; - public readonly onRequestBell = initEvent(); - public readonly onRequestRefreshRows = initEvent(); - public readonly onRequestReset = initEvent(); - public readonly onRequestSendFocus = initEvent(); - public readonly onRequestSyncScrollBar = initEvent(); - public readonly onRequestWindowsOptionsReport = initEvent(); + private readonly _onRequestBell = new EventEmitter(); + public readonly onRequestBell = this._onRequestBell.event; + private readonly _onRequestRefreshRows = new EventEmitter(); + public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; + private readonly _onRequestReset = new EventEmitter(); + public readonly onRequestReset = this._onRequestReset.event; + private readonly _onRequestSendFocus = new EventEmitter(); + public readonly onRequestSendFocus = this._onRequestSendFocus.event; + private readonly _onRequestSyncScrollBar = new EventEmitter(); + public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; + private readonly _onRequestWindowsOptionsReport = new EventEmitter(); + public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; - public readonly onA11yChar = initEvent(); - public readonly onA11yTab = initEvent(); - public readonly onCursorMove = initEvent(); - public readonly onLineFeed = initEvent(); - public readonly onScroll = initEvent(); - public readonly onTitleChange = initEvent(); - public readonly onColor = initEvent(); + private readonly _onA11yChar = new EventEmitter(); + public readonly onA11yChar = this._onA11yChar.event; + private readonly _onA11yTab = new EventEmitter(); + public readonly onA11yTab = this._onA11yTab.event; + private readonly _onCursorMove = new EventEmitter(); + public readonly onCursorMove = this._onCursorMove.event; + private readonly _onLineFeed = new EventEmitter(); + public readonly onLineFeed = this._onLineFeed.event; + private readonly _onScroll = new EventEmitter(); + public readonly onScroll = this._onScroll.event; + private readonly _onTitleChange = new EventEmitter(); + public readonly onTitleChange = this._onTitleChange.event; + private readonly _onColor = new EventEmitter(); + public readonly onColor = this._onColor.event; private _parseStack: IParseStack = { paused: false, @@ -479,11 +492,11 @@ export class InputHandler extends Disposable implements IInputHandler { } if (this._activeBuffer.x !== cursorStartX || this._activeBuffer.y !== cursorStartY) { - this.onCursorMove.fire(); + this._onCursorMove.fire(); } // Refresh any dirty rows accumulated as part of parsing - this.onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); + this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); } public print(data: Uint32Array, start: number, end: number): void { @@ -522,7 +535,7 @@ export class InputHandler extends Disposable implements IInputHandler { } if (screenReaderMode) { - this.onA11yChar.fire(stringFromCodePoint(code)); + this._onA11yChar.fire(stringFromCodePoint(code)); } if (this._currentLinkId !== undefined) { this._oscLinkService.addLineToLink(this._currentLinkId, this._activeBuffer.ybase + this._activeBuffer.y); @@ -674,7 +687,7 @@ export class InputHandler extends Disposable implements IInputHandler { * and `ITerminalOptions.bellSound`. */ public bell(): boolean { - this.onRequestBell.fire(); + this._onRequestBell.fire(); return true; } @@ -706,7 +719,7 @@ export class InputHandler extends Disposable implements IInputHandler { } this._dirtyRowTracker.markDirty(this._activeBuffer.y); - this.onLineFeed.fire(); + this._onLineFeed.fire(); return true; } @@ -795,7 +808,7 @@ export class InputHandler extends Disposable implements IInputHandler { const originalX = this._activeBuffer.x; this._activeBuffer.x = this._activeBuffer.nextStop(); if (this._optionsService.rawOptions.screenReaderMode) { - this.onA11yTab.fire(this._activeBuffer.x - originalX); + this._onA11yTab.fire(this._activeBuffer.x - originalX); } return true; } @@ -1205,7 +1218,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0); this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0); // Force a scroll event to refresh viewport - this.onScroll.fire(0); + this._onScroll.fire(0); } break; } @@ -1836,7 +1849,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(132, this._bufferService.rows); - this.onRequestReset.fire(); + this._onRequestReset.fire(); } break; case 6: @@ -1855,7 +1868,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this.onRequestSyncScrollBar.fire(); + this._onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1877,7 +1890,7 @@ export class InputHandler extends Disposable implements IInputHandler { // focusin: ^[[I // focusout: ^[[O this._coreService.decPrivateModes.sendFocus = true; - this.onRequestSendFocus.fire(); + this._onRequestSendFocus.fire(); break; case 1005: // utf8 ext mode mouse - removed in #2507 this._logService.debug('DECSET 1005 not supported (see #2507)'); @@ -1904,8 +1917,8 @@ export class InputHandler extends Disposable implements IInputHandler { case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); this._coreService.isCursorInitialized = true; - this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this.onRequestSyncScrollBar.fire(); + this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); + this._onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = true; @@ -2074,7 +2087,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(80, this._bufferService.rows); - this.onRequestReset.fire(); + this._onRequestReset.fire(); } break; case 6: @@ -2093,7 +2106,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this.onRequestSyncScrollBar.fire(); + this._onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -2132,8 +2145,8 @@ export class InputHandler extends Disposable implements IInputHandler { this.restoreCursor(); } this._coreService.isCursorInitialized = true; - this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this.onRequestSyncScrollBar.fire(); + this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); + this._onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = false; @@ -2632,7 +2645,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public softReset(params: IParams): boolean { this._coreService.isCursorHidden = false; - this.onRequestSyncScrollBar.fire(); + this._onRequestSyncScrollBar.fire(); this._activeBuffer.scrollTop = 0; this._activeBuffer.scrollBottom = this._bufferService.rows - 1; this._curAttrData = DEFAULT_ATTR_DATA.clone(); @@ -2752,11 +2765,11 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (second !== 2) { - this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); + this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); } break; case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t - this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); + this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); break; case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t if (this._bufferService) { @@ -2846,7 +2859,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public setTitle(data: string): boolean { this._windowTitle = data; - this.onTitleChange.fire(data); + this._onTitleChange.fire(data); return true; } @@ -2888,7 +2901,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } if (event.length) { - this.onColor.fire(event); + this._onColor.fire(event); } return true; } @@ -2962,11 +2975,11 @@ export class InputHandler extends Disposable implements IInputHandler { for (let i = 0; i < slots.length; ++i, ++offset) { if (offset >= this._specialColors.length) break; if (slots[i] === '?') { - this.onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); + this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); } else { const color = parseColor(slots[i]); if (color) { - this.onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); + this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); } } } @@ -3027,7 +3040,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public restoreIndexedColor(data: string): boolean { if (!data) { - this.onColor.fire([{ type: ColorRequestType.RESTORE }]); + this._onColor.fire([{ type: ColorRequestType.RESTORE }]); return true; } const event: IColorEvent = []; @@ -3041,7 +3054,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } if (event.length) { - this.onColor.fire(event); + this._onColor.fire(event); } return true; } @@ -3052,7 +3065,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color." */ public restoreFgColor(data: string): boolean { - this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); + this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); return true; } @@ -3062,7 +3075,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color." */ public restoreBgColor(data: string): boolean { - this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); + this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); return true; } @@ -3072,7 +3085,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color." */ public restoreCursorColor(data: string): boolean { - this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); + this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); return true; } @@ -3099,7 +3112,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadApplicationMode(): boolean { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this.onRequestSyncScrollBar.fire(); + this._onRequestSyncScrollBar.fire(); return true; } @@ -3111,7 +3124,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadNumericMode(): boolean { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this.onRequestSyncScrollBar.fire(); + this._onRequestSyncScrollBar.fire(); return true; } @@ -3225,7 +3238,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public fullReset(): boolean { this._parser.reset(); - this.onRequestReset.fire(); + this._onRequestReset.fire(); return true; } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 2ac70a5e8..e302d3e4b 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; -import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; @@ -17,8 +17,8 @@ export class MockBufferService implements IBufferService { public serviceBrand: any; public get buffer(): IBuffer { return this.buffers.active; } public buffers: IBufferSet = {} as any; - public onResize: IEvent<{ cols: number, rows: number }> = initEvent<{ cols: number, rows: number }>(); - public onScroll: IEvent = initEvent(); + public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event; + public onScroll: IEvent = new EventEmitter().event; public isUserScrolling: boolean = false; constructor( public cols: number, @@ -60,7 +60,7 @@ export class MockCoreMouseService implements ICoreMouseService { public addProtocol(name: string): void { } public reset(): void { } public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } - public onProtocolChange: IEvent = initEvent(); + public onProtocolChange: IEvent = new EventEmitter().event; public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } { throw new Error('Method not implemented.'); } @@ -92,9 +92,9 @@ export class MockCoreService implements ICoreService { sendFocus: false, wraparound: true }; - public onData: IEvent = initEvent(); - public onUserInput: IEvent = initEvent(); - public onBinary: IEvent = initEvent(); + public onData: IEvent = new EventEmitter().event; + public onUserInput: IEvent = new EventEmitter().event; + public onBinary: IEvent = new EventEmitter().event; public reset(): void { } public triggerDataEvent(data: string, wasUserInput?: boolean): void { } public triggerBinaryEvent(data: string): void { } @@ -113,7 +113,7 @@ export class MockOptionsService implements IOptionsService { public serviceBrand: any; public readonly rawOptions: Required = clone(DEFAULT_OPTIONS); public options: Required = this.rawOptions; - public onOptionChange: IEvent = initEvent(); + public onOptionChange: IEvent = new EventEmitter().event; constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { @@ -149,7 +149,7 @@ export class MockUnicodeService implements IUnicodeService { } public versions: string[] = []; public activeVersion: string = ''; - public onChange: IEvent = initEvent(); + public onChange: IEvent = new EventEmitter().event; public wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint); public getStringCellWidth(s: string): number { throw new Error('Method not implemented.'); @@ -159,8 +159,8 @@ export class MockUnicodeService implements IUnicodeService { export class MockDecorationService implements IDecorationService { public serviceBrand: any; public get decorations(): IterableIterator { return [].values(); } - public onDecorationRegistered = initEvent(); - public onDecorationRemoved = initEvent(); + public onDecorationRegistered = new EventEmitter().event; + public onDecorationRemoved = new EventEmitter().event; public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } public reset(): void { } public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 6e6e93e7e..d44bb1977 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -4,7 +4,7 @@ */ import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm'; -import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter'; +import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; import { IParams } from 'common/parser/Types'; import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services'; @@ -71,9 +71,12 @@ export interface ICircularList { maxLength: number; isFull: boolean; - onDelete: IEventWithEmitter; - onInsert: IEventWithEmitter; - onTrim: IEventWithEmitter; + onDeleteEmitter: IEventEmitter; + onDelete: IEvent; + onInsertEmitter: IEventEmitter; + onInsert: IEvent; + onTrimEmitter: IEventEmitter; + onTrim: IEvent; get(index: number): T | undefined; set(index: number, value: T): void; diff --git a/src/common/buffer/Buffer.test.ts b/src/common/buffer/Buffer.test.ts index 03297ba62..e5ea7f5ec 100644 --- a/src/common/buffer/Buffer.test.ts +++ b/src/common/buffer/Buffer.test.ts @@ -1071,7 +1071,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); - buffer.lines.onTrim.fire(1); + buffer.lines.onTrimEmitter.fire(1); assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { @@ -1081,7 +1081,7 @@ describe('Buffer', () => { const marker = buffer.addMarker(0); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.onTrim.fire(1); + buffer.lines.onTrimEmitter.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); }); @@ -1094,7 +1094,7 @@ describe('Buffer', () => { marker.onDispose(() => eventStack.push('disposed')); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.onTrim.fire(1); + buffer.lines.onTrimEmitter.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); assert.deepEqual(eventStack, ['disposed']); diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index ec3b9fcd5..c8b0d1b25 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -463,12 +463,12 @@ export class Buffer implements IBuffer { let insertCountEmitted = 0; for (let i = insertEvents.length - 1; i >= 0; i--) { insertEvents[i].index += insertCountEmitted; - this.lines.onInsert.fire(insertEvents[i]); + this.lines.onInsertEmitter.fire(insertEvents[i]); insertCountEmitted += insertEvents[i].amount; } const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength); if (amountToTrim > 0) { - this.lines.onTrim.fire(amountToTrim); + this.lines.onTrimEmitter.fire(amountToTrim); } } } diff --git a/src/common/buffer/BufferReflow.ts b/src/common/buffer/BufferReflow.ts index e496cbbb6..ece9a96ed 100644 --- a/src/common/buffer/BufferReflow.ts +++ b/src/common/buffer/BufferReflow.ts @@ -118,7 +118,7 @@ export function reflowLargerCreateNewLayout(lines: CircularList, to const countToRemove = toRemove[++nextToRemoveIndex]; // Tell markers that there was a deletion - lines.onDelete.fire({ + lines.onDeleteEmitter.fire({ index: i - countRemovedSoFar, amount: countToRemove }); diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index 1fa6fc275..46fcb0979 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -6,7 +6,7 @@ import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { Disposable } from 'common/Lifecycle'; @@ -19,7 +19,8 @@ export class BufferSet extends Disposable implements IBufferSet { private _alt!: Buffer; private _activeBuffer!: Buffer; - public readonly onBufferActivate = this.register(initEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); + private readonly _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); + public readonly onBufferActivate = this._onBufferActivate.event; /** * Create a new BufferSet for the given terminal. @@ -41,7 +42,7 @@ export class BufferSet extends Disposable implements IBufferSet { // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer this._alt = new Buffer(false, this._optionsService, this._bufferService); this._activeBuffer = this._normal; - this.onBufferActivate.fire({ + this._onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -85,7 +86,7 @@ export class BufferSet extends Disposable implements IBufferSet { this._alt.clearAllMarkers(); this._alt.clear(); this._activeBuffer = this._normal; - this.onBufferActivate.fire({ + this._onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -104,7 +105,7 @@ export class BufferSet extends Disposable implements IBufferSet { this._alt.x = this._normal.x; this._alt.y = this._normal.y; this._activeBuffer = this._alt; - this.onBufferActivate.fire({ + this._onBufferActivate.fire({ activeBuffer: this._alt, inactiveBuffer: this._normal }); diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 958bd9edf..56d64a726 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IMarker } from 'common/Types'; @@ -15,7 +15,8 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } - public readonly onDispose = initEvent(); + private readonly _onDispose = new EventEmitter(); + public readonly onDispose = this._onDispose.event; constructor( public line: number @@ -30,7 +31,7 @@ export class Marker extends Disposable implements IMarker { this.isDisposed = true; this.line = -1; // Emit before super.dispose such that dispose listeners get a change to react - this.onDispose.fire(); + this._onDispose.fire(); super.dispose(); } } diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 4a9c85554..cb40ffa1f 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; declare const setTimeout: (handler: () => void, timeout?: number) => void; @@ -42,7 +42,8 @@ export class WriteBuffer { private _syncCalls = 0; private _didUserInput = false; - public readonly onWriteParsed = initEvent(); + private readonly _onWriteParsed = new EventEmitter(); + public readonly onWriteParsed = this._onWriteParsed.event; constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } @@ -236,6 +237,6 @@ export class WriteBuffer { this._pendingData = 0; this._bufferOffset = 0; } - this.onWriteParsed.fire(); + this._onWriteParsed.fire(); } } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index a00962acd..033f5955d 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -5,19 +5,20 @@ import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm'; import { BufferApiView } from 'common/public/BufferApiView'; -import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICoreTerminal } from 'common/Types'; export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; - public readonly onBufferChange = initEvent(); + private readonly _onBufferChange = new EventEmitter(); + public readonly onBufferChange = this._onBufferChange.event; constructor(private _core: ICoreTerminal) { this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate'); - this._core.buffers.onBufferActivate(() => this.onBufferChange.fire(this.active)); + this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active)); } public get active(): IBufferApi { if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 3614fcfad..1bc93041c 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,7 +6,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; -import { EventEmitter, IEventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types'; @@ -22,8 +22,10 @@ export class BufferService extends Disposable implements IBufferService { /** Whether the user is scrolling (locks the scroll position) */ public isUserScrolling: boolean = false; - public readonly onResize = initEvent<{ cols: number, rows: number }>(); - public readonly onScroll = initEvent(); + private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); + public readonly onResize = this._onResize.event; + private readonly _onScroll = new EventEmitter(); + public readonly onScroll = this._onScroll.event; public get buffer(): IBuffer { return this.buffers.active; } @@ -47,7 +49,7 @@ export class BufferService extends Disposable implements IBufferService { this.rows = rows; this.buffers.resize(cols, rows); this.buffers.setupTabStops(this.cols); - this.onResize.fire({ cols, rows }); + this._onResize.fire({ cols, rows }); } public reset(): void { @@ -116,7 +118,7 @@ export class BufferService extends Disposable implements IBufferService { buffer.ydisp = buffer.ybase; } - this.onScroll.fire(buffer.ydisp); + this._onScroll.fire(buffer.ydisp); } /** @@ -146,7 +148,7 @@ export class BufferService extends Disposable implements IBufferService { } if (!suppressScrollEvent) { - this.onScroll.fire(buffer.ydisp); + this._onScroll.fire(buffer.ydisp); } } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index a5f1528a8..8c2a24def 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -3,7 +3,7 @@ * @license MIT */ import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; /** @@ -172,7 +172,8 @@ export class CoreMouseService implements ICoreMouseService { private _activeEncoding: string = ''; private _lastEvent: ICoreMouseEvent | null = null; - public readonly onProtocolChange = initEvent(); + private readonly _onProtocolChange = new EventEmitter(); + public readonly onProtocolChange = this._onProtocolChange.event; constructor( @IBufferService private readonly _bufferService: IBufferService, @@ -206,7 +207,7 @@ export class CoreMouseService implements ICoreMouseService { throw new Error(`unknown protocol "${name}"`); } this._activeProtocol = name; - this.onProtocolChange.fire(this._protocols[name].events); + this._onProtocolChange.fire(this._protocols[name].events); } public get activeEncoding(): string { diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 35c759193..9282197b3 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -4,7 +4,7 @@ */ import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IDecPrivateModes, IModes } from 'common/Types'; import { clone } from 'common/Clone'; import { Disposable } from 'common/Lifecycle'; @@ -34,9 +34,12 @@ export class CoreService extends Disposable implements ICoreService { // Circular dependency, this must be unset or memory will leak after Terminal.dispose private _scrollToBottom: (() => void) | undefined; - public readonly onData = this.register(initEvent()); - public readonly onUserInput = this.register(initEvent()); - public readonly onBinary = this.register(initEvent()); + private readonly _onData = this.register(new EventEmitter()); + public readonly onData = this._onData.event; + private readonly _onUserInput = this.register(new EventEmitter()); + public readonly onUserInput = this._onUserInput.event; + private readonly _onBinary = this.register(new EventEmitter()); + public readonly onBinary = this._onBinary.event; constructor( // TODO: Move this into a service @@ -71,12 +74,12 @@ export class CoreService extends Disposable implements ICoreService { // Fire onUserInput so listeners can react as well (eg. clear selection) if (wasUserInput) { - this.onUserInput.fire(); + this._onUserInput.fire(); } // Fire onData API this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); - this.onData.fire(data); + this._onData.fire(data); } public triggerBinaryEvent(data: string): void { @@ -84,6 +87,6 @@ export class CoreService extends Disposable implements ICoreService { return; } this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); - this.onBinary.fire(data); + this._onBinary.fire(data); } } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 5efeb77d8..522b04dea 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -4,7 +4,7 @@ */ import { css } from 'common/Color'; -import { EventEmitter, initEvent } from 'common/EventEmitter'; +import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { SortedList } from 'common/SortedList'; @@ -25,8 +25,10 @@ export class DecorationService extends Disposable implements IDecorationService */ private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); - public readonly onDecorationRegistered = this.register(initEvent()); - public readonly onDecorationRemoved = this.register(initEvent()); + private readonly _onDecorationRegistered = this.register(new EventEmitter()); + public readonly onDecorationRegistered = this._onDecorationRegistered.event; + private readonly _onDecorationRemoved = this.register(new EventEmitter()); + public readonly onDecorationRemoved = this._onDecorationRemoved.event; public get decorations(): IterableIterator { return this._decorations.values(); } @@ -40,13 +42,13 @@ export class DecorationService extends Disposable implements IDecorationService decoration.onDispose(() => { if (decoration) { if (this._decorations.delete(decoration)) { - this.onDecorationRemoved.fire(decoration); + this._onDecorationRemoved.fire(decoration); } markerDispose.dispose(); } }); this._decorations.insert(decoration); - this.onDecorationRegistered.fire(decoration); + this._onDecorationRegistered.fire(decoration); } return decoration; } @@ -82,7 +84,7 @@ export class DecorationService extends Disposable implements IDecorationService public dispose(): void { for (const d of this._decorations.values()) { - this.onDecorationRemoved.fire(d); + this._onDecorationRemoved.fire(d); } this.reset(); } @@ -93,8 +95,10 @@ class Decoration extends Disposable implements IInternalDecoration { public element: HTMLElement | undefined; public isDisposed: boolean = false; - public readonly onRender = this.register(initEvent()); - public readonly onDispose = this.register(initEvent()); + public readonly onRenderEmitter = this.register(new EventEmitter()); + public readonly onRender = this.onRenderEmitter.event; + private readonly _onDispose = this.register(new EventEmitter()); + public readonly onDispose = this._onDispose.event; private _cachedBg: IColor | undefined | null = null; public get backgroundColorRGB(): IColor | undefined { @@ -135,7 +139,7 @@ class Decoration extends Disposable implements IInternalDecoration { return; } this._isDisposed = true; - this.onDispose.fire(); + this._onDispose.fire(); super.dispose(); } } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 16beddbab..33aa3ee23 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -4,9 +4,9 @@ */ import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { CursorStyle } from 'common/Types'; -import { initEvent } from 'common/EventEmitter'; export const DEFAULT_OPTIONS: Readonly> = { cols: 80, @@ -57,7 +57,8 @@ export class OptionsService implements IOptionsService { public readonly rawOptions: Required; public options: Required; - public readonly onOptionChange = initEvent(); + private readonly _onOptionChange = new EventEmitter(); + public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { // set the default value of each option @@ -96,7 +97,7 @@ export class OptionsService implements IOptionsService { // Don't fire an option change event if they didn't change if (this.rawOptions[propName] !== value) { this.rawOptions[propName] = value; - this.onOptionChange.fire(propName); + this._onOptionChange.fire(propName); } }; diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index c47b1c2a9..e2b517cde 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter'; +import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, IOscLinkData } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; @@ -318,5 +318,5 @@ export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; readonly backgroundColorRGB: IColor | undefined; readonly foregroundColorRGB: IColor | undefined; - readonly onRender: IEventWithEmitter; + readonly onRenderEmitter: IEventEmitter; } diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 7306db93f..239f4d622 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -3,7 +3,7 @@ * @license MIT */ import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; -import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; import { UnicodeV6 } from 'common/input/UnicodeV6'; @@ -14,7 +14,8 @@ export class UnicodeService implements IUnicodeService { private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - public readonly onChange = initEvent(); + private readonly _onChange = new EventEmitter(); + public readonly onChange = this._onChange.event; constructor() { const defaultProvider = new UnicodeV6(); @@ -37,7 +38,7 @@ export class UnicodeService implements IUnicodeService { } this._active = version; this._activeProvider = this._providers[version]; - this.onChange.fire(version); + this._onChange.fire(version); } public register(provider: IUnicodeVersionProvider): void { diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 45b8aa749..639988ebd 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -24,7 +24,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBuffer } from 'common/buffer/Types'; import { CoreTerminal } from 'common/CoreTerminal'; -import { EventEmitter, forwardEvent, IEvent, initEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent, IEvent } from 'common/EventEmitter'; import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; import { IMarker, ITerminalOptions, ScrollSource } from 'common/Types'; @@ -32,11 +32,16 @@ export class Terminal extends CoreTerminal { // TODO: We should remove options once components adopt optionsService public get options(): Required { return this.optionsService.options; } - public readonly onBell = initEvent(); - public readonly onCursorMove = initEvent(); - public readonly onTitleChange = initEvent(); - public readonly onA11yChar = initEvent(); - public readonly onA11yTab = initEvent(); + private readonly _onBell = new EventEmitter(); + public readonly onBell = this._onBell.event; + private readonly _onCursorMove = new EventEmitter(); + public readonly onCursorMove = this._onCursorMove.event; + private readonly _onTitleChange = new EventEmitter(); + public readonly onTitleChange = this._onTitleChange.event; + private readonly _onA11yCharEmitter = new EventEmitter(); + public readonly onA11yChar = this._onA11yCharEmitter.event; + private readonly _onA11yTabEmitter = new EventEmitter(); + public readonly onA11yTab = this._onA11yTabEmitter.event; /** * Creates a new `Terminal` object. @@ -60,10 +65,10 @@ export class Terminal extends CoreTerminal { // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestReset(() => this.reset())); - this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove)); - this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange)); - this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar)); - this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab)); + this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); } public dispose(): void { @@ -106,7 +111,7 @@ export class Terminal extends CoreTerminal { } public bell(): void { - this.onBell.fire(); + this._onBell.fire(); } /** From 0d5a526e869e6f2f2cad5a42b41020ab59e8499b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 6 Oct 2022 10:28:54 -0700 Subject: [PATCH 56/56] Split Utf8ToUtf32 decoder unit tests up Fixes #4178 --- src/common/input/TextDecoder.test.ts | 63 +++++++++++++++------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/common/input/TextDecoder.test.ts b/src/common/input/TextDecoder.test.ts index da1760a24..b8db8a035 100644 --- a/src/common/input/TextDecoder.test.ts +++ b/src/common/input/TextDecoder.test.ts @@ -121,35 +121,42 @@ describe('text encodings', () => { describe('Utf8ToUtf32 decoder', () => { describe('full codepoint test', () => { - - it('0..65535 (1/2/3 byte sequences)', () => { - const decoder = new Utf8ToUtf32(); - const target = new Uint32Array(5); - for (let i = 0; i < 65536; ++i) { - // skip surrogate pairs and a BOM - if ((i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF) { - continue; + function formatRange(min: number, max: number): string { + return `${min}..${max} (0x${min.toString(16).toUpperCase()}..0x${max.toString(16).toUpperCase()})`; + } + for (let min = 0; min < 65535; min += 10000) { + const max = Math.min(min + 10000, 65536); + it(`${formatRange(min, max)} (1/2/3 byte sequences)`, () => { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + for (let i = min; i < max; ++i) { + // skip surrogate pairs and a BOM + if ((i >= 0xD800 && i <= 0xDFFF) || i === 0xFEFF) { + continue; + } + const utf8Data = fromByteString(encode(String.fromCharCode(i))); + const length = decoder.decode(utf8Data, target); + assert.equal(length, 1); + assert.equal(toString(target, length), String.fromCharCode(i)); + decoder.clear(); } - const utf8Data = fromByteString(encode(String.fromCharCode(i))); - const length = decoder.decode(utf8Data, target); - assert.equal(length, 1); - assert.equal(toString(target, length), String.fromCharCode(i)); - decoder.clear(); - } - }); - - it('65536..0x10FFFF (4 byte sequences)', function (): void { - this.timeout(20000); - const decoder = new Utf8ToUtf32(); - const target = new Uint32Array(5); - for (let i = 65536; i < 0x10FFFF; ++i) { - const utf8Data = fromByteString(encode(stringFromCodePoint(i))); - const length = decoder.decode(utf8Data, target); - assert.equal(length, 1); - assert.equal(target[0], i); - decoder.clear(); - } - }); + }); + } + for (let minRaw = 60000; minRaw < 0x10FFFF; minRaw += 10000) { + const min = Math.max(minRaw, 65536); + const max = Math.min(minRaw + 10000, 0x10FFFF); + it(`${formatRange(min, max)} (4 byte sequences)`, function (): void { + const decoder = new Utf8ToUtf32(); + const target = new Uint32Array(5); + for (let i = min; i < max; ++i) { + const utf8Data = fromByteString(encode(stringFromCodePoint(i))); + const length = decoder.decode(utf8Data, target); + assert.equal(length, 1); + assert.equal(target[0], i); + decoder.clear(); + } + }); + } it('0xFEFF(BOM)', () => { const decoder = new Utf8ToUtf32();