From 25eb7d647267c4b63343a6e0af3248a59fa43a73 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 15 Sep 2022 17:19:34 +0300 Subject: [PATCH 01/87] =?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 8df6b302..9dd5154c 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 a66e39c1..b7ff177d 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 8280948a..cf1256c1 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 a65cb986..3ff22ac0 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 d240738f..1b00fee5 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/87] 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 cf1256c1..375e442d 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 693d09cbad07e7d8b0ba88ca7dadda8128be4e2a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:41:48 -0700 Subject: [PATCH 03/87] Allow setting renderer addon before open is called --- addons/xterm-addon-webgl/src/WebglAddon.ts | 3 +- demo/client.ts | 18 +++----- src/browser/Terminal.ts | 11 ++++- src/browser/TestUtils.test.ts | 4 ++ src/browser/Types.d.ts | 1 + src/browser/services/RenderService.ts | 51 ++++++++++++++++------ src/browser/services/Services.ts | 1 + 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 5b98a048..a2441a2a 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -26,7 +26,8 @@ export class WebglAddon implements ITerminalAddon { public activate(terminal: Terminal): void { if (!terminal.element) { - throw new Error('Cannot activate WebglAddon before Terminal.open'); + (terminal as any)._core.onWillOpen(() => this.activate(terminal)); + return; } if (isSafari) { throw new Error('Webgl is not currently supported on Safari'); diff --git a/demo/client.ts b/demo/client.ts index 00b10b57..31a3347c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -267,18 +267,14 @@ function createTerminal(): void { protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://'; socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; - term.open(terminalContainer); addons.fit.instance!.fit(); - try { - typedTerm.loadAddon(addons.webgl.instance); - setTimeout(() => { - addTextureAtlas(addons.webgl.instance.textureAtlas); - addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); - }, 0); - } - catch { - addons.webgl.instance = undefined; - } + typedTerm.loadAddon(addons.webgl.instance); + setTimeout(() => { + addTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + }, 0); + + term.open(terminalContainer); term.focus(); addDomListener(paddingElement, 'change', setPadding); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index afdac748..728237b9 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -143,6 +143,9 @@ export class Terminal extends CoreTerminal implements ITerminal { public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } private _onA11yTabEmitter = new EventEmitter(); public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } + private _onWillOpen = new EventEmitter(); + public get onWillOpen(): IEvent { return this._onWillOpen.event; } + /** * Creates a new `Terminal` object. @@ -509,12 +512,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService); this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService); - const renderer = this._createRenderer(); - this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement)); + this._renderService = this.register(this._instantiationService.createInstance(RenderService, this.rows, this.screenElement)); this._instantiationService.setService(IRenderService, this._renderService); this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); + this._onWillOpen.fire(this.element); + if (!this._renderService.hasRenderer()) { + this._renderService.setRenderer(this._createRenderer()); + } + this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 0b5e00c1..fb098850 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -41,6 +41,7 @@ export class MockTerminal implements ITerminal { public onTitleChange!: IEvent; public onBell!: IEvent; public onScroll!: IEvent; + public onWillOpen!: IEvent; public onKey!: IEvent<{ key: string, domEvent: KeyboardEvent }>; public onRender!: IEvent<{ start: number, end: number }>; public onResize!: IEvent<{ cols: number, rows: number }>; @@ -400,6 +401,9 @@ export class MockRenderService implements IRenderService { public resize(cols: number, rows: number): void { throw new Error('Method not implemented.'); } + public hasRenderer(): boolean { + throw new Error('Method not implemented.'); + } public setRenderer(renderer: IRenderer): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 48461a5e..7e9053e1 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -23,6 +23,7 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal { onFocus: IEvent; onA11yChar: IEvent; onA11yTab: IEvent; + onWillOpen: IEvent; cancel(ev: Event, force?: boolean): boolean | void; } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 3849bdd3..6c5737c7 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -23,6 +23,7 @@ interface ISelectionState { export class RenderService extends Disposable implements IRenderService { public serviceBrand: undefined; + private _renderer: IRenderer | undefined; private _renderDebouncer: IRenderDebouncerWithCallback; private _screenDprMonitor: ScreenDprMonitor; private _pausedResizeTask = new DebouncedIdleTask(); @@ -48,10 +49,9 @@ export class RenderService extends Disposable implements IRenderService { private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; } - public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } + public get dimensions(): IRenderDimensions { return this._renderer!.dimensions; } constructor( - private _renderer: IRenderer, private _rowCount: number, screenElement: HTMLElement, @IOptionsService optionsService: IOptionsService, @@ -62,7 +62,7 @@ export class RenderService extends Disposable implements IRenderService { ) { super(); - this.register({ dispose: () => this._renderer.dispose() }); + this.register({ dispose: () => this._renderer?.dispose() }); this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end)); this.register(this._renderDebouncer); @@ -83,7 +83,7 @@ export class RenderService extends Disposable implements IRenderService { this.register(decorationService.onDecorationRemoved(() => this._fullRefresh())); // No need to register this as renderer is explicitly disposed in RenderService.dispose - this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); + // this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. @@ -125,6 +125,9 @@ export class RenderService extends Disposable implements IRenderService { } private _renderRows(start: number, end: number): void { + if (!this._renderer) { + return; + } this._renderer.renderRows(start, end); // Update selection if needed @@ -147,12 +150,18 @@ export class RenderService extends Disposable implements IRenderService { } private _handleOptionsChanged(): void { + if (!this._renderer) { + return; + } this._renderer.onOptionsChanged(); this.refreshRows(0, this._rowCount - 1); this._fireOnCanvasResize(); } private _fireOnCanvasResize(): void { + if (!this._renderer) { + return; + } // Don't fire the event if the dimensions haven't changed if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) { return; @@ -164,9 +173,13 @@ export class RenderService extends Disposable implements IRenderService { super.dispose(); } + public hasRenderer(): boolean { + return !!this._renderer; + } + public setRenderer(renderer: IRenderer): void { // TODO: RenderService should be the only one to dispose the renderer - this._renderer.dispose(); + this._renderer?.dispose(); this._renderer = renderer; this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); @@ -188,11 +201,17 @@ export class RenderService extends Disposable implements IRenderService { } public clearTextureAtlas(): void { - this._renderer?.clearTextureAtlas?.(); + if (!this._renderer) { + return; + } + this._renderer.clearTextureAtlas?.(); this._fullRefresh(); } public setColors(colors: IColorSet): void { + if (!this._renderer) { + return; + } this._renderer.setColors(colors); this._fullRefresh(); } @@ -202,13 +221,19 @@ export class RenderService extends Disposable implements IRenderService { // when devicePixelRatio changes this._charSizeService.measure(); + if (!this._renderer) { + return; + } this._renderer.onDevicePixelRatioChange(); this.refreshRows(0, this._rowCount - 1); } public onResize(cols: number, rows: number): void { + if (!this._renderer) { + return; + } if (this._isPaused) { - this._pausedResizeTask.set(() => this._renderer.onResize(cols, rows)); + this._pausedResizeTask.set(() => this._renderer!.onResize(cols, rows)); } else { this._renderer.onResize(cols, rows); } @@ -217,29 +242,29 @@ export class RenderService extends Disposable implements IRenderService { // TODO: Is this useful when we have onResize? public onCharSizeChanged(): void { - this._renderer.onCharSizeChanged(); + this._renderer?.onCharSizeChanged(); } public onBlur(): void { - this._renderer.onBlur(); + this._renderer?.onBlur(); } public onFocus(): void { - this._renderer.onFocus(); + this._renderer?.onFocus(); } public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { this._selectionState.start = start; this._selectionState.end = end; this._selectionState.columnSelectMode = columnSelectMode; - this._renderer.onSelectionChanged(start, end, columnSelectMode); + this._renderer?.onSelectionChanged(start, end, columnSelectMode); } public onCursorMove(): void { - this._renderer.onCursorMove(); + this._renderer?.onCursorMove(); } public clear(): void { - this._renderer.clear(); + this._renderer?.clear(); } } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index ab91f8a3..3b52501b 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -71,6 +71,7 @@ export interface IRenderService extends IDisposable { refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; + hasRenderer(): boolean; setRenderer(renderer: IRenderer): void; setColors(colors: IColorSet): void; onDevicePixelRatioChange(): void; From 2febe19222b75fef2cbfb3ed5381aa7ca715107b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:42:57 -0700 Subject: [PATCH 04/87] Set renderer after screen element is attached to dom --- src/browser/Terminal.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 728237b9..aae5cdef 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -517,11 +517,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); - this._onWillOpen.fire(this.element); - if (!this._renderService.hasRenderer()) { - this._renderService.setRenderer(this._createRenderer()); - } - this._compositionView = document.createElement('div'); this._compositionView.classList.add('composition-view'); this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView); @@ -530,6 +525,11 @@ export class Terminal extends CoreTerminal implements ITerminal { // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); + this._onWillOpen.fire(this.element); + if (!this._renderService.hasRenderer()) { + this._renderService.setRenderer(this._createRenderer()); + } + this._mouseService = this._instantiationService.createInstance(MouseService); this._instantiationService.setService(IMouseService, this._mouseService); From 868fc7ec2954b4529b0dc5e80688a753c8955a23 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:44:13 -0700 Subject: [PATCH 05/87] Allow loading canvas before open --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 26 +++++++++++--------- addons/xterm-addon-webgl/src/WebglAddon.ts | 15 +++++------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 9fca00a6..32fe242e 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -14,21 +14,23 @@ export class CanvasAddon implements ITerminalAddon { private _renderer?: CanvasRenderer; public activate(terminal: Terminal): void { + const core = (terminal as any)._core; if (!terminal.element) { - throw new Error('Cannot activate CanvasAddon before Terminal.open'); + core.onWillOpen(() => this.activate(terminal)); + return; } this._terminal = terminal; - const bufferService: IBufferService = (terminal as any)._core._bufferService; - const renderService: IRenderService = (terminal as any)._core._renderService; - const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService; - const charSizeService: ICharSizeService = (terminal as any)._core._charSizeService; - const coreService: ICoreService = (terminal as any)._core.coreService; - const coreBrowserService: ICoreBrowserService = (terminal as any)._core._coreBrowserService; - const decorationService: IDecorationService = (terminal as any)._core._decorationService; - const optionsService: IOptionsService = (terminal as any)._core.optionsService; - const colors: IColorSet = (terminal as any)._core._colorManager.colors; - const screenElement: HTMLElement = (terminal as any)._core.screenElement; - const linkifier = (terminal as any)._core.linkifier2; + const bufferService: IBufferService = core._bufferService; + const renderService: IRenderService = core._renderService; + const characterJoinerService: ICharacterJoinerService = core._characterJoinerService; + const charSizeService: ICharSizeService = core._charSizeService; + const coreService: ICoreService = core.coreService; + const coreBrowserService: ICoreBrowserService = core._coreBrowserService; + const decorationService: IDecorationService = core._decorationService; + const optionsService: IOptionsService = core.optionsService; + const colors: IColorSet = core._colorManager.colors; + const screenElement: HTMLElement = core.screenElement; + const linkifier = core.linkifier2; this._renderer = new CanvasRenderer(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-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index a2441a2a..e8fa8799 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -25,20 +25,21 @@ export class WebglAddon implements ITerminalAddon { ) {} public activate(terminal: Terminal): void { + const core = (terminal as any)._core; if (!terminal.element) { - (terminal as any)._core.onWillOpen(() => this.activate(terminal)); + core.onWillOpen(() => this.activate(terminal)); return; } if (isSafari) { throw new Error('Webgl is not currently supported on Safari'); } this._terminal = terminal; - const renderService: IRenderService = (terminal as any)._core._renderService; - const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService; - const coreBrowserService: ICoreBrowserService = (terminal as any)._core._coreBrowserService; - const coreService: ICoreService = (terminal as any)._core.coreService; - const decorationService: IDecorationService = (terminal as any)._core._decorationService; - const colors: IColorSet = (terminal as any)._core._colorManager.colors; + const renderService: IRenderService = core._renderService; + const characterJoinerService: ICharacterJoinerService = core._characterJoinerService; + const coreBrowserService: ICoreBrowserService = core._coreBrowserService; + const coreService: ICoreService = core.coreService; + const decorationService: IDecorationService = core._decorationService; + const colors: IColorSet = 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); 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 06/87] 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 d7cb0f7e..02a94392 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 2cdf4e3c..1ac98b51 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 07/87] 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 1ac98b51..4f316f24 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 08/87] 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 8bb684a2..c594da3e 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 09/87] 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 c594da3e..0e82f9e9 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 4f316f24..8cb1edf4 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 10/87] 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 8cb1edf4..5e19105d 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 11/87] 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 a7f4a700..689b847a 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 ef08fb7c..dccc7f6a 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 d8daa0d1..b276cd05 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 4764ede4..dee1e02c 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 43e89839..875ac6c9 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 e5d115a1..dae80151 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 12/87] 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 02a94392..fae3efa5 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 f734b002..d9127e7b 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 b599bb7e..dd61cf34 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 8fb71a5d..e302d3e4 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 1c43b67e..00000000 --- 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 22edad1d..e2b517cd 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 13/87] 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 b642efbc..68475829 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 5b98a048..a96e7c64 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 d8daa0d1..74488c6c 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 74aed0cc..6865b6db 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 9c978949..6bf9fe23 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 afdac748..e03be89c 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 8df6b302..223e2470 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 b04e157f..d5cc37c8 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 97258609..69213429 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 4ee1ffa1..29c8e664 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 4d2c04ec..c0c6bb5b 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 02a94392..da70f84a 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 b599bb7e..bcdb2065 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 f940bb8f..b2223b03 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 72c4085c..9737b389 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 4f316f24..9f816d6c 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 d86f6bf5..f331a0b7 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 e3b7dcd8..49f31147 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 54e991f8..465a4968 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 20a34603..a144eb1d 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 e5d115a1..d0f9b991 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 c7e8d294..d6662c31 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 e96b7579..b5d19623 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 1cad0ee2..1b1162e5 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 14/87] 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 68475829..fd9629c0 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 a96e7c64..45858e8c 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 74488c6c..0fc634a7 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 6bf9fe23..cf5b9dd2 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 e03be89c..9c10fc16 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 d5cc37c8..7062deec 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 69213429..66dbb7aa 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 29c8e664..3780c7ee 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 c0c6bb5b..599db1ab 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 da70f84a..3a17c567 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 bcdb2065..0ace846a 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 b2223b03..46fcb097 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 9737b389..56d64a72 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 9f816d6c..bf72ae21 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 f331a0b7..033f5955 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 49f31147..1bc93041 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 465a4968..8c2a24de 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 a144eb1d..9282197b 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 d0f9b991..9fb611c6 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 d6662c31..33aa3ee2 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 b5d19623..239f4d62 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 1b1162e5..639988eb 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 15/87] 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 fd9629c0..726ad080 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 689899ef..d14e1fe7 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 45858e8c..548be30c 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 58fbb6ed..7a487242 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 cf5b9dd2..844b64f8 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 9c10fc16..90c1b134 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 0b5e00c1..95920375 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 7fcc5ea9..23ba174f 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 223e2470..39de7b2c 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 7062deec..583006d6 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 66dbb7aa..dfb4df80 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 3780c7ee..5da197b1 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 599db1ab..53743449 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 2ad6f735..dd6953bb 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 4684809f..c0074bba 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 7f0879ef..d4711c1f 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 e302d3e4..2ac70a5e 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 d44bb197..6e6e93e7 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 e5ea7f5e..03297ba6 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 c8b0d1b2..ec3b9fcd 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 ece9a96e..e496cbbb 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 46fcb097..1fa6fc27 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 56d64a72..958bd9ed 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 bf72ae21..b8efa54b 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 033f5955..a00962ac 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 1bc93041..3614fcfa 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 8c2a24de..a5f1528a 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 9282197b..35c75919 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 522b04de..5efeb77d 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 33aa3ee2..16beddba 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 e2b517cd..c47b1c2a 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 239f4d62..7306db93 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 639988eb..45b8aa74 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 16/87] Docs --- src/common/EventEmitter.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index c0074bba..979a6464 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 17/87] 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 548be30c..b26c565c 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 7a487242..8d8b24f7 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 18/87] 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 b0151e30..fa4be1e5 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 83f82fa7..dc503d75 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 19/87] 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 f37ea1b8..641a5ca4 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 9fca00a6..372852b0 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 fd9629c0..77c2177e 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 61e2d934..bddac195 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 c07329fd..5e1e7062 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 e90007b7..dc506bad 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 95f22fce..eb004c9a 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 d9349286..73d66f64 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 b0151e30..00000000 --- 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 3098538d..49d384b7 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 d8bc54c1..cde8abec 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 893362c8..e70a4995 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 8d2870cd..fd5b596c 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 dee1e02c..cb72f3bc 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 e1ff08d8..9b9f8e2a 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 a22a423f..0eb726a6 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 83f82fa7..58c207e8 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 00000000..58084235 --- /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 00000000..7559aa8f --- /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 6e70671b..d009a978 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 20/87] 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 641a5ca4..0a977d9c 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 77c2177e..0a00f6f8 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 bddac195..78dc5432 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 5e1e7062..db622b3f 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 dc506bad..6a803342 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 eb004c9a..b20765d8 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 49d384b7..c06842a7 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 dccc7f6a..ea7c3836 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 58fbb6ed..34f247eb 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 cb72f3bc..b9a4088d 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 e9c0cf41..2cba49dd 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 9b9f8e2a..dbf2fdcf 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 9c10fc16..be1f6777 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 0b5e00c1..1717c204 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 223e2470..77e977ad 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 b6ee7bf1..bce72ec3 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 32256cf4..b8725685 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 7559aa8f..ccfcae43 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 66dbb7aa..c81357cd 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 21/87] 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 689b847a..b19c8cb1 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 2b274516..522f6fb6 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 70acff34..993304a2 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 f95e0bb4..45373353 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 cb1a85b4..00000000 --- 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 ab91f8a3..9eea5c76 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 c8cbbf66..8192bfed 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 22/87] 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 3098538d..b809378e 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 23/87] 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 0a977d9c..7241347f 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 372852b0..10b8cdbb 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 0a00f6f8..14f7faf6 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 dda6052a..0527eae9 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 03cf0285..466e2ec8 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 c06842a7..e52e3a40 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 7bbbd63d..6a2b98d4 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 00b10b57..a1e3c97c 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 24/87] 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 9510fc6b..aa6e6417 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 a1e3c97c..8bff4690 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 25/87] 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 7241347f..05200861 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 466e2ec8..00000000 --- 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 73d66f64..f729b7f0 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 e52e3a40..54ad6f6e 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 26/87] 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 05200861..d22088f2 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 54ad6f6e..f3be7f46 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 f729b7f0..f5995cf8 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 27/87] 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 b19c8cb1..c198c9ba 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 34f247eb..b3a3ed02 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 e70a4995..741de255 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 b9a4088d..8782e123 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 2cba49dd..1321c246 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 ccfcae43..8fde3ae8 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 28/87] 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 c198c9ba..f27ef29f 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 ea7c3836..50a8cd58 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 bcfa11c8..7add33a3 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 841ad067..51a27377 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 8782e123..3d9bdf16 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 1321c246..9290bb79 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 29/87] 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 7add33a3..d09f43e9 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 b3a3ed02..977d42d7 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 fd5b596c..00000000 --- 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 9290bb79..25871c24 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 741de255..ee88c708 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 3d9bdf16..0840b035 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 8fde3ae8..20c1357a 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 30/87] 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 979a6464..29e931d2 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 31/87] 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 977d42d7..43b15e0c 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 25871c24..07eca0bf 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 32/87] 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 d22088f2..8752b55f 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 33/87] 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 e0561494..3ab8ea5a 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 b20765d8..8d80514b 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 0840b035..478254d7 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 34/87] 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 8bff4690..4c3019a2 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 41289221..a65b8bbc 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 35/87] 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 3ab8ea5a..4c92bca4 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 4c3019a2..593b0da6 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 36/87] 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 4c92bca4..42303f05 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 37/87] 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 478254d7..c4bffe8d 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 38/87] 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 42303f05..586be167 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 2969a6d1..ecb25aa9 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 d09f43e9..6e3a18fc 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 e3e38575..f0ee105c 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 20c1357a..81a025f2 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 39/87] 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 586be167..c0e20abd 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 ecb25aa9..b1542d98 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 f0ee105c..d7512908 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 00000000..8a6e8b85 --- /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 81a025f2..5e29411a 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 40/87] 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 c0e20abd..ffea9f0e 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 d7512908..c7511946 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 00000000..b29475b9 --- /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 41/87] 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 ffea9f0e..89975241 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 6a803342..759b0a12 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 8a6e8b85..db375778 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 5e29411a..7410b175 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 42/87] 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 89975241..ba385449 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 43/87] 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 ba385449..2a9e09ba 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 7410b175..7e2badcb 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 44/87] Bump copyright year --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c4d2084..c78ae76e 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 45/87] 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 1f214767..36fa453d 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 b7abab76..34e75c01 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 d14e1fe7..689899ef 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 b26c565c..45858e8c 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 c7511946..b22f3d5e 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 844b64f8..cf5b9dd2 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 bc8b19cf..be1f6777 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 18c51864..1717c204 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 23ba174f..7fcc5ea9 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 1a1f1013..6a70ccec 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 583006d6..7062deec 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 73850bdb..c81357cd 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 5da197b1..3780c7ee 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 53743449..599db1ab 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 dd6953bb..2ad6f735 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 29e931d2..4684809f 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 d4711c1f..7f0879ef 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 2ac70a5e..e302d3e4 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 6e6e93e7..d44bb197 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 03297ba6..e5ea7f5e 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 ec3b9fcd..c8b0d1b2 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 e496cbbb..ece9a96e 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 1fa6fc27..46fcb097 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 958bd9ed..56d64a72 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 4a9c8555..cb40ffa1 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 a00962ac..033f5955 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 3614fcfa..1bc93041 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 a5f1528a..8c2a24de 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 35c75919..9282197b 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 5efeb77d..522b04de 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 16beddba..33aa3ee2 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 c47b1c2a..e2b517cd 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 7306db93..239f4d62 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 45b8aa74..639988eb 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 46/87] 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 da1760a2..b8db8a03 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(); From e89c507d06ab24b9bd6bb699e9ac5d6cd4431f91 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Oct 2022 14:22:51 -0700 Subject: [PATCH 47/87] Fix clearTextureAtlas call as implemented on IRenderer This is the root cause of microsoft/vscode#162996 --- addons/xterm-addon-webgl/src/WebglAddon.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 45858e8c..18ef3e91 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -60,6 +60,6 @@ export class WebglAddon implements ITerminalAddon { } public clearTextureAtlas(): void { - this._renderer?.clearCharAtlas(); + this._renderer?.clearTextureAtlas(); } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b22f3d5e..037b92ed 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -295,7 +295,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } } - public clearCharAtlas(): void { + public clearTextureAtlas(): void { this._charAtlas?.clearTexture(); this._clearModel(true); this._updateModel(0, this._terminal.rows - 1); From c4eef2f470e6ec6a95994ada67d79f01dd36be9d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:17:11 -0700 Subject: [PATCH 48/87] Fix race condition that could when clearing atlas on 2+ terminals This is the proper fix for microsoft/vscode#162996, after a bunch of investigation we found that this is causing the problem: VS Code clears the texture atlas on OS resume to fix an issue where the texture could be corrupted. This calls clearTextureAtlas twice which should be fine, but it's not because inside WebglRenderer.clear we're calling updateModel which is meant to only ever be called in an animation frame. This calls potentially both renderers to have stale views of the texture atlas for those rows and our perf caching of cells (the main purpose of the model) doesn't know it's stale. Co-Authored-By: Megan Rogge --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 1 - src/browser/renderer/shared/TextureAtlas.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 037b92ed..c3bb0616 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -298,7 +298,6 @@ export class WebglRenderer extends Disposable implements IRenderer { public clearTextureAtlas(): void { this._charAtlas?.clearTexture(); this._clearModel(true); - this._updateModel(0, this._terminal.rows - 1); this._requestRedrawViewport(); } diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index c4bffe8d..d450cfdc 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -153,6 +153,7 @@ export class TextureAtlas implements ITextureAtlas { this._currentRow.height = 0; this._fixedRows.length = 0; this._didWarmUp = false; + this.hasCanvasChanged = true; } public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number): IRasterizedGlyph { From 8e7093f03b3bcc1b596dcd13c6c99c411660521f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:20:01 -0700 Subject: [PATCH 49/87] Remove the canvas char atlas This was unused and removing from cache was broken apparently --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 2 +- .../src/atlas/CanvasCharAtlas.ts | 410 ------------------ .../src/atlas/CharAtlasCache.ts | 96 ---- .../src/atlas/LRUMap.test.ts | 65 --- addons/xterm-addon-canvas/src/atlas/LRUMap.ts | 136 ------ .../xterm-addon-canvas/src/atlas/Types.d.ts | 17 - 6 files changed, 1 insertion(+), 725 deletions(-) delete mode 100644 addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts delete mode 100644 addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts delete mode 100644 addons/xterm-addon-canvas/src/atlas/LRUMap.test.ts delete mode 100644 addons/xterm-addon-canvas/src/atlas/LRUMap.ts delete mode 100644 addons/xterm-addon-canvas/src/atlas/Types.d.ts diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 34e75c01..eb1c085f 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { removeTerminalFromCache } from 'browser/renderer/shared/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'; @@ -11,7 +12,6 @@ import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { Terminal } from 'xterm'; -import { removeTerminalFromCache } from './atlas/CharAtlasCache'; import { CursorRenderLayer } from './CursorRenderLayer'; import { LinkRenderLayer } from './LinkRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; diff --git a/addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts deleted file mode 100644 index a14ff5d7..00000000 --- a/addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts +++ /dev/null @@ -1,410 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { IGlyphIdentifier } from './Types'; -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/shared/RendererUtils'; -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; - -// 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. -// -// This helps to limit the amount of damage a program can do when it would otherwise thrash the -// cache. -const FRAME_CACHE_DRAW_LIMIT = 100; - -/** - * 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; - -interface IGlyphCacheValue { - index: number; - isEmpty: boolean; - inBitmap: boolean; -} - -export function getGlyphCacheKey(glyph: IGlyphIdentifier): number { - // Note that this only returns a valid key when code < 256 - // Layout: - // 0b00000000000000000000000000000001: italic (1) - // 0b00000000000000000000000000000010: dim (1) - // 0b00000000000000000000000000000100: bold (1) - // 0b00000000000000000000111111111000: fg (9) - // 0b00000000000111111111000000000000: bg (9) - // 0b00011111111000000000000000000000: code (8) - // 0b11100000000000000000000000000000: unused (3) - return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); -} - -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; - - // 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; - - // The number of characters stored in the atlas by width/height - private _width: number; - private _height: number; - - private _drawToCacheCount: number = 0; - - // An array of glyph keys that are waiting on the bitmap to be generated. - private _glyphsWaitingOnBitmap: IGlyphCacheValue[] = []; - - // The timeout that is used to batch bitmap generation so it's not requested for every new glyph. - private _bitmapCommitTimeout: number | null = null; - - // The bitmap to draw from, this is much faster on other browsers than others. - private _bitmap: ImageBitmap | null = null; - - constructor(document: Document, private _config: ICharAtlasConfig) { - this._cacheCanvas = document.createElement('canvas'); - this._cacheCanvas.width = TEXTURE_WIDTH; - this._cacheCanvas.height = TEXTURE_HEIGHT; - // The canvas needs alpha because we use clearColor to convert the background color to alpha. - // It might also contain some characters with transparent backgrounds if allowTransparency is - // set. - this._cacheCtx = throwIfFalsy(this._cacheCanvas.getContext('2d', { alpha: true })); - - 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, - willReadFrequently: true - })); - - this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth); - this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight); - const capacity = this._width * this._height; - this._cacheMap = new LRUMap(capacity); - this._cacheMap.prealloc(capacity); - - // This is useful for debugging - // document.body.appendChild(this._cacheCanvas); - } - - public dispose(): void { - if (this._bitmapCommitTimeout !== null) { - window.clearTimeout(this._bitmapCommitTimeout); - this._bitmapCommitTimeout = null; - } - } - - /** - * 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; - } - - public clear(): void { - if (this._cacheMap.size > 0) { - const capacity = this._width * this._height; - this._cacheMap = new LRUMap(capacity); - this._cacheMap.prealloc(capacity); - } - this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); - this._tmpCtx.clearRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight); - } - - public draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean { - // Space is always an empty cell, special case this as it's so common - if (glyph.code === 32) { - return true; - } - - // Exit early for uncachable glyphs - if (!this._canCache(glyph)) { - return false; - } - - const glyphKey = getGlyphCacheKey(glyph); - const cacheValue = this._cacheMap.get(glyphKey); - if (cacheValue !== null && cacheValue !== undefined) { - this._drawFromCache(ctx, cacheValue, x, y); - return true; - } - if (this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) { - let index; - if (this._cacheMap.size < this._cacheMap.capacity) { - index = this._cacheMap.size; - } else { - // we're out of space, so our call to set will delete this item - index = this._cacheMap.peek()!.index; - } - const cacheValue = this._drawToCache(glyph, index); - this._cacheMap.set(glyphKey, cacheValue); - this._drawFromCache(ctx, cacheValue, x, y); - return true; - } - return false; - } - - private _canCache(glyph: IGlyphIdentifier): boolean { - // Only cache ascii and extended characters for now, to be safe. In the future, we could do - // something more complicated to determine the expected width of a character. - // - // If we switch the renderer over to webgl at some point, we may be able to use blending modes - // to draw overlapping glyphs from the atlas: - // https://github.com/servo/webrender/issues/464#issuecomment-255632875 - // https://webglfundamentals.org/webgl/lessons/webgl-text-texture.html - return glyph.code < 256; - } - - private _toCoordinateX(index: number): number { - return (index % this._width) * this._config.scaledCharWidth; - } - - private _toCoordinateY(index: number): number { - return Math.floor(index / this._width) * this._config.scaledCharHeight; - } - - private _drawFromCache( - ctx: CanvasRenderingContext2D, - cacheValue: IGlyphCacheValue, - x: number, - y: number - ): void { - // We don't actually need to do anything if this is whitespace. - if (cacheValue.isEmpty) { - return; - } - const cacheX = this._toCoordinateX(cacheValue.index); - const cacheY = this._toCoordinateY(cacheValue.index); - ctx.drawImage( - cacheValue.inBitmap ? this._bitmap! : this._cacheCanvas, - cacheX, - cacheY, - this._config.scaledCharWidth, - this._config.scaledCharHeight, - x, - y, - this._config.scaledCharWidth, - this._config.scaledCharHeight - ); - } - - private _getColorFromAnsiIndex(idx: number): IColor { - if (idx < this._config.colors.ansi.length) { - return this._config.colors.ansi[idx]; - } - return DEFAULT_ANSI_COLORS[idx]; - } - - private _getBackgroundColor(glyph: IGlyphIdentifier): IColor { - if (this._config.allowTransparency) { - // 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 NULL_COLOR; - } - let result: IColor; - if (glyph.bg === INVERTED_DEFAULT_COLOR) { - result = this._config.colors.foreground; - } else if (glyph.bg < 256) { - result = this._getColorFromAnsiIndex(glyph.bg); - } else { - result = this._config.colors.background; - } - if (glyph.dim) { - result = color.blend(this._config.colors.background, color.multiplyOpacity(result, 0.5)); - } - return result; - } - - private _getForegroundColor(glyph: IGlyphIdentifier): IColor { - if (glyph.fg === INVERTED_DEFAULT_COLOR) { - return color.opaque(this._config.colors.background); - } - if (glyph.fg < 256) { - // 256 color support - return this._getColorFromAnsiIndex(glyph.fg); - } - return this._config.colors.foreground; - } - - // TODO: We do this (or something similar) in multiple places. We should split this off - // into a shared function. - private _drawToCache(glyph: IGlyphIdentifier, index: number): IGlyphCacheValue { - this._drawToCacheCount++; - - this._tmpCtx.save(); - - // draw the background - const backgroundColor = this._getBackgroundColor(glyph); - // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of - // transparency in backgroundColor - this._tmpCtx.globalCompositeOperation = 'copy'; - this._tmpCtx.fillStyle = backgroundColor.css; - this._tmpCtx.fillRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight); - this._tmpCtx.globalCompositeOperation = 'source-over'; - - // draw the foreground/glyph - const fontWeight = glyph.bold ? this._config.fontWeightBold : this._config.fontWeight; - const fontStyle = glyph.italic ? 'italic' : ''; - this._tmpCtx.font = - `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; - this._tmpCtx.textBaseline = TEXT_BASELINE; - - this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css; - - // Apply alpha to dim the character - if (glyph.dim) { - this._tmpCtx.globalAlpha = DIM_OPACITY; - } - - // Draw the character - this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight); - - // clear the background from the character to avoid issues with drawing over the previous - // character if it extends past it's bounds - let imageData = this._tmpCtx.getImageData( - 0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight - ); - let isEmpty = false; - if (!this._config.allowTransparency) { - isEmpty = clearColor(imageData, backgroundColor); - } - - // If this charcater is underscore and empty, shift it up until it is visible, try for a maximum - // of 5 pixels. - if (isEmpty && glyph.chars === '_' && !this._config.allowTransparency) { - for (let offset = 1; offset <= 5; offset++) { - // Draw the character - this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight - offset); - - // clear the background from the character to avoid issues with drawing over the previous - // character if it extends past it's bounds - imageData = this._tmpCtx.getImageData( - 0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight - ); - isEmpty = clearColor(imageData, backgroundColor); - if (!isEmpty) { - break; - } - } - } - - this._tmpCtx.restore(); - - // copy the data from imageData to _cacheCanvas - const x = this._toCoordinateX(index); - const y = this._toCoordinateY(index); - // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us - this._cacheCtx.putImageData(imageData, x, y); - - // Add the glyph and queue it to the bitmap (if the browser supports it) - const cacheValue = { - index, - isEmpty, - inBitmap: false - }; - this._addGlyphToBitmap(cacheValue); - - return cacheValue; - } - - private _addGlyphToBitmap(cacheValue: IGlyphCacheValue): void { - // Support is patchy for createImageBitmap at the moment, pass a canvas back - // if support is lacking as drawImage works there too. Firefox is also - // included here as ImageBitmap appears both buggy and has horrible - // performance (tested on v55). - if (!('createImageBitmap' in window) || isFirefox || isSafari) { - return; - } - - // Add the glyph to the queue - this._glyphsWaitingOnBitmap.push(cacheValue); - - // Check if bitmap generation timeout already exists - if (this._bitmapCommitTimeout !== null) { - return; - } - - this._bitmapCommitTimeout = window.setTimeout(() => this._generateBitmap(), GLYPH_BITMAP_COMMIT_DELAY); - } - - private _generateBitmap(): void { - const glyphsMovingToBitmap = this._glyphsWaitingOnBitmap; - this._glyphsWaitingOnBitmap = []; - window.createImageBitmap(this._cacheCanvas).then(bitmap => { - // Set bitmap - this._bitmap = bitmap; - - // Mark all new glyphs as in bitmap, excluding glyphs that came in after - // the bitmap was requested - for (let i = 0; i < glyphsMovingToBitmap.length; i++) { - const value = glyphsMovingToBitmap[i]; - // It doesn't matter if the value was already evicted, it will be - // released from memory after this block if so. - value.inBitmap = true; - } - }); - this._bitmapCommitTimeout = null; - } -} - -/** - * Makes a particular rgb color and colors that are nearly the same in an ImageData completely - * transparent. - * @returns True if the result is "empty", meaning all pixels are fully transparent. - */ -function clearColor(imageData: ImageData, color: IColor): boolean { - let isEmpty = true; - const r = color.rgba >>> 24; - const g = color.rgba >>> 16 & 0xFF; - const b = color.rgba >>> 8 & 0xFF; - for (let offset = 0; offset < imageData.data.length; offset += 4) { - if (Math.abs(imageData.data[offset] - r) + - Math.abs(imageData.data[offset + 1] - g) + - Math.abs(imageData.data[offset + 2] - b) < 35) { - imageData.data[offset + 3] = 0; - } else { - isEmpty = false; - } - } - return isEmpty; -} diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts deleted file mode 100644 index f5995cf8..00000000 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; -import { CanvasCharAtlas } from './CanvasCharAtlas'; -import { IColorSet } from 'browser/Types'; -import { Terminal } from 'xterm'; -import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; - -interface ICharAtlasCacheEntry { - atlas: CanvasCharAtlas; - config: ICharAtlasConfig; - // N.B. This implementation potentially holds onto copies of the terminal forever, so - // this may cause memory leaks. - ownedBy: Terminal[]; -} - -const charAtlasCache: ICharAtlasCacheEntry[] = []; - -/** - * Acquires a char atlas, either generating a new one or returning an existing - * one that is in use by another terminal. - */ -export function acquireCharAtlas( - terminal: Terminal, - colors: IColorSet, - scaledCellWidth: number, - scaledCellHeight: number, - scaledCharWidth: number, - scaledCharHeight: number, - devicePixelRatio: number -): CanvasCharAtlas { - 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(terminal); - if (ownedByIndex >= 0) { - if (configEquals(entry.config, newConfig)) { - return entry.atlas; - } - // The configs differ, release the renderer from the entry - if (entry.ownedBy.length === 1) { - entry.atlas.dispose(); - charAtlasCache.splice(i, 1); - } else { - entry.ownedBy.splice(ownedByIndex, 1); - } - break; - } - } - - // Try match a char atlas from the cache - for (let i = 0; i < charAtlasCache.length; i++) { - const entry = charAtlasCache[i]; - if (configEquals(entry.config, newConfig)) { - // Add the renderer to the cache entry and return - entry.ownedBy.push(terminal); - return entry.atlas; - } - } - - const newEntry: ICharAtlasCacheEntry = { - atlas: new CanvasCharAtlas( - document, - newConfig - ), - config: newConfig, - ownedBy: [terminal] - }; - charAtlasCache.push(newEntry); - return newEntry.atlas; -} - -/** - * Removes a terminal reference from the cache, allowing its memory to be freed. - */ -export function removeTerminalFromCache(terminal: Terminal): void { - for (let i = 0; i < charAtlasCache.length; i++) { - 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 - charAtlasCache[i].atlas.dispose(); - charAtlasCache.splice(i, 1); - } else { - // Remove the reference from the cache entry - charAtlasCache[i].ownedBy.splice(index, 1); - } - break; - } - } -} diff --git a/addons/xterm-addon-canvas/src/atlas/LRUMap.test.ts b/addons/xterm-addon-canvas/src/atlas/LRUMap.test.ts deleted file mode 100644 index b24ad2af..00000000 --- a/addons/xterm-addon-canvas/src/atlas/LRUMap.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { LRUMap } from './LRUMap'; - -describe('LRUMap', () => { - it('can be used to store and retrieve values', () => { - const map = new LRUMap(10); - map.set(1, 'valuea'); - map.set(2, 'valueb'); - map.set(3, 'valuec'); - assert.strictEqual(map.get(1), 'valuea'); - assert.strictEqual(map.get(2), 'valueb'); - assert.strictEqual(map.get(3), 'valuec'); - }); - - it('maintains a size from insertions', () => { - const map = new LRUMap(10); - assert.strictEqual(map.size, 0); - map.set(1, 'value'); - assert.strictEqual(map.size, 1); - map.set(2, 'value'); - assert.strictEqual(map.size, 2); - }); - - it('deletes the oldest entry when the capacity is exceeded', () => { - const map = new LRUMap(4); - map.set(1, 'value'); - map.set(2, 'value'); - map.set(3, 'value'); - map.set(4, 'value'); - map.set(5, 'value'); - assert.isNull(map.get(1)); - assert.isNotNull(map.get(2)); - assert.isNotNull(map.get(3)); - assert.isNotNull(map.get(4)); - assert.isNotNull(map.get(5)); - assert.strictEqual(map.size, 4); - }); - - it('prevents a recently accessed entry from getting deleted', () => { - const map = new LRUMap(2); - map.set(1, 'value'); - map.set(2, 'value'); - map.get(1); - // a would normally get deleted here, except that we called get() - map.set(3, 'value'); - assert.isNotNull(map.get(1)); - // b got deleted instead of a - assert.isNull(map.get(2)); - assert.isNotNull(map.get(3)); - }); - - it('supports mutation', () => { - const map = new LRUMap(10); - map.set(1, 'oldvalue'); - map.set(1, 'newvalue'); - // mutation doesn't change the size - assert.strictEqual(map.size, 1); - assert.strictEqual(map.get(1), 'newvalue'); - }); -}); diff --git a/addons/xterm-addon-canvas/src/atlas/LRUMap.ts b/addons/xterm-addon-canvas/src/atlas/LRUMap.ts deleted file mode 100644 index f70962fe..00000000 --- a/addons/xterm-addon-canvas/src/atlas/LRUMap.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -interface ILinkedListNode { - prev: ILinkedListNode | null; - next: ILinkedListNode | null; - key: number | null; - value: T | null; -} - -export class LRUMap { - private _map: { [key: number]: ILinkedListNode } = {}; - private _head: ILinkedListNode | null = null; - private _tail: ILinkedListNode | null = null; - private _nodePool: ILinkedListNode[] = []; - public size: number = 0; - - constructor(public capacity: number) { } - - private _unlinkNode(node: ILinkedListNode): void { - const prev = node.prev; - const next = node.next; - if (node === this._head) { - this._head = next; - } - if (node === this._tail) { - this._tail = prev; - } - if (prev !== null) { - prev.next = next; - } - if (next !== null) { - next.prev = prev; - } - } - - private _appendNode(node: ILinkedListNode): void { - const tail = this._tail; - if (tail !== null) { - tail.next = node; - } - node.prev = tail; - node.next = null; - this._tail = node; - if (this._head === null) { - this._head = node; - } - } - - /** - * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that - * they're more likely to live next to each other in memory, which seems to improve performance. - * - * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for - * large maps. - */ - public prealloc(count: number): void { - const nodePool = this._nodePool; - for (let i = 0; i < count; i++) { - nodePool.push({ - prev: null, - next: null, - key: null, - value: null - }); - } - } - - public get(key: number): T | null { - // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However, - // it's faster than calling hasOwnProperty, and in our case, it would never overlap. - const node = this._map[key]; - if (node !== undefined) { - this._unlinkNode(node); - this._appendNode(node); - return node.value; - } - return null; - } - - /** - * Gets a value from a key without marking it as the most recently used item. - */ - public peekValue(key: number): T | null { - const node = this._map[key]; - if (node !== undefined) { - return node.value; - } - return null; - } - - public peek(): T | null { - const head = this._head; - return head === null ? null : head.value; - } - - public set(key: number, value: T): void { - // This is unsafe: See note above. - let node = this._map[key]; - if (node !== undefined) { - // already exists, we just need to mutate it and move it to the end of the list - node = this._map[key]; - this._unlinkNode(node); - node.value = value; - } else if (this.size >= this.capacity) { - // we're out of space: recycle the head node, move it to the tail - node = this._head!; - this._unlinkNode(node); - delete this._map[node.key!]; - node.key = key; - node.value = value; - this._map[key] = node; - } else { - // make a new element - const nodePool = this._nodePool; - if (nodePool.length > 0) { - // use a preallocated node if we can - node = nodePool.pop()!; - node.key = key; - node.value = value; - } else { - node = { - prev: null, - next: null, - key, - value - }; - } - this._map[key] = node; - this.size++; - } - this._appendNode(node); - } -} diff --git a/addons/xterm-addon-canvas/src/atlas/Types.d.ts b/addons/xterm-addon-canvas/src/atlas/Types.d.ts deleted file mode 100644 index cde8abec..00000000 --- a/addons/xterm-addon-canvas/src/atlas/Types.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { FontWeight } from 'common/services/Services'; -import { IPartialColorSet } from 'browser/Types'; - -export interface IGlyphIdentifier { - chars: string; - code: number; - bg: number; - fg: number; - bold: boolean; - dim: boolean; - italic: boolean; -} From f88c0e74807ffe2cc2437d519bdc9d6c012f30d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 07:24:35 -0700 Subject: [PATCH 50/87] Polish emitter registering in canvas renderer Part of #4167 --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 4 ++- addons/xterm-addon-canvas/src/CanvasAddon.ts | 35 +++++++++---------- .../xterm-addon-canvas/src/CanvasRenderer.ts | 4 +-- .../src/CursorRenderLayer.ts | 11 +++--- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 4 +-- .../xterm-addon-canvas/src/TextRenderLayer.ts | 15 -------- addons/xterm-addon-canvas/src/Types.d.ts | 1 - 7 files changed, 28 insertions(+), 46 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 2a9e09ba..da7ae912 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -19,8 +19,9 @@ import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; +import { Disposable } from 'common/Lifecycle'; -export abstract class BaseRenderLayer implements IRenderLayer { +export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; private _scaledCharWidth: number = 0; @@ -51,6 +52,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected readonly _decorationService: IDecorationService, protected readonly _coreBrowserService: ICoreBrowserService ) { + super(); 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`); diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index f90dad15..0378670b 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -9,20 +9,26 @@ import { CanvasRenderer } from './CanvasRenderer'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { Disposable, toDisposable } from 'common/Lifecycle'; -export class CanvasAddon implements ITerminalAddon { +export class CanvasAddon extends Disposable implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: CanvasRenderer; - private readonly _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = this.register(new EventEmitter()); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; + public get textureAtlas(): HTMLCanvasElement | undefined { + return this._renderer?.textureAtlas; + } + public activate(terminal: Terminal): void { const core = (terminal as any)._core; if (!terminal.element) { - core.onWillOpen(() => this.activate(terminal)); + this.register(core.onWillOpen(() => this.activate(terminal))); return; } + this._terminal = terminal; const bufferService: IBufferService = core._bufferService; const renderService: IRenderService = core._renderService; @@ -35,24 +41,17 @@ export class CanvasAddon implements ITerminalAddon { const colors: IColorSet = core._colorManager.colors; const screenElement: HTMLElement = core.screenElement; const linkifier = core.linkifier2; + this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); - forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); + this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); - } - public dispose(): void { - if (!this._terminal) { - throw new Error('Cannot dispose CanvasAddon because it is activated'); - } - const renderService: IRenderService = (this._terminal as any)._core._renderService; - renderService.setRenderer((this._terminal as any)._core._createRenderer()); - renderService.onResize(this._terminal.cols, this._terminal.rows); - this._renderer?.dispose(); - this._renderer = undefined; - } - - public get textureAtlas(): HTMLCanvasElement | undefined { - return this._renderer?.textureAtlas; + this.register(toDisposable(() => { + renderService.setRenderer((this._terminal as any)._core._createRenderer()); + renderService.onResize(terminal.cols, terminal.rows); + this._renderer?.dispose(); + this._renderer = undefined; + })); } } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index eb1c085f..868bedd6 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -24,9 +24,9 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - private readonly _onRequestRedraw = new EventEmitter(); + private readonly _onRequestRedraw = this.register(new EventEmitter()); public readonly onRequestRedraw = this._onRequestRedraw.event; - private readonly _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = this.register(new EventEmitter()); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; constructor( diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 78dc5432..9c3ce718 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -12,6 +12,7 @@ import { IBufferService, IOptionsService, ICoreService, IDecorationService } fro import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; import { Terminal } from 'xterm'; +import { toDisposable } from 'common/Lifecycle'; interface ICursorState { x: number; @@ -57,14 +58,10 @@ export class CursorRenderLayer extends BaseRenderLayer { 'block': this._renderBlockCursor.bind(this), 'underline': this._renderUnderlineCursor.bind(this) }; - } - - public dispose(): void { - if (this._cursorBlinkStateManager) { - this._cursorBlinkStateManager.dispose(); + this.register(toDisposable(() => { + this._cursorBlinkStateManager?.dispose(); this._cursorBlinkStateManager = undefined; - } - super.dispose(); + })); } public resize(dim: IRenderDimensions): void { diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index db622b3f..da9ebe16 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -28,8 +28,8 @@ export class LinkRenderLayer extends BaseRenderLayer { ) { super(terminal, container, 'link', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); - linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); - linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); + this.register(linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e))); + this.register(linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e))); } public resize(dim: IRenderDimensions): void { diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 8d80514b..8d5508e6 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -293,19 +293,4 @@ export class TextRenderLayer extends BaseRenderLayer { this._characterOverlapCache[chars] = overlaps; return overlaps; } - - /** - * Clear the charcater at the cell specified. - * @param x The column of the char. - * @param y The row of the char. - */ - // private _clearChar(x: number, y: number): void { - // let colsToClear = 1; - // // Clear the adjacent character if it was wide - // const state = this._state.cache[x][y]; - // if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) { - // colsToClear = 2; - // } - // this.clearCells(x, y, colsToClear, 1); - // } } diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index 0527eae9..75c5ccc9 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -41,7 +41,6 @@ export interface IRenderer extends IDisposable { */ readonly onRequestRedraw: IEvent; - dispose(): void; setColors(colors: IColorSet): void; onDevicePixelRatioChange(): void; onResize(cols: number, rows: number): void; From fc8043a25a80ee2ab711326c5c5e061a1cef2658 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 07:32:51 -0700 Subject: [PATCH 51/87] Ensure webgl texture atlas is released on dispose --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 17 ++++++++--------- .../src/renderLayer/BaseRenderLayer.ts | 15 +++++++-------- .../src/renderLayer/LinkRenderLayer.ts | 5 +++-- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index c3bb0616..6145d28f 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,7 +14,7 @@ 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 { EventEmitter } from 'common/EventEmitter'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { ICoreService, IDecorationService } from 'common/services/Services'; import { CharData, IBufferLine, ICellData } from 'common/Types'; import { Terminal } from 'xterm'; @@ -131,15 +131,14 @@ export class WebglRenderer extends Disposable implements IRenderer { this._initializeWebGLState(); this._isAttached = this._coreBrowserService.window.document.body.contains(this._core.screenElement!); - } - public dispose(): void { - for (const l of this._renderLayers) { - l.dispose(); - } - this._canvas.parentElement?.removeChild(this._canvas); - removeTerminalFromCache(this._terminal); - super.dispose(); + this.register(toDisposable(() => { + for (const l of this._renderLayers) { + l.dispose(); + } + this._canvas.parentElement?.removeChild(this._canvas); + removeTerminalFromCache(this._terminal); + })); } public get textureAtlas(): HTMLCanvasElement | undefined { diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 07eca0bf..58e23fc2 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -12,8 +12,9 @@ import { ICoreBrowserService } from 'browser/services/Services'; import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { CellData } from 'common/buffer/CellData'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { Disposable, toDisposable } from 'common/Lifecycle'; -export abstract class BaseRenderLayer implements IRenderLayer { +export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; private _scaledCharWidth: number = 0; @@ -33,18 +34,16 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _colors: IColorSet, protected readonly _coreBrowserService: ICoreBrowserService ) { + super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); this._container.appendChild(this._canvas); - } - - public dispose(): void { - this._canvas.remove(); - if (this._charAtlas) { - this._charAtlas.dispose(); - } + this.register(toDisposable(() => { + this._canvas.remove(); + this._charAtlas?.dispose(); + })); } private _initCanvas(): void { diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index dbf2fdcf..15246348 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -10,6 +10,7 @@ import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; +import { toDisposable } from 'common/Lifecycle'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; @@ -23,8 +24,8 @@ export class LinkRenderLayer extends BaseRenderLayer { ) { super(container, 'link', zIndex, true, colors, coreBrowserService); - terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); - terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); + this.register(terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e))); + this.register(terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e))); } public resize(terminal: Terminal, dim: IRenderDimensions): void { From 56b37a73ac25e9853965f4e3f970978c61f3e1e3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 07:37:55 -0700 Subject: [PATCH 52/87] Free WebglAddon --- addons/xterm-addon-webgl/src/WebglAddon.ts | 38 ++++++++++------------ 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index e51ad865..0586e6ff 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -10,8 +10,9 @@ import { IColorSet } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { isSafari } from 'common/Platform'; import { ICoreService, IDecorationService } from 'common/services/Services'; +import { Disposable, toDisposable } from 'common/Lifecycle'; -export class WebglAddon implements ITerminalAddon { +export class WebglAddon extends Disposable implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; @@ -22,17 +23,19 @@ export class WebglAddon implements ITerminalAddon { constructor( private _preserveDrawingBuffer?: boolean - ) {} + ) { + super(); + } public activate(terminal: Terminal): void { - const core = (terminal as any)._core; - if (!terminal.element) { - core.onWillOpen(() => this.activate(terminal)); - return; - } if (isSafari) { throw new Error('Webgl is not currently supported on Safari'); } + const core = (terminal as any)._core; + if (!terminal.element) { + this.register(core.onWillOpen(() => this.activate(terminal))); + return; + } this._terminal = terminal; const renderService: IRenderService = core._renderService; const characterJoinerService: ICharacterJoinerService = core._characterJoinerService; @@ -40,21 +43,16 @@ export class WebglAddon implements ITerminalAddon { const coreService: ICoreService = core.coreService; const decorationService: IDecorationService = core._decorationService; const colors: IColorSet = 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); + this._renderer = this.register(new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer)); + this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); + this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); renderService.setRenderer(this._renderer); - } - public dispose(): void { - if (!this._terminal) { - throw new Error('Cannot dispose WebglAddon because it is activated'); - } - const renderService: IRenderService = (this._terminal as any)._core._renderService; - renderService.setRenderer((this._terminal as any)._core._createRenderer()); - renderService.onResize(this._terminal.cols, this._terminal.rows); - this._renderer?.dispose(); - this._renderer = undefined; + this.register(toDisposable(() => { + const renderService: IRenderService = (this._terminal as any)._core._renderService; + renderService.setRenderer((this._terminal as any)._core._createRenderer()); + renderService.onResize(terminal.cols, terminal.rows); + })); } public get textureAtlas(): HTMLCanvasElement | undefined { From 8116e035b456b9ce693468035d7d01901432c343 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 08:06:43 -0700 Subject: [PATCH 53/87] Register webgl renderers --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 3 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index f27ef29f..e04f081c 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -76,7 +76,7 @@ let $glyph: IRasterizedGlyph | undefined = undefined; let $leftCellPadding = 0; let $clippedPixels = 0; -export class GlyphRenderer extends Disposable { +export class GlyphRenderer extends Disposable { private _atlas: ITextureAtlas | undefined; private _program: WebGLProgram; @@ -99,7 +99,6 @@ export class GlyphRenderer extends Disposable { constructor( private _terminal: Terminal, - private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions ) { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 6145d28f..6f213527 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -254,8 +254,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer?.dispose(); this._glyphRenderer?.dispose(); - this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); - this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); + this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions)); + this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._gl, this.dimensions)); // Update dimensions and acquire char atlas this.onCharSizeChanged(); From 34475f24c36e06236085ec77d40249be66b107e1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 08:11:28 -0700 Subject: [PATCH 54/87] Release canvas texture atlas --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 10 +++++----- addons/xterm-addon-canvas/src/CanvasRenderer.ts | 15 +++++++-------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index da7ae912..b3b5fe10 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -19,7 +19,7 @@ import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -60,11 +60,11 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._initCanvas(); this._container.appendChild(this._canvas); this._refreshCharAtlas(this._colors); - } - public dispose(): void { - removeElementFromParent(this._canvas); - this._charAtlas?.dispose(); + this.register(toDisposable(() => { + removeElementFromParent(this._canvas); + this._charAtlas?.dispose(); + })); } private _initCanvas(): void { diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 868bedd6..b4e41282 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -9,7 +9,7 @@ import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/rende 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 { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { Terminal } from 'xterm'; import { CursorRenderLayer } from './CursorRenderLayer'; @@ -70,14 +70,13 @@ export class CanvasRenderer extends Disposable implements IRenderer { this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this.onOptionsChanged(); - } - public dispose(): void { - for (const l of this._renderLayers) { - l.dispose(); - } - super.dispose(); - removeTerminalFromCache(this._terminal); + this.register(toDisposable(() => { + for (const l of this._renderLayers) { + l.dispose(); + } + removeTerminalFromCache(this._terminal); + })); } public get textureAtlas(): HTMLCanvasElement | undefined { From ac659cb4127e75dd8b738acd6070e66379e82026 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 08:27:10 -0700 Subject: [PATCH 55/87] Prevent overriding of Disposable.dispose A common mistake made was overriding dispose and not calling super.dispose, so a TS trick is now used to move it to a readonly property instead of a member method which prevents it being overridden. --- .../src/renderLayer/CursorRenderLayer.ts | 11 +++--- src/browser/AccessibilityManager.ts | 12 +++---- src/browser/Linkifier2.ts | 10 +++--- src/browser/ScreenDprMonitor.ts | 10 +++--- src/browser/Terminal.ts | 17 ++++----- .../decorations/BufferDecorationRenderer.ts | 12 +++---- .../decorations/OverviewRulerRenderer.ts | 10 +++--- src/browser/renderer/dom/DomRenderer.ts | 16 ++++----- src/browser/services/RenderService.ts | 4 --- src/browser/services/SelectionService.ts | 8 ++--- src/common/CoreTerminal.ts | 14 +++----- src/common/InputHandler.ts | 4 --- src/common/Lifecycle.ts | 9 +++-- src/common/buffer/Marker.ts | 23 +++++------- .../parser/EscapeSequenceParser.test.ts | 2 +- src/common/parser/EscapeSequenceParser.ts | 23 ++++++------ src/common/services/BufferService.ts | 7 +--- src/common/services/DecorationService.ts | 36 ++++++++++--------- src/headless/Terminal.ts | 8 ----- 19 files changed, 94 insertions(+), 142 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 522f6fb6..3276a1d8 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -12,6 +12,7 @@ import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/ import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; import { ICoreService } from 'common/services/Services'; +import { toDisposable } from 'common/Lifecycle'; interface ICursorState { x: number; @@ -55,12 +56,10 @@ export class CursorRenderLayer extends BaseRenderLayer { 'underline': this._renderUnderlineCursor.bind(this) }; this.onOptionsChanged(terminal); - } - - public override dispose(): void { - this._cursorBlinkStateManager?.dispose(); - this._cursorBlinkStateManager = undefined; - super.dispose(); + this.register(toDisposable(() => { + this._cursorBlinkStateManager?.dispose(); + this._cursorBlinkStateManager = undefined; + })); } public resize(terminal: Terminal, dim: IRenderDimensions): void { diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index eba283d5..d1df4075 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -9,7 +9,7 @@ import { IBuffer } from 'common/buffer/Types'; import { isMac } from 'common/Platform'; import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer'; import { addDisposableDomListener } from 'browser/Lifecycle'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { IRenderService } from 'browser/services/Services'; import { removeElementFromParent } from 'browser/Dom'; @@ -104,12 +104,10 @@ export class AccessibilityManager extends Disposable { // This shouldn't be needed on modern browsers but is present in case the // media query that drives the ScreenDprMonitor isn't supported this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions())); - } - - public dispose(): void { - super.dispose(); - removeElementFromParent(this._accessibilityTreeRoot); - this._rowElements.length = 0; + this.register(toDisposable(() => { + removeElementFromParent(this._accessibilityTreeRoot); + this._rowElements.length = 0; + })); } private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index cf5b9dd2..c0fc2641 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -8,7 +8,7 @@ import { IDisposable } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle'; +import { Disposable, getDisposeArrayDisposable, disposeArray, toDisposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; export class Linkifier2 extends Disposable implements ILinkifier2 { @@ -36,11 +36,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { ) { super(); this.register(getDisposeArrayDisposable(this._linkCacheDisposables)); - } - - public dispose(): void { - super.dispose(); - this._lastMouseEvent = undefined; + this.register(toDisposable(() => { + this._lastMouseEvent = undefined; + })); } public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { diff --git a/src/browser/ScreenDprMonitor.ts b/src/browser/ScreenDprMonitor.ts index 8129da07..1c3f31b7 100644 --- a/src/browser/ScreenDprMonitor.ts +++ b/src/browser/ScreenDprMonitor.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void; @@ -26,6 +26,9 @@ export class ScreenDprMonitor extends Disposable { constructor(private _parentWindow: Window) { super(); this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio; + this.register(toDisposable(() => { + this.clearListener(); + })); } public setListener(listener: ScreenDprListener): void { @@ -43,11 +46,6 @@ export class ScreenDprMonitor extends Disposable { this._updateDpr(); } - public dispose(): void { - super.dispose(); - this.clearListener(); - } - private _updateDpr(): void { if (!this._outerListener) { return; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 14dbca38..27a22fe6 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -56,6 +56,7 @@ import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer import { DecorationService } from 'common/services/DecorationService'; import { IDecorationService } from 'common/services/Services'; import { OscLinkProvider } from 'browser/OscLinkProvider'; +import { toDisposable } from 'common/Lifecycle'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -184,6 +185,11 @@ export class Terminal extends CoreTerminal implements ITerminal { // Setup listeners this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); + + this.register(toDisposable(() => { + this._customKeyEventHandler = undefined; + this.element?.parentNode?.removeChild(this.element); + })); } /** @@ -235,17 +241,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.viewport?.onThemeChange(this._colorManager.colors); } - public dispose(): void { - if (this._isDisposed) { - return; - } - super.dispose(); - this._renderService?.dispose(); - this._customKeyEventHandler = undefined; - this.write = () => { }; - this.element?.parentNode?.removeChild(this.element); - } - protected _setup(): void { super._setup(); diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 7fcc5ea9..5836e266 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -5,7 +5,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IRenderService } from 'browser/services/Services'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services'; export class BufferDecorationRenderer extends Disposable { @@ -39,12 +39,10 @@ export class BufferDecorationRenderer extends Disposable { })); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); - } - - public override dispose(): void { - this._container.remove(); - this._decorationElements.clear(); - super.dispose(); + this.register(toDisposable(() => { + this._container.remove(); + this._decorationElements.clear(); + })); } private _queueRefresh(): void { diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index e7db50c5..9251d2e3 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -6,7 +6,7 @@ import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; // Helper objects to avoid excessive calculation and garbage collection during rendering. These are @@ -68,6 +68,9 @@ export class OverviewRulerRenderer extends Disposable { this._registerDecorationListeners(); this._registerBufferChangeListeners(); this._registerDimensionChangeListeners(); + this.register(toDisposable(() => { + this._canvas?.remove(); + })); } /** @@ -120,11 +123,6 @@ export class OverviewRulerRenderer extends Disposable { this._queueRefresh(true); } - public override dispose(): void { - this._canvas?.remove(); - super.dispose(); - } - private _refreshDrawConstants(): void { // width const outerWidth = Math.floor(this._canvas.width / 3); diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 6a70ccec..e40c44d7 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -6,7 +6,7 @@ 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/shared/Constants'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } 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'; @@ -89,16 +89,14 @@ export class DomRenderer extends Disposable implements IRenderer { this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e))); this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e))); - } - public dispose(): void { - this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); + this.register(toDisposable(() => { + this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); - // Outside influences such as React unmounts may manipulate the DOM before our disposal. - // https://github.com/xtermjs/xterm.js/issues/2960 - removeElementFromParent(this._rowContainer, this._selectionContainer, this._themeStyleElement, this._dimensionsStyleElement); - - super.dispose(); + // Outside influences such as React unmounts may manipulate the DOM before our disposal. + // https://github.com/xtermjs/xterm.js/issues/2960 + removeElementFromParent(this._rowContainer, this._selectionContainer, this._themeStyleElement, this._dimensionsStyleElement); + })); } private _updateDimensions(): void { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 3a48281c..b79b0910 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -169,10 +169,6 @@ export class RenderService extends Disposable implements IRenderService { this._onDimensionsChange.fire(this._renderer.dimensions); } - public dispose(): void { - super.dispose(); - } - public hasRenderer(): boolean { return !!this._renderer; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 3780c7ee..130537d1 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -15,7 +15,7 @@ import { IBufferRange, ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; import { moveToCellSequence } from 'browser/input/MoveToCell'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { getRangeLength } from 'common/buffer/BufferRange'; /** @@ -148,10 +148,10 @@ export class SelectionService extends Disposable implements ISelectionService { this._model = new SelectionModel(this._bufferService); this._activeSelectionMode = SelectionMode.NORMAL; - } - public dispose(): void { - this._removeMouseDownListeners(); + this.register(toDisposable(() => { + this._removeMouseDownListeners(); + })); } public reset(): void { diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 2ad6f735..bc065dda 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; 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'; @@ -143,15 +143,11 @@ 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)); - } - public dispose(): void { - if (this._isDisposed) { - return; - } - super.dispose(); - this._windowsMode?.dispose(); - this._windowsMode = undefined; + this.register(toDisposable(() => { + this._windowsMode?.dispose(); + this._windowsMode = undefined; + })); } public write(data: string | Uint8Array, callback?: () => void): void { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 7f0879ef..22c2b352 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -382,10 +382,6 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.registerDcsHandler({ intermediates: '$', final: 'q' }, new DcsHandler((data, params) => this.requestStatusString(data, params))); } - public dispose(): void { - super.dispose(); - } - /** * Async parse support. */ diff --git a/src/common/Lifecycle.ts b/src/common/Lifecycle.ts index b3a7cc21..7ccc8aa7 100644 --- a/src/common/Lifecycle.ts +++ b/src/common/Lifecycle.ts @@ -17,15 +17,18 @@ export abstract class Disposable implements IDisposable { } /** - * Disposes the object, triggering the `dispose` method on all registered IDisposables. + * Disposes the object, triggering the `dispose` method on all registered IDisposables. This is a + * readonly property instead of a method to prevent subclasses overriding it which is an easy + * mistake that can introduce memory leaks. If a class extends Disposable, all dispose calls + * should be done via {@link register}. */ - public dispose(): void { + public readonly dispose = (): void => { this._isDisposed = true; for (const d of this._disposables) { d.dispose(); } this._disposables.length = 0; - } + }; /** * Registers a disposable object. diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 56d64a72..b01dde88 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -3,35 +3,28 @@ * @license MIT */ -import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { Disposable } from 'common/Lifecycle'; +import { EventEmitter } from 'common/EventEmitter'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IMarker } from 'common/Types'; export class Marker extends Disposable implements IMarker { private static _nextId = 1; private _id: number = Marker._nextId++; - public isDisposed: boolean = false; - public get id(): number { return this._id; } private readonly _onDispose = new EventEmitter(); public readonly onDispose = this._onDispose.event; + public get isDisposed(): boolean { return this._isDisposed; }; + constructor( public line: number ) { super(); - } - - public dispose(): void { - if (this.isDisposed) { - return; - } - this.isDisposed = true; - this.line = -1; - // Emit before super.dispose such that dispose listeners get a change to react - this._onDispose.fire(); - super.dispose(); + this.register(toDisposable(() => { + this.line = -1; + this._onDispose.fire(); + })); } } diff --git a/src/common/parser/EscapeSequenceParser.test.ts b/src/common/parser/EscapeSequenceParser.test.ts index 4b23fa00..76e88dea 100644 --- a/src/common/parser/EscapeSequenceParser.test.ts +++ b/src/common/parser/EscapeSequenceParser.test.ts @@ -87,7 +87,7 @@ class TestEscapeSequenceParser extends EscapeSequenceParser { } } public mockOscParser(): void { - this._oscParser = oscPutParser; + (this as any)._oscParser = oscPutParser; } public identifier(id: IFunctionIdentifier): number { return this._identifier(id); diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index f20a7e91..5bcd2dd3 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -5,7 +5,7 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types'; import { ParserState, ParserAction } from 'common/parser/Constants'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IDisposable } from 'common/Types'; import { fill } from 'common/TypedArrayUtils'; import { Params } from 'common/parser/Params'; @@ -242,8 +242,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP protected _executeHandlers: { [flag: number]: ExecuteHandlerType }; protected _csiHandlers: IHandlerCollection; protected _escHandlers: IHandlerCollection; - protected _oscParser: IOscParser; - protected _dcsParser: IDcsParser; + protected readonly _oscParser: IOscParser; + protected readonly _dcsParser: IDcsParser; protected _errorHandler: (state: IParsingState) => IParsingState; // fallback handlers @@ -284,8 +284,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP this._executeHandlers = Object.create(null); this._csiHandlers = Object.create(null); this._escHandlers = Object.create(null); - this._oscParser = new OscParser(); - this._dcsParser = new DcsParser(); + this.register(toDisposable(() => { + this._csiHandlers = Object.create(null); + this._executeHandlers = Object.create(null); + this._escHandlers = Object.create(null); + })); + this._oscParser = this.register(new OscParser()); + this._dcsParser = this.register(new DcsParser()); this._errorHandler = this._errorHandlerFb; // swallow 7bit ST (ESC+\) @@ -338,14 +343,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP return res.reverse().join(''); } - public dispose(): void { - this._csiHandlers = Object.create(null); - this._executeHandlers = Object.create(null); - this._escHandlers = Object.create(null); - this._oscParser.dispose(); - this._dcsParser.dispose(); - } - public setPrintHandler(handler: PrintHandlerType): void { this._printHandler = handler; } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 1bc93041..eaa355b2 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -36,12 +36,7 @@ export class BufferService extends Disposable implements IBufferService { super(); this.cols = Math.max(optionsService.rawOptions.cols || 0, MINIMUM_COLS); this.rows = Math.max(optionsService.rawOptions.rows || 0, MINIMUM_ROWS); - this.buffers = new BufferSet(optionsService, this); - } - - public dispose(): void { - super.dispose(); - this.buffers.dispose(); + this.buffers = this.register(new BufferSet(optionsService, this)); } public resize(cols: number, rows: number): void { diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 522b04de..c27e7b2f 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -5,7 +5,7 @@ import { css } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { SortedList } from 'common/SortedList'; import { IColor } from 'common/Types'; @@ -32,6 +32,16 @@ export class DecorationService extends Disposable implements IDecorationService public get decorations(): IterableIterator { return this._decorations.values(); } + constructor() { + super(); + + this.register(toDisposable(() => { + for (const d of this._decorations.values()) { + this._onDecorationRemoved.fire(d); + } + this.reset(); + })); + } public registerDecoration(options: IDecorationOptions): IDecoration | undefined { if (options.marker.isDisposed) { return undefined; @@ -81,25 +91,19 @@ export class DecorationService extends Disposable implements IDecorationService } }); } - - public dispose(): void { - for (const d of this._decorations.values()) { - this._onDecorationRemoved.fire(d); - } - this.reset(); - } } class Decoration extends Disposable implements IInternalDecoration { public readonly marker: IMarker; 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 get isDisposed(): boolean { return this._isDisposed; } + private _cachedBg: IColor | undefined | null = null; public get backgroundColorRGB(): IColor | undefined { if (this._cachedBg === null) { @@ -132,14 +136,12 @@ class Decoration extends Disposable implements IInternalDecoration { if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) { this.options.overviewRulerOptions.position = 'full'; } - } - public override dispose(): void { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - this._onDispose.fire(); - super.dispose(); + this.register(toDisposable(() => { + if (this._isDisposed) { + return; + } + this._onDispose.fire(); + })); } } diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 639988eb..39465205 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -71,14 +71,6 @@ export class Terminal extends CoreTerminal { this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); } - public dispose(): void { - if (this._isDisposed) { - return; - } - super.dispose(); - this.write = () => { }; - } - /** * Convenience property to active buffer. */ From f61d8c91a39b56b61141e0e62a77c979ef58938f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 08:30:39 -0700 Subject: [PATCH 56/87] Properly register disposables in SearchAddon --- addons/xterm-addon-search/src/SearchAddon.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 689899ef..249dd594 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -5,6 +5,7 @@ import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm'; import { EventEmitter } from 'common/EventEmitter'; +import { Disposable, toDisposable } from 'common/Lifecycle'; export interface ISearchOptions { regex?: boolean; @@ -50,7 +51,7 @@ type LineCacheEntry = [ const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?'; const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs -export class SearchAddon implements ITerminalAddon { +export class SearchAddon extends Disposable implements ITerminalAddon { private _terminal: Terminal | undefined; private _cachedSearchTerm: string | undefined; private _selectedDecoration: IDecoration | undefined; @@ -72,13 +73,18 @@ export class SearchAddon implements ITerminalAddon { private _resultIndex: number | undefined; - private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>(); + private readonly _onDidChangeResults = this.register(new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>()); public readonly onDidChangeResults = this._onDidChangeResults.event; public activate(terminal: Terminal): void { this._terminal = terminal; - this._onDataDisposable = this._terminal.onWriteParsed(() => this._updateMatches()); - this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches()); + this._onDataDisposable = this.register(this._terminal.onWriteParsed(() => this._updateMatches())); + this._onResizeDisposable = this.register(this._terminal.onResize(() => this._updateMatches())); + this.register(toDisposable(() => { + this.clearDecorations(); + this._onDataDisposable?.dispose(); + this._onResizeDisposable?.dispose(); + })); } private _updateMatches(): void { @@ -94,12 +100,6 @@ export class SearchAddon implements ITerminalAddon { } } - public dispose(): void { - this.clearDecorations(); - this._onDataDisposable?.dispose(); - this._onResizeDisposable?.dispose(); - } - public clearDecorations(retainCachedSearchTerm?: boolean): void { this._selectedDecoration?.dispose(); this._searchResults?.clear(); From 00f6d3a88c9b85c377cadaca03fb19233ee72526 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 09:00:14 -0700 Subject: [PATCH 57/87] Register all event emitters, make some services disposable --- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 6 ++-- src/browser/Terminal.ts | 22 +++++++------- src/browser/renderer/dom/DomRenderer.ts | 2 +- src/browser/services/CharSizeService.ts | 2 +- src/browser/services/RenderService.ts | 8 ++--- src/common/CircularList.ts | 10 ++++--- src/common/CoreTerminal.ts | 30 +++++++++---------- src/common/InputHandler.ts | 26 ++++++++-------- src/common/buffer/Marker.ts | 4 +-- src/common/input/WriteBuffer.ts | 9 ++++-- src/common/public/BufferNamespaceApi.ts | 2 +- src/common/services/BufferService.ts | 4 +-- src/common/services/CoreMouseService.ts | 6 ++-- src/common/services/LogService.ts | 8 +++-- src/common/services/OptionsService.ts | 6 ++-- src/common/services/UnicodeService.ts | 6 ++-- src/headless/Terminal.ts | 10 +++---- 18 files changed, 89 insertions(+), 76 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 0586e6ff..0f00c100 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -16,9 +16,9 @@ export class WebglAddon extends Disposable implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private readonly _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = this.register(new EventEmitter()); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onContextLoss = new EventEmitter(); + private readonly _onContextLoss = this.register(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 6f213527..a554085e 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -46,11 +46,11 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - private readonly _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = this.register(new EventEmitter()); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onRequestRedraw = new EventEmitter(); + private readonly _onRequestRedraw = this.register(new EventEmitter()); public readonly onRequestRedraw = this._onRequestRedraw.event; - private readonly _onContextLoss = new EventEmitter(); + private readonly _onContextLoss = this.register(new EventEmitter()); public readonly onContextLoss = this._onContextLoss.event; constructor( diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 27a22fe6..fef92d3d 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -123,28 +123,28 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; - private readonly _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = this.register(new EventEmitter()); public readonly onCursorMove = this._onCursorMove.event; - private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); + private readonly _onKey = this.register(new EventEmitter<{ key: string, domEvent: KeyboardEvent }>()); public readonly onKey = this._onKey.event; - private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRender = this.register(new EventEmitter<{ start: number, end: number }>()); public readonly onRender = this._onRender.event; - private readonly _onSelectionChange = new EventEmitter(); + private readonly _onSelectionChange = this.register(new EventEmitter()); public readonly onSelectionChange = this._onSelectionChange.event; - private readonly _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = this.register(new EventEmitter()); public readonly onTitleChange = this._onTitleChange.event; - private readonly _onBell = new EventEmitter(); + private readonly _onBell = this.register(new EventEmitter()); public readonly onBell = this._onBell.event; - private _onFocus = new EventEmitter(); + private _onFocus = this.register(new EventEmitter()); public get onFocus(): IEvent { return this._onFocus.event; } - private _onBlur = new EventEmitter(); + private _onBlur = this.register(new EventEmitter()); public get onBlur(): IEvent { return this._onBlur.event; } - private _onA11yCharEmitter = new EventEmitter(); + private _onA11yCharEmitter = this.register(new EventEmitter()); public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } - private _onA11yTabEmitter = new EventEmitter(); + private _onA11yTabEmitter = this.register(new EventEmitter()); public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } - private _onWillOpen = new EventEmitter(); + private _onWillOpen = this.register(new EventEmitter()); public get onWillOpen(): IEvent { return this._onWillOpen.event; } /** diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index e40c44d7..b474bc7a 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 readonly onRequestRedraw = new EventEmitter().event; + public readonly onRequestRedraw = this.register(new EventEmitter()).event; constructor( private _colors: IColorSet, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 7062deec..80116f00 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 readonly _onCharSizeChange = new EventEmitter(); + private readonly _onCharSizeChange = this.register(new EventEmitter()); public readonly onCharSizeChange = this._onCharSizeChange.event; constructor( diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index b79b0910..a063b37f 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -40,13 +40,13 @@ export class RenderService extends Disposable implements IRenderService { columnSelectMode: false }; - private readonly _onDimensionsChange = new EventEmitter(); + private readonly _onDimensionsChange = this.register(new EventEmitter()); public readonly onDimensionsChange = this._onDimensionsChange.event; - private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRenderedViewportChange = this.register(new EventEmitter<{ start: number, end: number }>()); public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; - private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRender = this.register(new EventEmitter<{ start: number, end: number }>()); public readonly onRender = this._onRender.event; - private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRefreshRequest = this.register(new EventEmitter<{ start: number, end: number }>()); public readonly onRefreshRequest = this._onRefreshRequest.event; public get dimensions(): IRenderDimensions { return this._renderer!.dimensions; } diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 599db1ab..b7e1e075 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -5,6 +5,7 @@ import { ICircularList } from 'common/Types'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; export interface IInsertEvent { index: number; @@ -20,21 +21,22 @@ export interface IDeleteEvent { * Represents a circular list; a list with a maximum size that wraps around when push is called, * overriding values at the start of the list. */ -export class CircularList implements ICircularList { +export class CircularList extends Disposable implements ICircularList { protected _array: (T | undefined)[]; private _startIndex: number; private _length: number; - public readonly onDeleteEmitter = new EventEmitter(); + public readonly onDeleteEmitter = this.register(new EventEmitter()); public readonly onDelete = this.onDeleteEmitter.event; - public readonly onInsertEmitter = new EventEmitter(); + public readonly onInsertEmitter = this.register(new EventEmitter()); public readonly onInsert = this.onInsertEmitter.event; - public readonly onTrimEmitter = new EventEmitter(); + public readonly onTrimEmitter = this.register(new EventEmitter()); public readonly onTrim = this.onTrimEmitter.event; constructor( private _maxLength: number ) { + super(); this._array = new Array(this._maxLength); this._startIndex = 0; this._length = 0; diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index bc065dda..ea2528db 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -59,15 +59,15 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - private readonly _onBinary = new EventEmitter(); + private readonly _onBinary = this.register(new EventEmitter()); public readonly onBinary = this._onBinary.event; - private readonly _onData = new EventEmitter(); + private readonly _onData = this.register(new EventEmitter()); public readonly onData = this._onData.event; - protected _onLineFeed = new EventEmitter(); + protected _onLineFeed = this.register(new EventEmitter()); public readonly onLineFeed = this._onLineFeed.event; - private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); + private readonly _onResize = this.register(new EventEmitter<{ cols: number, rows: number }>()); public readonly onResize = this._onResize.event; - protected readonly _onWriteParsed = new EventEmitter(); + protected readonly _onWriteParsed = this.register(new EventEmitter()); public readonly onWriteParsed = this._onWriteParsed.event; /** @@ -75,13 +75,13 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { * it's filtered out. */ protected _onScrollApi?: EventEmitter; - protected _onScroll = new EventEmitter(); + protected _onScroll = this.register(new EventEmitter()); public get onScroll(): IEvent { if (!this._onScrollApi) { - this._onScrollApi = new EventEmitter(); - this.register(this._onScroll.event(ev => { + this._onScrollApi = this.register(new EventEmitter()); + this._onScroll.event(ev => { this._onScrollApi?.fire(ev.position); - })); + }); } return this._onScrollApi.event; } @@ -103,17 +103,17 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup and initialize services this._instantiationService = new InstantiationService(); - this.optionsService = new OptionsService(options); + this.optionsService = this.register(new OptionsService(options)); this._instantiationService.setService(IOptionsService, this.optionsService); this._bufferService = this.register(this._instantiationService.createInstance(BufferService)); this._instantiationService.setService(IBufferService, this._bufferService); - this._logService = this._instantiationService.createInstance(LogService); + this._logService = this.register(this._instantiationService.createInstance(LogService)); this._instantiationService.setService(ILogService, this._logService); this.coreService = this.register(this._instantiationService.createInstance(CoreService, () => this.scrollToBottom())); this._instantiationService.setService(ICoreService, this.coreService); - this.coreMouseService = this._instantiationService.createInstance(CoreMouseService); + this.coreMouseService = this.register(this._instantiationService.createInstance(CoreMouseService)); this._instantiationService.setService(ICoreMouseService, this.coreMouseService); - this.unicodeService = this._instantiationService.createInstance(UnicodeService); + this.unicodeService = this.register(this._instantiationService.createInstance(UnicodeService)); this._instantiationService.setService(IUnicodeService, this.unicodeService); this._charsetService = this._instantiationService.createInstance(CharsetService); this._instantiationService.setService(ICharsetService, this._charsetService); @@ -121,7 +121,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._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); + this._inputHandler = this.register(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); @@ -141,7 +141,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { })); // Setup WriteBuffer - this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); + this._writeBuffer = this.register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult))); this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); this.register(toDisposable(() => { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 22c2b352..b91b446c 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -132,32 +132,32 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; - private readonly _onRequestBell = new EventEmitter(); + private readonly _onRequestBell = this.register(new EventEmitter()); public readonly onRequestBell = this._onRequestBell.event; - private readonly _onRequestRefreshRows = new EventEmitter(); + private readonly _onRequestRefreshRows = this.register(new EventEmitter()); public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; - private readonly _onRequestReset = new EventEmitter(); + private readonly _onRequestReset = this.register(new EventEmitter()); public readonly onRequestReset = this._onRequestReset.event; - private readonly _onRequestSendFocus = new EventEmitter(); + private readonly _onRequestSendFocus = this.register(new EventEmitter()); public readonly onRequestSendFocus = this._onRequestSendFocus.event; - private readonly _onRequestSyncScrollBar = new EventEmitter(); + private readonly _onRequestSyncScrollBar = this.register(new EventEmitter()); public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; - private readonly _onRequestWindowsOptionsReport = new EventEmitter(); + private readonly _onRequestWindowsOptionsReport = this.register(new EventEmitter()); public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; - private readonly _onA11yChar = new EventEmitter(); + private readonly _onA11yChar = this.register(new EventEmitter()); public readonly onA11yChar = this._onA11yChar.event; - private readonly _onA11yTab = new EventEmitter(); + private readonly _onA11yTab = this.register(new EventEmitter()); public readonly onA11yTab = this._onA11yTab.event; - private readonly _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = this.register(new EventEmitter()); public readonly onCursorMove = this._onCursorMove.event; - private readonly _onLineFeed = new EventEmitter(); + private readonly _onLineFeed = this.register(new EventEmitter()); public readonly onLineFeed = this._onLineFeed.event; - private readonly _onScroll = new EventEmitter(); + private readonly _onScroll = this.register(new EventEmitter()); public readonly onScroll = this._onScroll.event; - private readonly _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = this.register(new EventEmitter()); public readonly onTitleChange = this._onTitleChange.event; - private readonly _onColor = new EventEmitter(); + private readonly _onColor = this.register(new EventEmitter()); public readonly onColor = this._onColor.event; private _parseStack: IParseStack = { diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index b01dde88..54bcb84f 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -13,10 +13,10 @@ export class Marker extends Disposable implements IMarker { private _id: number = Marker._nextId++; public get id(): number { return this._id; } - private readonly _onDispose = new EventEmitter(); + private readonly _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; - public get isDisposed(): boolean { return this._isDisposed; }; + public get isDisposed(): boolean { return this._isDisposed; } constructor( public line: number diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index cb40ffa1..68dbc6f7 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -5,6 +5,7 @@ */ import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; declare const setTimeout: (handler: () => void, timeout?: number) => void; @@ -33,7 +34,7 @@ const WRITE_TIMEOUT_MS = 12; */ const WRITE_BUFFER_LENGTH_THRESHOLD = 50; -export class WriteBuffer { +export class WriteBuffer extends Disposable { private _writeBuffer: (string | Uint8Array)[] = []; private _callbacks: ((() => void) | undefined)[] = []; private _pendingData = 0; @@ -42,10 +43,12 @@ export class WriteBuffer { private _syncCalls = 0; private _didUserInput = false; - private readonly _onWriteParsed = new EventEmitter(); + private readonly _onWriteParsed = this.register(new EventEmitter()); public readonly onWriteParsed = this._onWriteParsed.event; - constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } + constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { + super(); + } public handleUserInput(): void { this._didUserInput = true; diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index 033f5955..9e49ce2b 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -5,7 +5,7 @@ import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm'; import { BufferApiView } from 'common/public/BufferApiView'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { EventEmitter } from 'common/EventEmitter'; import { ICoreTerminal } from 'common/Types'; export class BufferNamespaceApi implements IBufferNamespaceApi { diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index eaa355b2..f238206c 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -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 readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); + private readonly _onResize = this.register(new EventEmitter<{ cols: number, rows: number }>()); public readonly onResize = this._onResize.event; - private readonly _onScroll = new EventEmitter(); + private readonly _onScroll = this.register(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 8c2a24de..d955370e 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -5,6 +5,7 @@ import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; +import { Disposable } from 'common/Lifecycle'; /** * Supported default protocols. @@ -165,20 +166,21 @@ const DEFAULT_ENCODINGS: { [key: string]: CoreMouseEncoding } = { * a tracking report to the backend based on protocol and encoding limitations. * To send a mouse event call `triggerMouseEvent`. */ -export class CoreMouseService implements ICoreMouseService { +export class CoreMouseService extends Disposable implements ICoreMouseService { private _protocols: { [name: string]: ICoreMouseProtocol } = {}; private _encodings: { [name: string]: CoreMouseEncoding } = {}; private _activeProtocol: string = ''; private _activeEncoding: string = ''; private _lastEvent: ICoreMouseEvent | null = null; - private readonly _onProtocolChange = new EventEmitter(); + private readonly _onProtocolChange = this.register(new EventEmitter()); public readonly onProtocolChange = this._onProtocolChange.event; constructor( @IBufferService private readonly _bufferService: IBufferService, @ICoreService private readonly _coreService: ICoreService ) { + super(); // register default protocols and encodings for (const name of Object.keys(DEFAULT_PROTOCOLS)) this.addProtocol(name, DEFAULT_PROTOCOLS[name]); for (const name of Object.keys(DEFAULT_ENCODINGS)) this.addEncoding(name, DEFAULT_ENCODINGS[name]); diff --git a/src/common/services/LogService.ts b/src/common/services/LogService.ts index d3566567..854f85de 100644 --- a/src/common/services/LogService.ts +++ b/src/common/services/LogService.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { Disposable } from 'common/Lifecycle'; import { ILogService, IOptionsService, LogLevelEnum } from 'common/services/Services'; type LogType = (message?: any, ...optionalParams: any[]) => void; @@ -29,7 +30,7 @@ const optionsKeyToLogLevel: { [key: string]: LogLevelEnum } = { const LOG_PREFIX = 'xterm.js: '; -export class LogService implements ILogService { +export class LogService extends Disposable implements ILogService { public serviceBrand: any; public logLevel: LogLevelEnum = LogLevelEnum.OFF; @@ -37,12 +38,13 @@ export class LogService implements ILogService { constructor( @IOptionsService private readonly _optionsService: IOptionsService ) { + super(); this._updateLogLevel(); - this._optionsService.onOptionChange(key => { + this.register(this._optionsService.onOptionChange(key => { if (key === 'logLevel') { this._updateLogLevel(); } - }); + })); } private _updateLogLevel(): void { diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 33aa3ee2..336591e5 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -7,6 +7,7 @@ import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/S import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { CursorStyle } from 'common/Types'; +import { Disposable } from 'common/Lifecycle'; export const DEFAULT_OPTIONS: Readonly> = { cols: 80, @@ -51,16 +52,17 @@ export const DEFAULT_OPTIONS: Readonly> = { const FONT_WEIGHT_OPTIONS: Extract[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']; -export class OptionsService implements IOptionsService { +export class OptionsService extends Disposable implements IOptionsService { public serviceBrand: any; public readonly rawOptions: Required; public options: Required; - private readonly _onOptionChange = new EventEmitter(); + private readonly _onOptionChange = this.register(new EventEmitter()); public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { + super(); // set the default value of each option const defaultOptions = { ...DEFAULT_OPTIONS }; for (const key in options) { diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 239f4d62..44386e54 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -5,19 +5,21 @@ import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { UnicodeV6 } from 'common/input/UnicodeV6'; +import { Disposable } from 'common/Lifecycle'; -export class UnicodeService implements IUnicodeService { +export class UnicodeService extends Disposable implements IUnicodeService { public serviceBrand: any; private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null); private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - private readonly _onChange = new EventEmitter(); + private readonly _onChange = super.register(new EventEmitter(); public readonly onChange = this._onChange.event; constructor() { + super(); const defaultProvider = new UnicodeV6(); this.register(defaultProvider); this._active = defaultProvider.version; diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 39465205..f021c42b 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 readonly _onBell = new EventEmitter(); + private readonly _onBell = this.register(new EventEmitter()); public readonly onBell = this._onBell.event; - private readonly _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = this.register(new EventEmitter()); public readonly onCursorMove = this._onCursorMove.event; - private readonly _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = this.register(new EventEmitter()); public readonly onTitleChange = this._onTitleChange.event; - private readonly _onA11yCharEmitter = new EventEmitter(); + private readonly _onA11yCharEmitter = this.register(new EventEmitter()); public readonly onA11yChar = this._onA11yCharEmitter.event; - private readonly _onA11yTabEmitter = new EventEmitter(); + private readonly _onA11yTabEmitter = this.register(new EventEmitter()); public readonly onA11yTab = this._onA11yTabEmitter.event; /** From 94c3d112396538142a94a68beb9873aea033a137 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 09:11:28 -0700 Subject: [PATCH 58/87] Fix compile --- src/browser/services/CharSizeService.ts | 6 ++++-- src/common/services/UnicodeService.ts | 11 ++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 80116f00..267a361b 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -4,10 +4,11 @@ */ import { IOptionsService } from 'common/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { EventEmitter } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; +import { Disposable } from 'common/Lifecycle'; -export class CharSizeService implements ICharSizeService { +export class CharSizeService extends Disposable implements ICharSizeService { public serviceBrand: undefined; public width: number = 0; @@ -24,6 +25,7 @@ export class CharSizeService implements ICharSizeService { parentElement: HTMLElement, @IOptionsService private readonly _optionsService: IOptionsService ) { + super(); this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService); } diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 44386e54..5c5b74f6 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -5,27 +5,28 @@ import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { UnicodeV6 } from 'common/input/UnicodeV6'; -import { Disposable } from 'common/Lifecycle'; - -export class UnicodeService extends Disposable implements IUnicodeService { +export class UnicodeService implements IUnicodeService { public serviceBrand: any; private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null); private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - private readonly _onChange = super.register(new EventEmitter(); + private readonly _onChange = new EventEmitter(); public readonly onChange = this._onChange.event; constructor() { - super(); const defaultProvider = new UnicodeV6(); this.register(defaultProvider); this._active = defaultProvider.version; this._activeProvider = defaultProvider; } + public dispose(): void { + this._onChange.dispose(); + } + public get versions(): string[] { return Object.keys(this._providers); } From d3976c05f17acd6b2bf0a7c735867dabad5f75bb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 09:27:58 -0700 Subject: [PATCH 59/87] Fix marker dispose unit tests --- src/browser/Terminal.test.ts | 1 - src/common/buffer/Marker.ts | 35 ++++++++++++++++++++++++----------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index eaf420ed..a9d0b772 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -1378,7 +1378,6 @@ describe('Terminal', () => { assert.deepEqual(disposeStack, [markers[0], markers[1]]); // trimmed marker objs should be disposed assert.deepEqual(disposeStack.map(el => el.isDisposed), [true, true]); - assert.deepEqual(disposeStack.map(el => (el as any)._isDisposed), [true, true]); // trimmed markers should contain line -1 assert.deepEqual(disposeStack.map(el => el.line), [-1, -1]); }); diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 54bcb84f..e639a684 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -4,27 +4,40 @@ */ import { EventEmitter } from 'common/EventEmitter'; -import { Disposable, toDisposable } from 'common/Lifecycle'; -import { IMarker } from 'common/Types'; +import { disposeArray } from 'common/Lifecycle'; +import { IDisposable, IMarker } from 'common/Types'; -export class Marker extends Disposable implements IMarker { +export class Marker implements IMarker { private static _nextId = 1; + public isDisposed: boolean = false; + private _disposables: IDisposable[] = []; + private _id: number = Marker._nextId++; public get id(): number { return this._id; } - private readonly _onDispose = this.register(new EventEmitter()); - public readonly onDispose = this._onDispose.event; - public get isDisposed(): boolean { return this._isDisposed; } + private readonly _onDispose = new EventEmitter(); + public readonly onDispose = this._onDispose.event; constructor( public line: number ) { - super(); - this.register(toDisposable(() => { - this.line = -1; - this._onDispose.fire(); - })); + } + + public dispose(): void { + if (this.isDisposed) { + return; + } + this.isDisposed = true; + this.line = -1; + // Emit before super.dispose such that dispose listeners get a change to react + this._onDispose.fire(); + disposeArray(this._disposables); + } + + public register(disposable: IDisposable): void { + this._disposables.push(disposable); + this._disposables.length = 0; } } From d8cf3af555a88056bdb2b93d9253b0e8ef9ee0f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 10:23:19 -0700 Subject: [PATCH 60/87] Fix Marker.dispose --- src/common/buffer/Marker.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index e639a684..0629e26a 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -16,8 +16,7 @@ export class Marker implements IMarker { private _id: number = Marker._nextId++; public get id(): number { return this._id; } - - private readonly _onDispose = new EventEmitter(); + private readonly _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; constructor( @@ -34,10 +33,11 @@ export class Marker implements IMarker { // Emit before super.dispose such that dispose listeners get a change to react this._onDispose.fire(); disposeArray(this._disposables); - } - - public register(disposable: IDisposable): void { - this._disposables.push(disposable); this._disposables.length = 0; } + + public register(disposable: T): T { + this._disposables.push(disposable); + return disposable; + } } From eecf52e949bb80b986e0b067143403f7a4db3ee7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 10:57:31 -0700 Subject: [PATCH 61/87] Add lint rule to ensure on prefix is only for event emitters Fixes #4183 --- .eslintrc.json | 6 ++- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 14 +++---- addons/xterm-addon-canvas/src/CanvasAddon.ts | 4 +- .../xterm-addon-canvas/src/CanvasRenderer.ts | 34 ++++++++-------- .../src/CursorRenderLayer.ts | 12 +++--- .../src/SelectionRenderLayer.ts | 8 ++-- .../xterm-addon-canvas/src/TextRenderLayer.ts | 4 +- addons/xterm-addon-canvas/src/Types.d.ts | 28 ++++++------- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 4 +- .../src/RectangleRenderer.ts | 2 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 38 +++++++++--------- .../src/renderLayer/BaseRenderLayer.ts | 14 +++---- .../src/renderLayer/CursorRenderLayer.ts | 14 +++---- .../src/renderLayer/Types.ts | 12 +++--- src/browser/ColorManager.ts | 2 +- src/browser/Terminal.ts | 28 ++++++------- src/browser/TestUtils.test.ts | 40 +++++++++---------- src/browser/Types.d.ts | 10 ++--- src/browser/Viewport.ts | 8 ++-- src/browser/renderer/dom/DomRenderer.ts | 18 ++++----- .../dom/DomRendererRowFactory.test.ts | 4 +- .../renderer/dom/DomRendererRowFactory.ts | 2 +- src/browser/renderer/shared/Types.d.ts | 16 ++++---- src/browser/selection/SelectionModel.test.ts | 6 +-- src/browser/selection/SelectionModel.ts | 2 +- src/browser/services/RenderService.ts | 40 +++++++++---------- src/browser/services/SelectionService.ts | 4 +- src/browser/services/Services.ts | 17 ++++---- 29 files changed, 198 insertions(+), 195 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index aa6e6417..5849fa51 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -96,7 +96,11 @@ { "selector": "enumMember", "format": ["UPPER_CASE"] }, // memberLike - Allow enum-like objects to use UPPER_CASE { "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] }, - { "selector": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] }, + // restrict on* naming for events only + { "selector": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"], "custom": { + "regex": "^on[A-Z].+", + "match": false + } }, // typeLike { "selector": "typeLike", "format": ["PascalCase"] }, { "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] } diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index b3b5fe10..b18bb209 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -75,13 +75,13 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer } } - public onOptionsChanged(): void {} - public onBlur(): void {} - public onFocus(): void {} - public onCursorMove(): void {} - public onGridChanged(startRow: number, endRow: number): void {} + public handleOptionsChanged(): void {} + public handleBlur(): void {} + public handleFocus(): void {} + public handleCursorMove(): void {} + public handleGridChanged(startRow: number, endRow: number): void {} - public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { this._selectionModel.update(this._terminal, start, end, columnSelectMode); } @@ -105,7 +105,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer // Regenerate char atlas and force a full redraw this._refreshCharAtlas(this._colors); - this.onGridChanged(0, this._bufferService.rows - 1); + this.handleGridChanged(0, this._bufferService.rows - 1); } /** diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 0378670b..c9c55e0d 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -45,11 +45,11 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); renderService.setRenderer(this._renderer); - renderService.onResize(bufferService.cols, bufferService.rows); + renderService.handleResize(bufferService.cols, bufferService.rows); this.register(toDisposable(() => { renderService.setRenderer((this._terminal as any)._core._createRenderer()); - renderService.onResize(terminal.cols, terminal.rows); + renderService.handleResize(terminal.cols, terminal.rows); this._renderer?.dispose(); this._renderer = undefined; })); diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index b4e41282..1abda096 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -69,7 +69,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); - this.onOptionsChanged(); + this.handleOptionsChanged(); this.register(toDisposable(() => { for (const l of this._renderLayers) { @@ -83,12 +83,12 @@ export class CanvasRenderer extends Disposable implements IRenderer { return this._renderLayers[0].cacheCanvas; } - public onDevicePixelRatioChange(): void { + public handleDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed if (this._devicePixelRatio !== this._coreBrowserService.dpr) { this._devicePixelRatio = this._coreBrowserService.dpr; - this.onResize(this._bufferService.cols, this._bufferService.rows); + this.handleResize(this._bufferService.cols, this._bufferService.rows); } } @@ -101,7 +101,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { } } - public onResize(cols: number, rows: number): void { + public handleResize(cols: number, rows: number): void { // Update character and canvas dimensions this._updateDimensions(); @@ -115,32 +115,32 @@ export class CanvasRenderer extends Disposable implements IRenderer { this._screenElement.style.height = `${this.dimensions.canvasHeight}px`; } - public onCharSizeChanged(): void { - this.onResize(this._bufferService.cols, this._bufferService.rows); + public handleCharSizeChanged(): void { + this.handleResize(this._bufferService.cols, this._bufferService.rows); } - public onBlur(): void { - this._runOperation(l => l.onBlur()); + public handleBlur(): void { + this._runOperation(l => l.handleBlur()); } - public onFocus(): void { - this._runOperation(l => l.onFocus()); + public handleFocus(): void { + this._runOperation(l => l.handleFocus()); } - public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode)); + public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + this._runOperation(l => l.handleSelectionChanged(start, end, columnSelectMode)); // Selection foreground requires a full re-render if (this._colors.selectionForeground) { this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } - public onCursorMove(): void { - this._runOperation(l => l.onCursorMove()); + public handleCursorMove(): void { + this._runOperation(l => l.handleCursorMove()); } - public onOptionsChanged(): void { - this._runOperation(l => l.onOptionsChanged()); + public handleOptionsChanged(): void { + this._runOperation(l => l.handleOptionsChanged()); } public clear(): void { @@ -159,7 +159,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { */ public renderRows(start: number, end: number): void { for (const l of this._renderLayers) { - l.onGridChanged(start, end); + l.handleGridChanged(start, end); } } diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 9c3ce718..d32eb229 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -79,20 +79,20 @@ export class CursorRenderLayer extends BaseRenderLayer { public reset(): void { this._clearCursor(); this._cursorBlinkStateManager?.restartBlinkAnimation(); - this.onOptionsChanged(); + this.handleOptionsChanged(); } - public onBlur(): void { + public handleBlur(): void { this._cursorBlinkStateManager?.pause(); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } - public onFocus(): void { + public handleFocus(): void { this._cursorBlinkStateManager?.resume(); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } - public onOptionsChanged(): void { + public handleOptionsChanged(): void { if (this._optionsService.rawOptions.cursorBlink) { if (!this._cursorBlinkStateManager) { this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => { @@ -108,11 +108,11 @@ export class CursorRenderLayer extends BaseRenderLayer { this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } - public onCursorMove(): void { + public handleCursorMove(): void { this._cursorBlinkStateManager?.restartBlinkAnimation(); } - public onGridChanged(startRow: number, endRow: number): void { + public handleGridChanged(startRow: number, endRow: number): void { if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { this._render(false); } else { diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 759b0a12..0b8408c0 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -59,18 +59,18 @@ export class SelectionRenderLayer extends BaseRenderLayer { } } - public onBlur(): void { + public handleBlur(): void { this.reset(); this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); } - public onFocus(): void { + public handleFocus(): void { this.reset(); this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); } - public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { - super.onSelectionChanged(start, end, columnSelectMode); + public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + super.handleSelectionChanged(start, end, columnSelectMode); this._redrawSelection(start, end, columnSelectMode); } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 8d5508e6..23ac55ad 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -236,7 +236,7 @@ export class TextRenderLayer extends BaseRenderLayer { this._forEachCell(firstRow, lastRow, (cell, x, y) => this._drawChars(cell, x, y)); } - public onGridChanged(firstRow: number, lastRow: number): void { + public handleGridChanged(firstRow: number, lastRow: number): void { // Resize has not been called yet if (this._state.cache.length === 0) { return; @@ -251,7 +251,7 @@ export class TextRenderLayer extends BaseRenderLayer { this._drawForeground(firstRow, lastRow); } - public onOptionsChanged(): void { + public handleOptionsChanged(): void { this._setTransparency(this._optionsService.rawOptions.allowTransparency); } diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index 75c5ccc9..b1c3f0e3 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -42,14 +42,14 @@ export interface IRenderer extends IDisposable { readonly onRequestRedraw: IEvent; 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; + handleDevicePixelRatioChange(): void; + handleResize(cols: number, rows: number): void; + handleCharSizeChanged(): void; + handleBlur(): void; + handleFocus(): void; + handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + handleCursorMove(): void; + handleOptionsChanged(): void; clear(): void; renderRows(start: number, end: number): void; clearTextureAtlas?(): void; @@ -62,22 +62,22 @@ export interface IRenderLayer extends IDisposable { /** * Called when the terminal loses focus. */ - onBlur(): void; + handleBlur(): void; /** * * Called when the terminal gets focus. */ - onFocus(): void; + handleFocus(): void; /** * Called when the cursor is moved. */ - onCursorMove(): void; + handleCursorMove(): void; /** * Called when options change. */ - onOptionsChanged(): void; + handleOptionsChanged(): void; /** * Called when the theme changes. @@ -88,12 +88,12 @@ export interface IRenderLayer extends IDisposable { * Called when the data in the grid has changed (or needs to be rendered * again). */ - onGridChanged(startRow: number, endRow: number): void; + handleGridChanged(startRow: number, endRow: number): void; /** * Calls when the selection changes. */ - onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; /** * Resize the render layer. diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index e04f081c..511c9381 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -167,7 +167,7 @@ export class GlyphRenderer extends Disposable { gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // Set viewport - this.onResize(); + this.handleResize(); } public beginFrame(): boolean { @@ -263,7 +263,7 @@ export class GlyphRenderer extends Disposable { } } - public onResize(): void { + public handleResize(): void { const gl = this._gl; gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); this.clear(); diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 50a8cd58..ff935d1c 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -151,7 +151,7 @@ export class RectangleRenderer extends Disposable { gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count); } - public onResize(): void { + public handleResize(): void { this._updateViewportRectangle(); } diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 0f00c100..59dd1e40 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -51,7 +51,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon { this.register(toDisposable(() => { const renderService: IRenderService = (this._terminal as any)._core._renderService; renderService.setRenderer((this._terminal as any)._core._createRenderer()); - renderService.onResize(terminal.cols, terminal.rows); + renderService.handleResize(terminal.cols, terminal.rows); })); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a554085e..bd906b22 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -162,16 +162,16 @@ export class WebglRenderer extends Disposable implements IRenderer { this._clearModel(true); } - public onDevicePixelRatioChange(): void { + public handleDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed if (this._devicePixelRatio !== this._coreBrowserService.dpr) { this._devicePixelRatio = this._coreBrowserService.dpr; - this.onResize(this._terminal.cols, this._terminal.rows); + this.handleResize(this._terminal.cols, this._terminal.rows); } } - public onResize(cols: number, rows: number): void { + public handleResize(cols: number, rows: number): void { // Update character and canvas dimensions this._updateDimensions(); @@ -193,9 +193,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`; this._rectangleRenderer.setDimensions(this.dimensions); - this._rectangleRenderer.onResize(); + this._rectangleRenderer.handleResize(); this._glyphRenderer.setDimensions(this.dimensions); - this._glyphRenderer.onResize(); + this._glyphRenderer.handleResize(); this._refreshCharAtlas(); @@ -204,43 +204,43 @@ export class WebglRenderer extends Disposable implements IRenderer { this._clearModel(false); } - public onCharSizeChanged(): void { - this.onResize(this._terminal.cols, this._terminal.rows); + public handleCharSizeChanged(): void { + this.handleResize(this._terminal.cols, this._terminal.rows); } - public onBlur(): void { + public handleBlur(): void { for (const l of this._renderLayers) { - l.onBlur(this._terminal); + l.handleBlur(this._terminal); } // Request a redraw for active/inactive selection background this._requestRedrawViewport(); } - public onFocus(): void { + public handleFocus(): void { for (const l of this._renderLayers) { - l.onFocus(this._terminal); + l.handleFocus(this._terminal); } // Request a redraw for active/inactive selection background this._requestRedrawViewport(); } - public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { for (const l of this._renderLayers) { - l.onSelectionChanged(this._terminal, start, end, columnSelectMode); + l.handleSelectionChanged(this._terminal, start, end, columnSelectMode); } this._model.selection.update(this._terminal, start, end, columnSelectMode); this._requestRedrawViewport(); } - public onCursorMove(): void { + public handleCursorMove(): void { for (const l of this._renderLayers) { - l.onCursorMove(this._terminal); + l.handleCursorMove(this._terminal); } } - public onOptionsChanged(): void { + public handleOptionsChanged(): void { for (const l of this._renderLayers) { - l.onOptionsChanged(this._terminal); + l.handleOptionsChanged(this._terminal); } this._updateDimensions(); this._refreshCharAtlas(); @@ -258,7 +258,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._gl, this.dimensions)); // Update dimensions and acquire char atlas - this.onCharSizeChanged(); + this.handleCharSizeChanged(); } /** @@ -328,7 +328,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update render layers for (const l of this._renderLayers) { - l.onGridChanged(this._terminal, start, end); + l.handleGridChanged(this._terminal, start, end); } // Tell renderer the frame is beginning diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 58e23fc2..50f47f6c 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -54,12 +54,12 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer } } - public onOptionsChanged(terminal: Terminal): void {} - public onBlur(terminal: Terminal): void {} - public onFocus(terminal: Terminal): void {} - public onCursorMove(terminal: Terminal): void {} - public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {} - public onSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {} + public handleOptionsChanged(terminal: Terminal): void {} + public handleBlur(terminal: Terminal): void {} + public handleFocus(terminal: Terminal): void {} + public handleCursorMove(terminal: Terminal): void {} + public handleGridChanged(terminal: Terminal, startRow: number, endRow: number): void {} + public handleSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {} public setColors(terminal: Terminal, colorSet: IColorSet): void { this._refreshCharAtlas(terminal, colorSet); @@ -81,7 +81,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer // Regenerate char atlas and force a full redraw this._refreshCharAtlas(terminal, this._colors); - this.onGridChanged(terminal, 0, terminal.rows - 1); + this.handleGridChanged(terminal, 0, terminal.rows - 1); } /** diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 3276a1d8..d685dd94 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -55,7 +55,7 @@ export class CursorRenderLayer extends BaseRenderLayer { 'block': this._renderBlockCursor.bind(this), 'underline': this._renderUnderlineCursor.bind(this) }; - this.onOptionsChanged(terminal); + this.handleOptionsChanged(terminal); this.register(toDisposable(() => { this._cursorBlinkStateManager?.dispose(); this._cursorBlinkStateManager = undefined; @@ -77,20 +77,20 @@ export class CursorRenderLayer extends BaseRenderLayer { public reset(terminal: Terminal): void { this._clearCursor(); this._cursorBlinkStateManager?.restartBlinkAnimation(terminal); - this.onOptionsChanged(terminal); + this.handleOptionsChanged(terminal); } - public onBlur(terminal: Terminal): void { + public handleBlur(terminal: Terminal): void { this._cursorBlinkStateManager?.pause(); this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY }); } - public onFocus(terminal: Terminal): void { + public handleFocus(terminal: Terminal): void { this._cursorBlinkStateManager?.resume(terminal); this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY }); } - public onOptionsChanged(terminal: Terminal): void { + public handleOptionsChanged(terminal: Terminal): void { if (terminal.options.cursorBlink) { if (!this._cursorBlinkStateManager) { this._cursorBlinkStateManager = new CursorBlinkStateManager(() => { @@ -106,11 +106,11 @@ export class CursorRenderLayer extends BaseRenderLayer { this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY }); } - public onCursorMove(terminal: Terminal): void { + public handleCursorMove(terminal: Terminal): void { this._cursorBlinkStateManager?.restartBlinkAnimation(terminal); } - public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void { + public handleGridChanged(terminal: Terminal, startRow: number, endRow: number): void { if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { this._render(terminal, false); } else { diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts index 993304a2..08561b44 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/Types.ts @@ -11,22 +11,22 @@ export interface IRenderLayer extends IDisposable { /** * Called when the terminal loses focus. */ - onBlur(terminal: Terminal): void; + handleBlur(terminal: Terminal): void; /** * * Called when the terminal gets focus. */ - onFocus(terminal: Terminal): void; + handleFocus(terminal: Terminal): void; /** * Called when the cursor is moved. */ - onCursorMove(terminal: Terminal): void; + handleCursorMove(terminal: Terminal): void; /** * Called when options change. */ - onOptionsChanged(terminal: Terminal): void; + handleOptionsChanged(terminal: Terminal): void; /** * Called when the theme changes. @@ -37,12 +37,12 @@ export interface IRenderLayer extends IDisposable { * Called when the data in the grid has changed (or needs to be rendered * again). */ - onGridChanged(terminal: Terminal, startRow: number, endRow: number): void; + handleGridChanged(terminal: Terminal, startRow: number, endRow: number): void; /** * Calls when the selection changes. */ - onSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + handleSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; /** * Registers a handler to join characters to render as a group diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 0eb726a6..de5c828c 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -102,7 +102,7 @@ export class ColorManager implements IColorManager { this._updateRestoreColors(); } - public onOptionsChange(key: string, value: any): void { + public handleOptionsChange(key: string, value: any): void { switch (key) { case 'minimumContrastRatio': this._contrastCache.clear(); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index fef92d3d..091f6df7 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -238,7 +238,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } } this._renderService?.setColors(this._colorManager.colors); - this.viewport?.onThemeChange(this._colorManager.colors); + this.viewport?.handleThemeChange(this._colorManager.colors); } protected _setup(): void { @@ -289,7 +289,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // When the font changes the size of the cells may change which requires a renderer clear if (this._renderService) { this._renderService.clear(); - this._renderService.onResize(this.cols, this.rows); + this._renderService.handleResize(this.cols, this.rows); this.refresh(0, this.rows - 1); } break; @@ -500,7 +500,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._theme = this.options.theme || this._theme; this._colorManager = new ColorManager(); - this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e, this.optionsService.rawOptions[e]))); + this.register(this.optionsService.onOptionChange(e => this._colorManager!.handleOptionsChange(e, this.optionsService.rawOptions[e]))); this._colorManager.setTheme(this._theme); this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService); @@ -533,17 +533,17 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportScrollArea, this.element ); - this.viewport.onThemeChange(this._colorManager.colors); + this.viewport.handleThemeChange(this._colorManager.colors); this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea())); this.register(this.viewport); this.register(this.onCursorMove(() => { - this._renderService!.onCursorMove(); + this._renderService!.handleCursorMove(); this._syncTextArea(); })); - this.register(this.onResize(() => this._renderService!.onResize(this.cols, this.rows))); - this.register(this.onBlur(() => this._renderService!.onBlur())); - this.register(this.onFocus(() => this._renderService!.onFocus())); + this.register(this.onResize(() => this._renderService!.handleResize(this.cols, this.rows))); + this.register(this.onBlur(() => this._renderService!.handleBlur())); + this.register(this.onFocus(() => this._renderService!.handleFocus())); this.register(this._renderService.onDimensionsChange(() => this.viewport!.syncScrollArea())); this._selectionService = this.register(this._instantiationService.createInstance(SelectionService, @@ -554,7 +554,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.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode))); + this.register(this._selectionService.onRequestRedraw(e => this._renderService!.handleSelectionChanged(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 // in order to register it as a selection on the OS. This event is fired @@ -571,7 +571,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement)); - this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); + this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e))); // apply mouse event classes set by escape codes before terminal was attached if (this.coreMouseService.areMouseEventsActive) { @@ -621,7 +621,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._theme = theme; this._colorManager?.setTheme(theme); this._renderService?.setColors(this._colorManager!.colors); - this.viewport?.onThemeChange(this._colorManager!.colors); + this.viewport?.handleThemeChange(this._colorManager!.colors); } /** @@ -860,20 +860,20 @@ export class Terminal extends CoreTerminal implements ITerminal { // normal viewport scrolling // conditionally stop event, if the viewport still had rows to scroll within - if (this.viewport!.onWheel(ev)) { + if (this.viewport!.handleWheel(ev)) { return this.cancel(ev); } }, { passive: false })); this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => { if (this.coreMouseService.areMouseEventsActive) return; - this.viewport!.onTouchStart(ev); + this.viewport!.handleTouchStart(ev); return this.cancel(ev); }, { passive: true })); this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => { if (this.coreMouseService.areMouseEventsActive) return; - if (!this.viewport!.onTouchMove(ev)) { + if (!this.viewport!.handleTouchMove(ev)) { return this.cancel(ev); } }, { passive: false })); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index d72f1446..282d77a7 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -285,14 +285,14 @@ export class MockRenderer implements IRenderer { public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { throw new Error('Method not implemented.'); } - public onResize(cols: number, rows: number): void { } - public onCharSizeChanged(): void { } - public onBlur(): void { } - public onFocus(): void { } - public onSelectionChanged(start: [number, number], end: [number, number]): void { } - public onCursorMove(): void { } - public onOptionsChanged(): void { } - public onDevicePixelRatioChange(): void { } + public handleResize(cols: number, rows: number): void { } + public handleCharSizeChanged(): void { } + public handleBlur(): void { } + public handleFocus(): void { } + public handleSelectionChanged(start: [number, number], end: [number, number]): void { } + public handleCursorMove(): void { } + public handleOptionsChanged(): void { } + public handleDevicePixelRatioChange(): void { } public clear(): void { } public renderRows(start: number, end: number): void { } } @@ -302,16 +302,16 @@ export class MockViewport implements IViewport { throw new Error('Method not implemented.'); } public scrollBarWidth: number = 0; - public onThemeChange(colors: IColorSet): void { + public handleThemeChange(colors: IColorSet): void { throw new Error('Method not implemented.'); } - public onWheel(ev: WheelEvent): boolean { + public handleWheel(ev: WheelEvent): boolean { throw new Error('Method not implemented.'); } - public onTouchStart(ev: TouchEvent): void { + public handleTouchStart(ev: TouchEvent): void { throw new Error('Method not implemented.'); } - public onTouchMove(ev: TouchEvent): boolean { + public handleTouchMove(ev: TouchEvent): boolean { throw new Error('Method not implemented.'); } public syncScrollArea(): void { } @@ -410,25 +410,25 @@ export class MockRenderService implements IRenderService { public setColors(colors: IColorSet): void { throw new Error('Method not implemented.'); } - public onDevicePixelRatioChange(): void { + public handleDevicePixelRatioChange(): void { throw new Error('Method not implemented.'); } - public onResize(cols: number, rows: number): void { + public handleResize(cols: number, rows: number): void { throw new Error('Method not implemented.'); } - public onCharSizeChanged(): void { + public handleCharSizeChanged(): void { throw new Error('Method not implemented.'); } - public onBlur(): void { + public handleBlur(): void { throw new Error('Method not implemented.'); } - public onFocus(): void { + public handleFocus(): void { throw new Error('Method not implemented.'); } - public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + public handleSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { throw new Error('Method not implemented.'); } - public onCursorMove(): void { + public handleCursorMove(): void { throw new Error('Method not implemented.'); } public clear(): void { @@ -498,7 +498,7 @@ export class MockSelectionService implements ISelectionService { public refresh(isLinuxMouseSelection?: boolean): void { throw new Error('Method not implemented.'); } - public onMouseDown(event: MouseEvent): void { + public handleMouseDown(event: MouseEvent): void { throw new Error('Method not implemented.'); } public isCellInSelection(x: number, y: number): boolean { diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 7e9053e1..5b475b52 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -108,7 +108,7 @@ export interface IBrowser { export interface IColorManager { colors: IColorSet; - onOptionsChange(key: string, value: any): void; + handleOptionsChange(key: string, value: any): void; } export interface IColorSet { @@ -147,10 +147,10 @@ export interface IViewport extends IDisposable { scrollBarWidth: number; syncScrollArea(immediate?: boolean): void; getLinesScrolled(ev: WheelEvent): number; - onWheel(ev: WheelEvent): boolean; - onTouchStart(ev: TouchEvent): void; - onTouchMove(ev: TouchEvent): boolean; - onThemeChange(colors: IColorSet): void; + handleWheel(ev: WheelEvent): boolean; + handleTouchStart(ev: TouchEvent): void; + handleTouchMove(ev: TouchEvent): boolean; + handleThemeChange(colors: IColorSet): void; } export interface ILinkifierEvent { diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 45373353..94503dde 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -77,7 +77,7 @@ export class Viewport extends Disposable implements IViewport { setTimeout(() => this.syncScrollArea(), 0); } - public onThemeChange(colors: IColorSet): void { + public handleThemeChange(colors: IColorSet): void { this._viewportElement.style.backgroundColor = colors.background.css; } @@ -234,7 +234,7 @@ export class Viewport extends Disposable implements IViewport { * `Viewport`. * @param ev The mouse wheel event. */ - public onWheel(ev: WheelEvent): boolean { + public handleWheel(ev: WheelEvent): boolean { const amount = this._getPixelsScrolled(ev); if (amount === 0) { return false; @@ -315,7 +315,7 @@ export class Viewport extends Disposable implements IViewport { * Handles the touchstart event, recording the touch occurred. * @param ev The touch event. */ - public onTouchStart(ev: TouchEvent): void { + public handleTouchStart(ev: TouchEvent): void { this._lastTouchY = ev.touches[0].pageY; } @@ -323,7 +323,7 @@ export class Viewport extends Disposable implements IViewport { * Handles the touchmove event, scrolling the viewport if the position shifted. * @param ev The touch event. */ - public onTouchMove(ev: TouchEvent): boolean { + public handleTouchMove(ev: TouchEvent): boolean { const deltaY = this._lastTouchY - ev.touches[0].pageY; this._lastTouchY = ev.touches[0].pageY; if (deltaY === 0) { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index b474bc7a..bdd3b0c6 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -241,7 +241,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._themeStyleElement.textContent = styles; } - public onDevicePixelRatioChange(): void { + public handleDevicePixelRatioChange(): void { this._updateDimensions(); } @@ -258,30 +258,30 @@ export class DomRenderer extends Disposable implements IRenderer { } } - public onResize(cols: number, rows: number): void { + public handleResize(cols: number, rows: number): void { this._refreshRowElements(cols, rows); this._updateDimensions(); } - public onCharSizeChanged(): void { + public handleCharSizeChanged(): void { this._updateDimensions(); } - public onBlur(): void { + public handleBlur(): void { this._rowContainer.classList.remove(FOCUS_CLASS); } - public onFocus(): void { + public handleFocus(): void { this._rowContainer.classList.add(FOCUS_CLASS); } - public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { // Remove all selections while (this._selectionContainer.children.length) { this._selectionContainer.removeChild(this._selectionContainer.children[0]); } - this._rowFactory.onSelectionChanged(start, end, columnSelectMode); + this._rowFactory.handleSelectionChanged(start, end, columnSelectMode); this.renderRows(0, this._bufferService.rows - 1); // Selection does not exist @@ -341,11 +341,11 @@ export class DomRenderer extends Disposable implements IRenderer { return element; } - public onCursorMove(): void { + public handleCursorMove(): void { // No-op, the cursor is drawn when rows are drawn } - public onOptionsChanged(): void { + public handleOptionsChanged(): void { // Force a refresh this._updateDimensions(); this._injectCss(); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 776ff701..b228f3d6 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -299,7 +299,7 @@ describe('DomRendererRowFactory', () => { it('should force selected cells with content to be rendered above the background', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - rowFactory.onSelectionChanged([1, 0], [2, 0], false); + rowFactory.handleSelectionChanged([1, 0], [2, 0], false); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), 'ab' @@ -307,7 +307,7 @@ describe('DomRendererRowFactory', () => { }); it('should force whitespace cells to be rendered above the background', () => { lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - rowFactory.onSelectionChanged([0, 0], [2, 0], false); + rowFactory.handleSelectionChanged([0, 0], [2, 0], false); const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); assert.equal(getFragmentHtml(fragment), ' a' diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index bce72ec3..6c9f1845 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -48,7 +48,7 @@ export class DomRendererRowFactory { this._colors = colors; } - public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { this._selectionStart = start; this._selectionEnd = end; this._columnSelectMode = columnSelectMode; diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 7e2badcb..5704f312 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -62,14 +62,14 @@ export interface IRenderer extends IDisposable { 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; + handleDevicePixelRatioChange(): void; + handleResize(cols: number, rows: number): void; + handleCharSizeChanged(): void; + handleBlur(): void; + handleFocus(): void; + handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + handleCursorMove(): void; + handleOptionsChanged(): void; clear(): void; renderRows(start: number, end: number): void; clearTextureAtlas?(): void; diff --git a/src/browser/selection/SelectionModel.test.ts b/src/browser/selection/SelectionModel.test.ts index 5ce3e316..79ff372b 100644 --- a/src/browser/selection/SelectionModel.test.ts +++ b/src/browser/selection/SelectionModel.test.ts @@ -50,17 +50,17 @@ describe('SelectionModel', () => { it('should trim a portion of the selection when a part of it is trimmed', () => { model.selectionStart = [0, 0]; model.selectionEnd = [10, 2]; - model.onTrim(1); + model.handleTrim(1); assert.deepEqual(model.finalSelectionStart, [0, 0]); assert.deepEqual(model.finalSelectionEnd, [10, 1]); - model.onTrim(1); + model.handleTrim(1); assert.deepEqual(model.finalSelectionStart, [0, 0]); assert.deepEqual(model.finalSelectionEnd, [10, 0]); }); it('should clear selection when it is trimmed in its entirety', () => { model.selectionStart = [0, 0]; model.selectionEnd = [10, 0]; - model.onTrim(1); + model.handleTrim(1); assert.deepEqual(model.finalSelectionStart, undefined); assert.deepEqual(model.finalSelectionEnd, undefined); }); diff --git a/src/browser/selection/SelectionModel.ts b/src/browser/selection/SelectionModel.ts index 6c8abbfd..041c7b24 100644 --- a/src/browser/selection/SelectionModel.ts +++ b/src/browser/selection/SelectionModel.ts @@ -120,7 +120,7 @@ export class SelectionModel { * @param amount The amount the buffer is being trimmed. * @return Whether a refresh is necessary. */ - public onTrim(amount: number): boolean { + public handleTrim(amount: number): boolean { // Adjust the selection position based on the trimmed amount. if (this.selectionStart) { this.selectionStart[1] -= amount; diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index a063b37f..b58c372d 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -68,13 +68,13 @@ export class RenderService extends Disposable implements IRenderService { this.register(this._renderDebouncer); this._screenDprMonitor = new ScreenDprMonitor(coreBrowserService.window); - this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange()); + this._screenDprMonitor.setListener(() => this.handleDevicePixelRatioChange()); this.register(this._screenDprMonitor); this.register(bufferService.onResize(() => this._fullRefresh())); this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear())); this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); - this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged())); + this.register(this._charSizeService.onCharSizeChange(() => this.handleCharSizeChanged())); // Do a full refresh whenever any decoration is added or removed. This may not actually result // in changes but since decorations should be used sparingly or added/removed all in the same @@ -87,7 +87,7 @@ export class RenderService extends Disposable implements IRenderService { // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. - this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.onDevicePixelRatioChange())); + this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange())); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so @@ -132,7 +132,7 @@ export class RenderService extends Disposable implements IRenderService { // Update selection if needed if (this._needsSelectionRefresh) { - this._renderer.onSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); + this._renderer.handleSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); this._needsSelectionRefresh = false; } @@ -153,7 +153,7 @@ export class RenderService extends Disposable implements IRenderService { if (!this._renderer) { return; } - this._renderer.onOptionsChanged(); + this._renderer.handleOptionsChanged(); this.refreshRows(0, this._rowCount - 1); this._fireOnCanvasResize(); } @@ -212,7 +212,7 @@ export class RenderService extends Disposable implements IRenderService { this._fullRefresh(); } - public onDevicePixelRatioChange(): void { + public handleDevicePixelRatioChange(): void { // Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable // when devicePixelRatio changes this._charSizeService.measure(); @@ -220,44 +220,44 @@ export class RenderService extends Disposable implements IRenderService { if (!this._renderer) { return; } - this._renderer.onDevicePixelRatioChange(); + this._renderer.handleDevicePixelRatioChange(); this.refreshRows(0, this._rowCount - 1); } - public onResize(cols: number, rows: number): void { + public handleResize(cols: number, rows: number): void { if (!this._renderer) { return; } if (this._isPaused) { - this._pausedResizeTask.set(() => this._renderer!.onResize(cols, rows)); + this._pausedResizeTask.set(() => this._renderer!.handleResize(cols, rows)); } else { - this._renderer.onResize(cols, rows); + this._renderer.handleResize(cols, rows); } this._fullRefresh(); } // TODO: Is this useful when we have onResize? - public onCharSizeChanged(): void { - this._renderer?.onCharSizeChanged(); + public handleCharSizeChanged(): void { + this._renderer?.handleCharSizeChanged(); } - public onBlur(): void { - this._renderer?.onBlur(); + public handleBlur(): void { + this._renderer?.handleBlur(); } - public onFocus(): void { - this._renderer?.onFocus(); + public handleFocus(): void { + this._renderer?.handleFocus(); } - public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { this._selectionState.start = start; this._selectionState.end = end; this._selectionState.columnSelectMode = columnSelectMode; - this._renderer?.onSelectionChanged(start, end, columnSelectMode); + this._renderer?.handleSelectionChanged(start, end, columnSelectMode); } - public onCursorMove(): void { - this._renderer?.onCursorMove(); + public handleCursorMove(): void { + this._renderer?.handleCursorMove(); } public clear(): void { diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 130537d1..1dbc8333 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -376,7 +376,7 @@ export class SelectionService extends Disposable implements ISelectionService { * @param amount The amount the buffer is being trimmed. */ private _onTrim(amount: number): void { - const needsRefresh = this._model.onTrim(amount); + const needsRefresh = this._model.handleTrim(amount); if (needsRefresh) { this.refresh(); } @@ -438,7 +438,7 @@ export class SelectionService extends Disposable implements ISelectionService { * Handles te mousedown event, setting up for a new selection. * @param event The mousedown event. */ - public onMouseDown(event: MouseEvent): void { + public handleMouseDown(event: MouseEvent): void { this._mouseDownTimeStamp = event.timeStamp; // If we have selection, we want the context menu on right click even if the // terminal is in mouse mode. diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 4343c450..bbc439e8 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -74,14 +74,13 @@ export interface IRenderService extends IDisposable { hasRenderer(): boolean; setRenderer(renderer: IRenderer): void; setColors(colors: IColorSet): void; - onDevicePixelRatioChange(): void; - onResize(cols: number, rows: number): void; - // TODO: Is this useful when we have onResize? - onCharSizeChanged(): void; - onBlur(): void; - onFocus(): void; - onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; - onCursorMove(): void; + handleDevicePixelRatioChange(): void; + handleResize(cols: number, rows: number): void; + handleCharSizeChanged(): void; + handleBlur(): void; + handleFocus(): void; + handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + handleCursorMove(): void; clear(): void; } @@ -110,7 +109,7 @@ export interface ISelectionService { shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean; shouldForceSelection(event: MouseEvent): boolean; refresh(isLinuxMouseSelection?: boolean): void; - onMouseDown(event: MouseEvent): void; + handleMouseDown(event: MouseEvent): void; isCellInSelection(x: number, y: number): boolean; } From 5d8e4a0af4464dbf60a907880b52551c9e7aacac Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 11:05:52 -0700 Subject: [PATCH 62/87] Add lint rule to ensure _on prefix is only for event emitters --- .eslintrc.json | 8 +++++ .../xterm-addon-canvas/src/LinkRenderLayer.ts | 8 ++--- .../src/renderLayer/LinkRenderLayer.ts | 8 ++--- src/browser/AccessibilityManager.ts | 28 +++++++-------- src/browser/Linkifier2.ts | 8 ++--- src/browser/Terminal.ts | 8 ++--- src/browser/Viewport.ts | 4 +-- src/browser/renderer/dom/DomRenderer.ts | 8 ++--- src/browser/services/RenderService.ts | 4 +-- src/browser/services/SelectionService.ts | 34 +++++++++---------- 10 files changed, 63 insertions(+), 55 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5849fa51..e6db42e2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -101,6 +101,14 @@ "regex": "^on[A-Z].+", "match": false } }, + { "selector": "method", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require", "custom": { + "regex": "^on[A-Z].+", + "match": false + } }, + { "selector": "method", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require", "custom": { + "regex": "^on[A-Z].+", + "match": false + } }, // typeLike { "selector": "typeLike", "format": ["PascalCase"] }, { "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] } diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index da9ebe16..b1403f7a 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -28,8 +28,8 @@ export class LinkRenderLayer extends BaseRenderLayer { ) { super(terminal, container, 'link', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); - this.register(linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e))); - this.register(linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e))); + this.register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); + this.register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); } public resize(dim: IRenderDimensions): void { @@ -54,7 +54,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } } - private _onShowLinkUnderline(e: ILinkifierEvent): void { + private _handleShowLinkUnderline(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; } else if (e.fg && is256Color(e.fg)) { @@ -78,7 +78,7 @@ export class LinkRenderLayer extends BaseRenderLayer { this._state = e; } - private _onHideLinkUnderline(e: ILinkifierEvent): void { + private _handleHideLinkUnderline(e: ILinkifierEvent): void { this._clearCurrentLink(); } } diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 15246348..2ce90daf 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -24,8 +24,8 @@ export class LinkRenderLayer extends BaseRenderLayer { ) { super(container, 'link', zIndex, true, colors, coreBrowserService); - this.register(terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e))); - this.register(terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e))); + this.register(terminal.linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); + this.register(terminal.linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); } public resize(terminal: Terminal, dim: IRenderDimensions): void { @@ -50,7 +50,7 @@ export class LinkRenderLayer extends BaseRenderLayer { } } - private _onShowLinkUnderline(e: ILinkifierEvent): void { + private _handleShowLinkUnderline(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; } else if (e.fg !== undefined && is256Color(e.fg)) { @@ -74,7 +74,7 @@ export class LinkRenderLayer extends BaseRenderLayer { this._state = e; } - private _onHideLinkUnderline(e: ILinkifierEvent): void { + private _handleHideLinkUnderline(e: ILinkifierEvent): void { this._clearCurrentLink(); } } diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index d1df4075..d0c9f601 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -65,8 +65,8 @@ export class AccessibilityManager extends Disposable { this._rowContainer.appendChild(this._rowElements[i]); } - this._topBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.TOP); - this._bottomBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.BOTTOM); + this._topBoundaryFocusListener = e => this._handleBoundaryFocus(e, BoundaryPosition.TOP); + this._bottomBoundaryFocusListener = e => this._handleBoundaryFocus(e, BoundaryPosition.BOTTOM); this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); @@ -87,14 +87,14 @@ export class AccessibilityManager extends Disposable { this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot); this.register(this._renderRowsDebouncer); - this.register(this._terminal.onResize(e => this._onResize(e.rows))); + this.register(this._terminal.onResize(e => this._handleResize(e.rows))); this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end))); this.register(this._terminal.onScroll(() => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run - this.register(this._terminal.onA11yChar(char => this._onChar(char))); - this.register(this._terminal.onLineFeed(() => this._onChar('\n'))); - this.register(this._terminal.onA11yTab(spaceCount => this._onTab(spaceCount))); - this.register(this._terminal.onKey(e => this._onKey(e.key))); + this.register(this._terminal.onA11yChar(char => this._handleChar(char))); + this.register(this._terminal.onLineFeed(() => this._handleChar('\n'))); + this.register(this._terminal.onA11yTab(spaceCount => this._handleTab(spaceCount))); + this.register(this._terminal.onKey(e => this._handleKey(e.key))); this.register(this._terminal.onBlur(() => this._clearLiveRegion())); this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions())); @@ -110,7 +110,7 @@ export class AccessibilityManager extends Disposable { })); } - private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { + private _handleBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { const boundaryElement = e.target as HTMLElement; const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2]; @@ -170,7 +170,7 @@ export class AccessibilityManager extends Disposable { e.stopImmediatePropagation(); } - private _onResize(rows: number): void { + private _handleResize(rows: number): void { // Remove bottom boundary listener this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener); @@ -198,13 +198,13 @@ export class AccessibilityManager extends Disposable { return element; } - private _onTab(spaceCount: number): void { + private _handleTab(spaceCount: number): void { for (let i = 0; i < spaceCount; i++) { - this._onChar(' '); + this._handleChar(' '); } } - private _onChar(char: string): void { + private _handleChar(char: string): void { if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { if (this._charsToConsume.length > 0) { // Have the screen reader ignore the char if it was just input @@ -244,7 +244,7 @@ export class AccessibilityManager extends Disposable { } } - private _onKey(keyChar: string): void { + private _handleKey(keyChar: string): void { this._clearLiveRegion(); this._charsToConsume.push(keyChar); } @@ -278,7 +278,7 @@ export class AccessibilityManager extends Disposable { return; } if (this._rowElements.length !== this._terminal.rows) { - this._onResize(this._terminal.rows); + this._handleResize(this._terminal.rows); } for (let i = 0; i < this._terminal.rows; i++) { this._refreshRowDimensions(this._rowElements[i]); diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index c0fc2641..236efb22 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -64,12 +64,12 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { this._isMouseOut = true; this._clearCurrentLink(); })); - this.register(addDisposableDomListener(this._element, 'mousemove', this._onMouseMove.bind(this))); + this.register(addDisposableDomListener(this._element, 'mousemove', this._handleMouseMove.bind(this))); this.register(addDisposableDomListener(this._element, 'mousedown', this._handleMouseDown.bind(this))); this.register(addDisposableDomListener(this._element, 'mouseup', this._handleMouseUp.bind(this))); } - private _onMouseMove(event: MouseEvent): void { + private _handleMouseMove(event: MouseEvent): void { this._lastMouseEvent = event; if (!this._element || !this._mouseService) { @@ -97,12 +97,12 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { } if (!this._lastBufferCell || (position.x !== this._lastBufferCell.x || position.y !== this._lastBufferCell.y)) { - this._onHover(position); + this._handleHover(position); this._lastBufferCell = position; } } - private _onHover(position: IBufferCellPosition): void { + private _handleHover(position: IBufferCellPosition): void { // TODO: This currently does not cache link provider results across wrapped lines, activeLine should be something like `activeRange: {startY, endY}` // Check if we need to clear the link if (this._activeLine !== position.y) { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 091f6df7..b3a5b317 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -316,7 +316,7 @@ export class Terminal extends CoreTerminal implements ITerminal { /** * Binds the desired focus behavior on a given terminal object. */ - private _onTextAreaFocus(ev: KeyboardEvent): void { + private _handleTextAreaFocus(ev: KeyboardEvent): void { if (this.coreService.decPrivateModes.sendFocus) { this.coreService.triggerDataEvent(C0.ESC + '[I'); } @@ -337,7 +337,7 @@ export class Terminal extends CoreTerminal implements ITerminal { /** * Binds the desired blur behavior on a given terminal object. */ - private _onTextAreaBlur(): void { + private _handleTextAreaBlur(): void { // Text can safely be removed on blur. Doing it earlier could interfere with // screen readers reading it out. this.textarea!.value = ''; @@ -488,8 +488,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this.textarea.setAttribute('autocapitalize', 'off'); this.textarea.setAttribute('spellcheck', 'false'); this.textarea.tabIndex = 0; - this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._onTextAreaFocus(ev))); - this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur())); + this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._handleTextAreaFocus(ev))); + this.register(addDisposableDomListener(this.textarea, 'blur', () => this._handleTextAreaBlur())); this._helperContainer.appendChild(this.textarea); this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea, this._document.defaultView ?? window); diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 94503dde..395052e6 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -65,7 +65,7 @@ export class Viewport extends Disposable implements IViewport { // Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case, // therefore we account for a standard amount to make it visible this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; - this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this))); + this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._handleScroll.bind(this))); // Track properties used in performance critical code manually to avoid using slow getters this._activeBuffer = this._bufferService.buffer; @@ -157,7 +157,7 @@ export class Viewport extends Disposable implements IViewport { * terminal to scroll to it. * @param ev The scroll event. */ - private _onScroll(ev: Event): void { + private _handleScroll(ev: Event): void { // Record current scroll top position this._lastScrollTop = this._viewportElement.scrollTop; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index bdd3b0c6..a961bbe0 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -87,8 +87,8 @@ export class DomRenderer extends Disposable implements IRenderer { this._screenElement.appendChild(this._rowContainer); this._screenElement.appendChild(this._selectionContainer); - this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e))); - this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e))); + this.register(this._linkifier2.onShowLinkUnderline(e => this._handleLinkHover(e))); + this.register(this._linkifier2.onHideLinkUnderline(e => this._handleLinkLeave(e))); this.register(toDisposable(() => { this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass); @@ -376,11 +376,11 @@ export class DomRenderer extends Disposable implements IRenderer { return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`; } - private _onLinkHover(e: ILinkifierEvent): void { + private _handleLinkHover(e: ILinkifierEvent): void { this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true); } - private _onLinkLeave(e: ILinkifierEvent): void { + private _handleLinkLeave(e: ILinkifierEvent): void { this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false); } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index b58c372d..a51fe722 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -92,13 +92,13 @@ export class RenderService extends Disposable implements IRenderService { // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so if ('IntersectionObserver' in coreBrowserService.window) { - const observer = new coreBrowserService.window.IntersectionObserver(e => this._onIntersectionChange(e[e.length - 1]), { threshold: 0 }); + const observer = new coreBrowserService.window.IntersectionObserver(e => this._handleIntersectionChange(e[e.length - 1]), { threshold: 0 }); observer.observe(screenElement); this.register({ dispose: () => observer.disconnect() }); } } - private _onIntersectionChange(entry: IntersectionObserverEntry): void { + private _handleIntersectionChange(entry: IntersectionObserverEntry): void { this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting; // Terminal was hidden on open diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 1dbc8333..868fadda 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -134,15 +134,15 @@ export class SelectionService extends Disposable implements ISelectionService { super(); // Init listeners - this._mouseMoveListener = event => this._onMouseMove(event as MouseEvent); - this._mouseUpListener = event => this._onMouseUp(event as MouseEvent); + this._mouseMoveListener = event => this._handleMouseMove(event as MouseEvent); + this._mouseUpListener = event => this._handleMouseUp(event as MouseEvent); this._coreService.onUserInput(() => { if (this.hasSelection) { this.clearSelection(); } }); - this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._onTrim(amount)); - this.register(this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e))); + this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._handleTrim(amount)); + this.register(this._bufferService.buffers.onBufferActivate(e => this._handleBufferActivate(e))); this.enable(); @@ -375,7 +375,7 @@ export class SelectionService extends Disposable implements ISelectionService { * Handle the buffer being trimmed, adjust the selection position. * @param amount The amount the buffer is being trimmed. */ - private _onTrim(amount: number): void { + private _handleTrim(amount: number): void { const needsRefresh = this._model.handleTrim(amount); if (needsRefresh) { this.refresh(); @@ -468,14 +468,14 @@ export class SelectionService extends Disposable implements ISelectionService { this._dragScrollAmount = 0; if (this._enabled && event.shiftKey) { - this._onIncrementalClick(event); + this._handleIncrementalClick(event); } else { if (event.detail === 1) { - this._onSingleClick(event); + this._handleSingleClick(event); } else if (event.detail === 2) { - this._onDoubleClick(event); + this._handleDoubleClick(event); } else if (event.detail === 3) { - this._onTripleClick(event); + this._handleTripleClick(event); } } @@ -512,7 +512,7 @@ export class SelectionService extends Disposable implements ISelectionService { * position. * @param event The mouse event. */ - private _onIncrementalClick(event: MouseEvent): void { + private _handleIncrementalClick(event: MouseEvent): void { if (this._model.selectionStart) { this._model.selectionEnd = this._getMouseBufferCoords(event); } @@ -523,7 +523,7 @@ export class SelectionService extends Disposable implements ISelectionService { * start position. * @param event The mouse event. */ - private _onSingleClick(event: MouseEvent): void { + private _handleSingleClick(event: MouseEvent): void { this._model.selectionStartLength = 0; this._model.isSelectAllActive = false; this._activeSelectionMode = this.shouldColumnSelect(event) ? SelectionMode.COLUMN : SelectionMode.NORMAL; @@ -557,7 +557,7 @@ export class SelectionService extends Disposable implements ISelectionService { * Performs a double click, selecting the current word. * @param event The mouse event. */ - private _onDoubleClick(event: MouseEvent): void { + private _handleDoubleClick(event: MouseEvent): void { if (this._selectWordAtCursor(event, true)) { this._activeSelectionMode = SelectionMode.WORD; } @@ -568,7 +568,7 @@ export class SelectionService extends Disposable implements ISelectionService { * select mode. * @param event The mouse event. */ - private _onTripleClick(event: MouseEvent): void { + private _handleTripleClick(event: MouseEvent): void { const coords = this._getMouseBufferCoords(event); if (coords) { this._activeSelectionMode = SelectionMode.LINE; @@ -589,7 +589,7 @@ export class SelectionService extends Disposable implements ISelectionService { * end of the selection and refreshing the selection. * @param event The mousemove event. */ - private _onMouseMove(event: MouseEvent): void { + private _handleMouseMove(event: MouseEvent): void { // If the mousemove listener is active it means that a selection is // currently being made, we should stop propagation to prevent mouse events // to be sent to the pty. @@ -690,7 +690,7 @@ export class SelectionService extends Disposable implements ISelectionService { * Handles the mouseup event, removing the mousedown listeners. * @param event The mouseup event. */ - private _onMouseUp(event: MouseEvent): void { + private _handleMouseUp(event: MouseEvent): void { const timeElapsed = event.timeStamp - this._mouseDownTimeStamp; this._removeMouseDownListeners(); @@ -746,14 +746,14 @@ export class SelectionService extends Disposable implements ISelectionService { this._onSelectionChange.fire(); } - private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void { + private _handleBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void { this.clearSelection(); // Only adjust the selection on trim, shiftElements is rarely used (only in // reverseIndex) and delete in a splice is only ever used when the same // number of elements was just added. Given this is could actually be // beneficial to leave the selection as is for these cases. this._trimListener.dispose(); - this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount)); + this._trimListener = e.activeBuffer.lines.onTrim(amount => this._handleTrim(amount)); } /** From c4750eaf779d3d2afbb949ab1548949b47196cf8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 11:42:55 -0700 Subject: [PATCH 63/87] Create basic theme service Part of #4184 --- .../src/RectangleRenderer.ts | 4 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 7 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 +- .../src/renderLayer/BaseRenderLayer.ts | 8 ++-- .../src/renderLayer/CursorRenderLayer.ts | 4 +- .../src/renderLayer/LinkRenderLayer.ts | 4 +- .../src/renderLayer/Types.ts | 4 +- src/browser/Terminal.ts | 48 ++++++++++--------- src/browser/Types.d.ts | 4 +- src/browser/renderer/dom/DomRenderer.ts | 4 +- .../renderer/dom/DomRendererRowFactory.ts | 4 +- .../renderer/shared/CellColorResolver.ts | 4 +- src/browser/renderer/shared/CharAtlasCache.ts | 4 +- src/browser/renderer/shared/CharAtlasUtils.ts | 4 +- src/browser/services/Services.ts | 21 ++++++-- src/browser/services/ThemeService.ts | 46 ++++++++++++++++++ 16 files changed, 120 insertions(+), 54 deletions(-) create mode 100644 src/browser/services/ThemeService.ts diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ff935d1c..62a61101 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -8,7 +8,7 @@ import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext } from ' import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColor } from 'common/Types'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/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'; @@ -84,7 +84,7 @@ export class RectangleRenderer extends Disposable { constructor( private _terminal: Terminal, - private _colors: IColorSet, + private _colors: ReadonlyColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions ) { diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 59dd1e40..f0e0a0e9 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -5,7 +5,7 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; -import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; +import { ICharacterJoinerService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { isSafari } from 'common/Platform'; @@ -42,8 +42,9 @@ export class WebglAddon extends Disposable implements ITerminalAddon { const coreBrowserService: ICoreBrowserService = core._coreBrowserService; const coreService: ICoreService = core.coreService; const decorationService: IDecorationService = core._decorationService; - const colors: IColorSet = core._colorManager.colors; - this._renderer = this.register(new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer)); + const themeService: IThemeService = core._themeService; + // TODO: Pass in theme service + this._renderer = this.register(new WebglRenderer(terminal, themeService.colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer)); this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); this.register(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 bd906b22..072e11a9 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -9,7 +9,7 @@ import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/s 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 { IColorSet, ITerminal } from 'browser/Types'; +import { IColorSet, ITerminal, ReadonlyColorSet } 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'; @@ -55,7 +55,7 @@ export class WebglRenderer extends Disposable implements IRenderer { constructor( private _terminal: Terminal, - private _colors: IColorSet, + private _colors: ReadonlyColorSet, private readonly _characterJoinerService: ICharacterJoinerService, private readonly _coreBrowserService: ICoreBrowserService, coreService: ICoreService, diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 50f47f6c..d4a0a86c 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -6,7 +6,7 @@ import { IRenderLayer } from './Types'; import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { Terminal } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; @@ -31,7 +31,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer id: string, zIndex: number, private _alpha: boolean, - protected _colors: IColorSet, + protected _colors: ReadonlyColorSet, protected readonly _coreBrowserService: ICoreBrowserService ) { super(); @@ -61,7 +61,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer public handleGridChanged(terminal: Terminal, startRow: number, endRow: number): void {} public handleSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {} - public setColors(terminal: Terminal, colorSet: IColorSet): void { + public setColors(terminal: Terminal, colorSet: ReadonlyColorSet): void { this._refreshCharAtlas(terminal, colorSet); } @@ -89,7 +89,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * @param terminal The terminal. * @param colorSet The color set to use for the char atlas. */ - private _refreshCharAtlas(terminal: Terminal, colorSet: IColorSet): void { + private _refreshCharAtlas(terminal: Terminal, colorSet: ReadonlyColorSet): void { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index d685dd94..b1718d75 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -7,7 +7,7 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; @@ -37,7 +37,7 @@ export class CursorRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: IColorSet, + colors: ReadonlyColorSet, private _onRequestRefreshRowsEvent: IEventEmitter, coreBrowserService: ICoreBrowserService, private readonly _coreService: ICoreService diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 2ce90daf..9c841843 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -6,7 +6,7 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; -import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; +import { ITerminal, IColorSet, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; @@ -18,7 +18,7 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor( container: HTMLElement, zIndex: number, - colors: IColorSet, + colors: ReadonlyColorSet, terminal: ITerminal, coreBrowserService: ICoreBrowserService ) { diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts index 08561b44..32013ee3 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/Types.ts @@ -4,7 +4,7 @@ */ import { IDisposable, Terminal } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; export interface IRenderLayer extends IDisposable { @@ -31,7 +31,7 @@ export interface IRenderLayer extends IDisposable { /** * Called when the theme changes. */ - setColors(terminal: Terminal, colorSet: IColorSet): void; + setColors(terminal: Terminal, colorSet: ReadonlyColorSet): void; /** * Called when the data in the grid has changed (or needs to be rendered diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index b3a5b317..c641a544 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -39,9 +39,8 @@ import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseActio import { evaluateKeyboardEvent } from 'common/input/Keyboard'; 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'; -import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ICoreBrowserService, ICharacterJoinerService, IThemeService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; @@ -57,6 +56,7 @@ import { DecorationService } from 'common/services/DecorationService'; import { IDecorationService } from 'common/services/Services'; import { OscLinkProvider } from 'browser/OscLinkProvider'; import { toDisposable } from 'common/Lifecycle'; +import { ThemeService } from 'browser/services/ThemeService'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -86,6 +86,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _coreBrowserService: ICoreBrowserService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; + private _themeService: IThemeService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; @@ -120,8 +121,6 @@ export class Terminal extends CoreTerminal implements ITerminal { public viewport: IViewport | undefined; private _compositionHelper: ICompositionHelper | undefined; private _accessibilityManager: AccessibilityManager | undefined; - private _colorManager: ColorManager | undefined; - private _theme: ITheme | undefined; private readonly _onCursorMove = this.register(new EventEmitter()); public readonly onCursorMove = this._onCursorMove.event; @@ -199,9 +198,9 @@ export class Terminal extends CoreTerminal implements ITerminal { * while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request. */ private _handleColorEvent(event: IColorEvent): void { - if (!this._colorManager) return; + if (!this._themeService) return; for (const req of event) { - let acc: 'foreground' | 'background' | 'cursor' | 'ansi' | undefined = undefined; + let acc: 'foreground' | 'background' | 'cursor' | 'ansi'; let ident = ''; switch (req.index) { case ColorIndex.FOREGROUND: // OSC 10 | 110 @@ -224,21 +223,26 @@ export class Terminal extends CoreTerminal implements ITerminal { switch (req.type) { case ColorRequestType.REPORT: const channels = color.toColorRGB(acc === 'ansi' - ? this._colorManager.colors.ansi[req.index] - : this._colorManager.colors[acc]); + ? this._themeService.colors.ansi[req.index] + : this._themeService.colors[acc]); this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1_ESCAPED.ST}`); break; case ColorRequestType.SET: - if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); - else this._colorManager.colors[acc] = rgba.toColor(...req.color); + if (acc === 'ansi') { + this._themeService.modifyColors(colors => colors.ansi[req.index] = rgba.toColor(...req.color)); + } else { + const narrowedAcc = acc; + this._themeService.modifyColors(colors => colors[narrowedAcc] = rgba.toColor(...req.color)); + } break; case ColorRequestType.RESTORE: - this._colorManager.restoreColor(req.index); + this._themeService.restoreColor(req.index); break; } } - this._renderService?.setColors(this._colorManager.colors); - this.viewport?.handleThemeChange(this._colorManager.colors); + // TODO: Have these listen to theme change + this._renderService?.setColors(this._themeService.colors); + this.viewport?.handleThemeChange(this._themeService.colors); } protected _setup(): void { @@ -498,10 +502,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer); this._instantiationService.setService(ICharSizeService, this._charSizeService); - this._theme = this.options.theme || this._theme; - this._colorManager = new ColorManager(); - this.register(this.optionsService.onOptionChange(e => this._colorManager!.handleOptionsChange(e, this.optionsService.rawOptions[e]))); - this._colorManager.setTheme(this._theme); + this._themeService = this._instantiationService.createInstance(ThemeService); + this._instantiationService.setService(IThemeService, this._themeService); this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService); this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService); @@ -533,7 +535,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportScrollArea, this.element ); - this.viewport.handleThemeChange(this._colorManager.colors); + // TODO: Listen to theme service event + this.viewport.handleThemeChange(this._themeService.colors); this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea())); this.register(this.viewport); @@ -610,7 +613,8 @@ export class Terminal extends CoreTerminal implements ITerminal { } private _createRenderer(): IRenderer { - return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); + // TODO: Listen to theme service + return this._instantiationService.createInstance(DomRenderer, this._themeService!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); } /** @@ -618,10 +622,8 @@ export class Terminal extends CoreTerminal implements ITerminal { * @param theme The theme to set. */ private _setTheme(theme: ITheme): void { - this._theme = theme; - this._colorManager?.setTheme(theme); - this._renderService?.setColors(this._colorManager!.colors); - this.viewport?.handleThemeChange(this._colorManager!.colors); + // TODO: Listen to theme service events + this.viewport?.handleThemeChange(this._themeService!.colors); } /** diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 5b475b52..830cbac1 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -126,6 +126,8 @@ export interface IColorSet { contrastCache: IColorContrastCache; } +export type ReadonlyColorSet = Readonly> & { ansi: Readonly['ansi']> }; + export interface IColorContrastCache { clear(): void; setCss(bg: number, fg: number, value: string | null): void; @@ -150,7 +152,7 @@ export interface IViewport extends IDisposable { handleWheel(ev: WheelEvent): boolean; handleTouchStart(ev: TouchEvent): void; handleTouchMove(ev: TouchEvent): boolean; - handleThemeChange(colors: IColorSet): void; + handleThemeChange(colors: ReadonlyColorSet): void; } export interface ILinkifierEvent { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index a961bbe0..d9d0268b 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -7,7 +7,7 @@ import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/rende 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/shared/Constants'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; +import { IColorSet, ILinkifierEvent, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; @@ -43,7 +43,7 @@ export class DomRenderer extends Disposable implements IRenderer { public readonly onRequestRedraw = this.register(new EventEmitter()).event; constructor( - private _colors: IColorSet, + private _colors: ReadonlyColorSet, private readonly _element: HTMLElement, private readonly _screenElement: HTMLElement, private readonly _viewportElement: HTMLElement, diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 6c9f1845..56b19601 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -9,7 +9,7 @@ import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/ import { CellData } from 'common/buffer/CellData'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { color, rgba } from 'common/Color'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils'; @@ -35,7 +35,7 @@ export class DomRendererRowFactory { constructor( private readonly _document: Document, - private _colors: IColorSet, + private _colors: ReadonlyColorSet, @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, @IOptionsService private readonly _optionsService: IOptionsService, @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index b29475b9..eeb0edb6 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -1,6 +1,6 @@ import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; import { IDecorationService } from 'common/services/Services'; import { ICellData } from 'common/Types'; @@ -26,7 +26,7 @@ export class CellColorResolver { constructor( private readonly _terminal: Terminal, - private _colors: IColorSet, + private _colors: ReadonlyColorSet, private readonly _selectionRenderModel: ISelectionRenderModel, private readonly _decorationService: IDecorationService, private readonly _coreBrowserService: ICoreBrowserService diff --git a/src/browser/renderer/shared/CharAtlasCache.ts b/src/browser/renderer/shared/CharAtlasCache.ts index ee88c708..e953dc46 100644 --- a/src/browser/renderer/shared/CharAtlasCache.ts +++ b/src/browser/renderer/shared/CharAtlasCache.ts @@ -5,7 +5,7 @@ import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; import { Terminal } from 'xterm'; -import { IColorSet, ITerminal } from 'browser/Types'; +import { ITerminal, ReadonlyColorSet } from 'browser/Types'; import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; @@ -27,7 +27,7 @@ const charAtlasCache: ITextureAtlasCacheEntry[] = []; */ export function acquireTextureAtlas( terminal: Terminal, - colors: IColorSet, + colors: ReadonlyColorSet, scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, diff --git a/src/browser/renderer/shared/CharAtlasUtils.ts b/src/browser/renderer/shared/CharAtlasUtils.ts index 58c207e8..e443168e 100644 --- a/src/browser/renderer/shared/CharAtlasUtils.ts +++ b/src/browser/renderer/shared/CharAtlasUtils.ts @@ -6,10 +6,10 @@ import { ICharAtlasConfig } from './Types'; import { Attributes } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { NULL_COLOR } from 'common/Color'; -export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet, devicePixelRatio: number): ICharAtlasConfig { +export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: ReadonlyColorSet, devicePixelRatio: number): ICharAtlasConfig { // null out some fields that don't matter const clonedColors: IColorSet = { foreground: colors.foreground, diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index bbc439e8..6132c3ea 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -5,10 +5,11 @@ import { IEvent } from 'common/EventEmitter'; import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; -import { IDisposable } from 'common/Types'; +import { ColorIndex, IDisposable } from 'common/Types'; +import { ITheme } from 'common/services/Services'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { @@ -73,7 +74,7 @@ export interface IRenderService extends IDisposable { resize(cols: number, rows: number): void; hasRenderer(): boolean; setRenderer(renderer: IRenderer): void; - setColors(colors: IColorSet): void; + setColors(colors: ReadonlyColorSet): void; handleDevicePixelRatioChange(): void; handleResize(cols: number, rows: number): void; handleCharSizeChanged(): void; @@ -121,3 +122,17 @@ export interface ICharacterJoinerService { deregister(joinerId: number): boolean; getJoinedCharacters(row: number): [number, number][]; } + +export const IThemeService = createDecorator('ThemeService'); +export interface IThemeService { + serviceBrand: undefined; + + readonly colors: ReadonlyColorSet; + setTheme(theme?: ITheme): void; + restoreColor(slot?: ColorIndex): void; + /** + * Allows external modifying of colors in the theme, this is used instead of {@link colors} to + * prevent accidental writes. + */ + modifyColors(callback: (colors: IColorSet) => void): void; +} diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts new file mode 100644 index 00000000..72f2163e --- /dev/null +++ b/src/browser/services/ThemeService.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ColorManager } from 'browser/ColorManager'; +import { IThemeService } from 'browser/services/Services'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; +import { Disposable } from 'common/Lifecycle'; +import { IOptionsService, ITheme } from 'common/services/Services'; +import { ColorIndex } from 'common/Types'; + +export class ThemeService extends Disposable implements IThemeService { + public serviceBrand: undefined; + + // TODO: Merge color manager into theme service + private _colorManager = new ColorManager(); + + public get colors(): ReadonlyColorSet { return this._colorManager.colors; } + + constructor( + @IOptionsService private readonly _optionsService: IOptionsService + ) { + super(); + this.register(this._optionsService.onOptionChange(key => { + if (key === 'theme') { + this.setTheme(this._optionsService.rawOptions.theme); + } + this._colorManager.handleOptionsChange(key, this._optionsService.rawOptions[key]); + })); + this._colorManager.setTheme(this._optionsService.rawOptions.theme); + } + + public setTheme(theme: ITheme = {}): void { + this._colorManager.setTheme(theme); + } + + public restoreColor(slot?: ColorIndex): void { + this._colorManager.restoreColor(slot); + } + + public modifyColors(callback: (colors: IColorSet) => void): void { + callback(this._colorManager.colors); + // TODO: Fire event + } +} From cc6def57e43f5b567fadc99b7d0d3c815f1a3b8e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 11:46:41 -0700 Subject: [PATCH 64/87] Use readonly color set in canvas addon --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 8 ++++---- addons/xterm-addon-canvas/src/CanvasAddon.ts | 6 +++--- addons/xterm-addon-canvas/src/CanvasRenderer.ts | 4 ++-- addons/xterm-addon-canvas/src/CursorRenderLayer.ts | 4 ++-- addons/xterm-addon-canvas/src/LinkRenderLayer.ts | 4 ++-- addons/xterm-addon-canvas/src/SelectionRenderLayer.ts | 4 ++-- addons/xterm-addon-canvas/src/TextRenderLayer.ts | 4 ++-- addons/xterm-addon-canvas/src/Types.d.ts | 4 ++-- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index b18bb209..c1e0d6eb 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -11,7 +11,7 @@ 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 { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -46,7 +46,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer id: string, zIndex: number, private _alpha: boolean, - protected _colors: IColorSet, + protected _colors: ReadonlyColorSet, protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, protected readonly _decorationService: IDecorationService, @@ -85,7 +85,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._selectionModel.update(this._terminal, start, end, columnSelectMode); } - public setColors(colorSet: IColorSet): void { + public setColors(colorSet: ReadonlyColorSet): void { this._refreshCharAtlas(colorSet); } @@ -112,7 +112,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * Refreshes the char atlas, aquiring a new one if necessary. * @param colorSet The color set to use for the char atlas. */ - private _refreshCharAtlas(colorSet: IColorSet): void { + private _refreshCharAtlas(colorSet: ReadonlyColorSet): void { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index c9c55e0d..1edfb3b9 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; +import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -38,11 +38,11 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { const coreBrowserService: ICoreBrowserService = core._coreBrowserService; const decorationService: IDecorationService = core._decorationService; const optionsService: IOptionsService = core.optionsService; - const colors: IColorSet = core._colorManager.colors; + const themeService: IThemeService = core._themeService; const screenElement: HTMLElement = core.screenElement; const linkifier = core.linkifier2; - this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); + this._renderer = new CanvasRenderer(terminal, themeService.colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); renderService.setRenderer(this._renderer); renderService.handleResize(bufferService.cols, bufferService.rows); diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 1abda096..367db872 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -7,7 +7,7 @@ import { removeTerminalFromCache } from 'browser/renderer/shared/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 { IColorSet, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -31,7 +31,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { constructor( private readonly _terminal: Terminal, - private _colors: IColorSet, + private _colors: ReadonlyColorSet, private readonly _screenElement: HTMLElement, linkifier2: ILinkifier2, private readonly _bufferService: IBufferService, diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index d32eb229..1bf7c6ff 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/ import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; @@ -37,7 +37,7 @@ export class CursorRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: IColorSet, + colors: ReadonlyColorSet, private readonly _onRequestRedraw: IEventEmitter, bufferService: IBufferService, optionsService: IOptionsService, diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index b1403f7a..2ddeee75 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; -import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; +import { IColorSet, ILinkifierEvent, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; import { Terminal } from 'xterm'; @@ -19,7 +19,7 @@ export class LinkRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: IColorSet, + colors: ReadonlyColorSet, linkifier2: ILinkifier2, bufferService: IBufferService, optionsService: IOptionsService, diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 0b8408c0..49b36813 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -5,7 +5,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICoreBrowserService } from 'browser/services/Services'; import { Terminal } from 'xterm'; @@ -24,7 +24,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: IColorSet, + colors: ReadonlyColorSet, bufferService: IBufferService, coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 23ac55ad..0393b30a 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -9,7 +9,7 @@ import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, Content, UnderlineStyle } from 'common/buffer/Constants'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; @@ -35,7 +35,7 @@ export class TextRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: IColorSet, + colors: ReadonlyColorSet, alpha: boolean, bufferService: IBufferService, optionsService: IOptionsService, diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index b1c3f0e3..4e9373c3 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -4,7 +4,7 @@ */ import { IDisposable } from 'common/Types'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IEvent } from 'common/EventEmitter'; // TODO: Use core interfaces @@ -82,7 +82,7 @@ export interface IRenderLayer extends IDisposable { /** * Called when the theme changes. */ - setColors(colorSet: IColorSet): void; + setColors(colorSet: ReadonlyColorSet): void; /** * Called when the data in the grid has changed (or needs to be rendered From 4f5154fd21b2cecdaf72098d4ac24f65ff1a59d3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 11:56:29 -0700 Subject: [PATCH 65/87] Ensure stale bitmap is not used when drawing new characters This would cause a problem where the first time a glyph is used it would not be visible because it was using the old invalidated bitmap. --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index b18bb209..2990fe03 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -357,10 +357,6 @@ export abstract class BaseRenderLayer extends Disposable 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; @@ -372,6 +368,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._ctx.save(); this._clipRow(y); // Draw the image, use the bitmap if it's available + if (this._charAtlas.hasCanvasChanged) { + this._bitmapGenerator?.refresh(); + this._charAtlas.hasCanvasChanged = false; + } this._ctx.drawImage( this._bitmapGenerator?.bitmap || this._charAtlas!.cacheCanvas, glyph.texturePosition.x, From 4dee0c5d4bc3834c2ff0e85ec82002a756546cee Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 12:15:21 -0700 Subject: [PATCH 66/87] Build out theme service more --- src/browser/Terminal.ts | 1 - src/browser/renderer/shared/Types.d.ts | 4 ++-- src/browser/services/RenderService.ts | 12 ++++++++---- src/browser/services/Services.ts | 5 +++-- src/browser/services/ThemeService.ts | 13 ++++++++++--- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index c641a544..a2f1aad9 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -241,7 +241,6 @@ export class Terminal extends CoreTerminal implements ITerminal { } } // TODO: Have these listen to theme change - this._renderService?.setColors(this._themeService.colors); this.viewport?.handleThemeChange(this._themeService.colors); } diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 5704f312..d5dd378b 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -4,7 +4,7 @@ */ import { FontWeight, Terminal } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IDisposable } from 'common/Types'; import { IEvent } from 'common/EventEmitter'; @@ -61,7 +61,7 @@ export interface IRenderer extends IDisposable { readonly onRequestRedraw: IEvent; dispose(): void; - setColors(colors: IColorSet): void; + setColors(colors: ReadonlyColorSet): void; handleDevicePixelRatioChange(): void; handleResize(cols: number, rows: number): void; handleCharSizeChanged(): void; diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index a51fe722..585344f9 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -9,10 +9,11 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types'; +import { IColorSet, IRenderDebouncerWithCallback, ReadonlyColorSet } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; -import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; +import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { DebouncedIdleTask } from 'common/TaskQueue'; +import { ThemeService } from 'browser/services/ThemeService'; interface ISelectionState { start: [number, number] | undefined; @@ -58,7 +59,8 @@ export class RenderService extends Disposable implements IRenderService { @ICharSizeService private readonly _charSizeService: ICharSizeService, @IDecorationService decorationService: IDecorationService, @IBufferService bufferService: IBufferService, - @ICoreBrowserService coreBrowserService: ICoreBrowserService + @ICoreBrowserService coreBrowserService: ICoreBrowserService, + @IThemeService themeService: IThemeService ) { super(); @@ -89,6 +91,8 @@ export class RenderService extends Disposable implements IRenderService { // matchMedia query. this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange())); + this.register(themeService.onChangeColors(e => this._handleChangeColors(e))); + // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so if ('IntersectionObserver' in coreBrowserService.window) { @@ -204,7 +208,7 @@ export class RenderService extends Disposable implements IRenderService { this._fullRefresh(); } - public setColors(colors: IColorSet): void { + private _handleChangeColors(colors: ReadonlyColorSet): void { if (!this._renderer) { return; } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 6132c3ea..76a0f7df 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -74,7 +74,6 @@ export interface IRenderService extends IDisposable { resize(cols: number, rows: number): void; hasRenderer(): boolean; setRenderer(renderer: IRenderer): void; - setColors(colors: ReadonlyColorSet): void; handleDevicePixelRatioChange(): void; handleResize(cols: number, rows: number): void; handleCharSizeChanged(): void; @@ -128,7 +127,9 @@ export interface IThemeService { serviceBrand: undefined; readonly colors: ReadonlyColorSet; - setTheme(theme?: ITheme): void; + + readonly onChangeColors: IEvent; + restoreColor(slot?: ColorIndex): void; /** * Allows external modifying of colors in the theme, this is used instead of {@link colors} to diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts index 72f2163e..7765eca6 100644 --- a/src/browser/services/ThemeService.ts +++ b/src/browser/services/ThemeService.ts @@ -6,6 +6,7 @@ import { ColorManager } from 'browser/ColorManager'; import { IThemeService } from 'browser/services/Services'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; +import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IOptionsService, ITheme } from 'common/services/Services'; import { ColorIndex } from 'common/Types'; @@ -18,29 +19,35 @@ export class ThemeService extends Disposable implements IThemeService { public get colors(): ReadonlyColorSet { return this._colorManager.colors; } + private readonly _onChangeColors = this.register(new EventEmitter()); + public readonly onChangeColors = this._onChangeColors.event; + constructor( @IOptionsService private readonly _optionsService: IOptionsService ) { super(); this.register(this._optionsService.onOptionChange(key => { if (key === 'theme') { - this.setTheme(this._optionsService.rawOptions.theme); + this._setTheme(this._optionsService.rawOptions.theme); } this._colorManager.handleOptionsChange(key, this._optionsService.rawOptions[key]); })); this._colorManager.setTheme(this._optionsService.rawOptions.theme); } - public setTheme(theme: ITheme = {}): void { + private _setTheme(theme: ITheme = {}): void { this._colorManager.setTheme(theme); + this._onChangeColors.fire(this.colors); } public restoreColor(slot?: ColorIndex): void { this._colorManager.restoreColor(slot); + this._onChangeColors.fire(this.colors); } public modifyColors(callback: (colors: IColorSet) => void): void { callback(this._colorManager.colors); - // TODO: Fire event + // Assume the change happened + this._onChangeColors.fire(this.colors); } } From 80e474c6ad57eefaf8507fa051cca7718b151cc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 13:31:31 -0700 Subject: [PATCH 67/87] Viewport listen to theme service --- src/browser/Terminal.ts | 19 +------------------ src/browser/Types.d.ts | 1 - src/browser/Viewport.ts | 13 ++++++++----- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index a2f1aad9..d535f9a0 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -240,8 +240,6 @@ export class Terminal extends CoreTerminal implements ITerminal { break; } } - // TODO: Have these listen to theme change - this.viewport?.handleThemeChange(this._themeService.colors); } protected _setup(): void { @@ -310,9 +308,6 @@ export class Terminal extends CoreTerminal implements ITerminal { } break; case 'tabStopWidth': this.buffers.setupTabStops(); break; - case 'theme': - this._setTheme(this.optionsService.rawOptions.theme); - break; } } @@ -531,11 +526,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this.viewport = this._instantiationService.createInstance(Viewport, (amount: number) => this.scrollLines(amount, true, ScrollSource.VIEWPORT), this._viewportElement, - this._viewportScrollArea, - this.element + this._viewportScrollArea ); - // TODO: Listen to theme service event - this.viewport.handleThemeChange(this._themeService.colors); this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea())); this.register(this.viewport); @@ -616,15 +608,6 @@ export class Terminal extends CoreTerminal implements ITerminal { return this._instantiationService.createInstance(DomRenderer, this._themeService!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); } - /** - * Sets the theme on the renderer. The renderer must have been initialized. - * @param theme The theme to set. - */ - private _setTheme(theme: ITheme): void { - // TODO: Listen to theme service events - this.viewport?.handleThemeChange(this._themeService!.colors); - } - /** * Bind certain mouse events to the terminal. * By default only 3 button + wheel up/down is ativated. For higher buttons diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 830cbac1..da1f358f 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -152,7 +152,6 @@ export interface IViewport extends IDisposable { handleWheel(ev: WheelEvent): boolean; handleTouchStart(ev: TouchEvent): void; handleTouchMove(ev: TouchEvent): boolean; - handleThemeChange(colors: ReadonlyColorSet): void; } export interface ILinkifierEvent { diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 395052e6..8f88c559 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -5,8 +5,8 @@ import { Disposable } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IColorSet, IViewport } from 'browser/Types'; -import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; +import { IColorSet, IViewport, ReadonlyColorSet } from 'browser/Types'; +import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { IBuffer } from 'common/buffer/Types'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; @@ -52,12 +52,12 @@ export class Viewport extends Disposable implements IViewport { private readonly _scrollLines: (amount: number) => void, private readonly _viewportElement: HTMLElement, private readonly _scrollArea: HTMLElement, - private readonly _element: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @IOptionsService private readonly _optionsService: IOptionsService, @ICharSizeService private readonly _charSizeService: ICharSizeService, @IRenderService private readonly _renderService: IRenderService, - @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, + @IThemeService themeService: IThemeService ) { super(); @@ -73,11 +73,14 @@ export class Viewport extends Disposable implements IViewport { this._renderDimensions = this._renderService.dimensions; this.register(this._renderService.onDimensionsChange(e => this._renderDimensions = e)); + this._handleThemeChange(themeService.colors); + this.register(themeService.onChangeColors(e => this._handleThemeChange(e))); + // Perform this async to ensure the ICharSizeService is ready. setTimeout(() => this.syncScrollArea(), 0); } - public handleThemeChange(colors: IColorSet): void { + private _handleThemeChange(colors: ReadonlyColorSet): void { this._viewportElement.style.backgroundColor = colors.background.css; } From 6e35bcd7676c830422332ff9201a14154773704f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 13:55:23 -0700 Subject: [PATCH 68/87] Use theme service in more places --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 24 ++++----- addons/xterm-addon-canvas/src/CanvasAddon.ts | 2 +- .../xterm-addon-canvas/src/CanvasRenderer.ts | 18 +++---- .../src/CursorRenderLayer.ts | 20 ++++---- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 14 +++--- .../src/SelectionRenderLayer.ts | 12 ++--- .../xterm-addon-canvas/src/TextRenderLayer.ts | 14 +++--- addons/xterm-addon-canvas/src/Types.d.ts | 5 -- addons/xterm-addon-webgl/src/WebglAddon.ts | 3 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 22 ++++---- src/browser/Terminal.ts | 2 +- src/browser/TestUtils.test.ts | 42 ++++++++++++++-- src/browser/renderer/dom/DomRenderer.ts | 50 +++++++++---------- .../dom/DomRendererRowFactory.test.ts | 29 ++--------- .../renderer/dom/DomRendererRowFactory.ts | 36 +++++++------ .../renderer/shared/CellColorResolver.ts | 25 +++++----- src/browser/renderer/shared/Types.d.ts | 1 - src/browser/services/RenderService.ts | 10 +--- 18 files changed, 160 insertions(+), 169 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index c1e0d6eb..ad1f1c05 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -10,7 +10,7 @@ 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 { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; @@ -46,20 +46,24 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer id: string, zIndex: number, private _alpha: boolean, - protected _colors: ReadonlyColorSet, + protected readonly _themeService: IThemeService, protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, protected readonly _decorationService: IDecorationService, protected readonly _coreBrowserService: ICoreBrowserService ) { super(); - this._cellColorResolver = new CellColorResolver(this._terminal, this._colors, this._selectionModel, this._decorationService, this._coreBrowserService); + this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService); this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); this._container.appendChild(this._canvas); - this._refreshCharAtlas(this._colors); + this._refreshCharAtlas(this._themeService.colors); + this.register(this._themeService.onChangeColors(e => { + this._refreshCharAtlas(e); + this.reset(); + })); this.register(toDisposable(() => { removeElementFromParent(this._canvas); @@ -85,10 +89,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._selectionModel.update(this._terminal, start, end, columnSelectMode); } - public setColors(colorSet: ReadonlyColorSet): void { - this._refreshCharAtlas(colorSet); - } - protected _setTransparency(alpha: boolean): void { // Do nothing when alpha doesn't change if (alpha === this._alpha) { @@ -104,7 +104,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._container.replaceChild(this._canvas, oldCanvas); // Regenerate char atlas and force a full redraw - this._refreshCharAtlas(this._colors); + this._refreshCharAtlas(this._themeService.colors); this.handleGridChanged(0, this._bufferService.rows - 1); } @@ -138,7 +138,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._clearAll(); } - this._refreshCharAtlas(this._colors); + this._refreshCharAtlas(this._themeService.colors); } public abstract reset(): void; @@ -294,7 +294,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer if (this._alpha) { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } else { - this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillStyle = this._themeService.colors.background.css; this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); } } @@ -314,7 +314,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer width * this._scaledCellWidth, height * this._scaledCellHeight); } else { - this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillStyle = this._themeService.colors.background.css; this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 1edfb3b9..1dc607e5 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -42,7 +42,7 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { const screenElement: HTMLElement = core.screenElement; const linkifier = core.linkifier2; - this._renderer = new CanvasRenderer(terminal, themeService.colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); + this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); renderService.setRenderer(this._renderer); renderService.handleResize(bufferService.cols, bufferService.rows); diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 367db872..63ed4ed8 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -6,7 +6,7 @@ import { removeTerminalFromCache } from 'browser/renderer/shared/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 { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; @@ -31,7 +31,6 @@ export class CanvasRenderer extends Disposable implements IRenderer { constructor( private readonly _terminal: Terminal, - private _colors: ReadonlyColorSet, private readonly _screenElement: HTMLElement, linkifier2: ILinkifier2, private readonly _bufferService: IBufferService, @@ -40,15 +39,16 @@ export class CanvasRenderer extends Disposable implements IRenderer { characterJoinerService: ICharacterJoinerService, coreService: ICoreService, private readonly _coreBrowserService: ICoreBrowserService, - decorationService: IDecorationService + decorationService: IDecorationService, + private readonly _themeService: IThemeService ) { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ - 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 TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService), + new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService), + new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService), + new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService) ]; this.dimensions = { scaledCharWidth: 0, @@ -93,10 +93,8 @@ export class CanvasRenderer extends Disposable implements IRenderer { } public setColors(colors: IColorSet): void { - this._colors = colors; // Clear layers and force a full render for (const l of this._renderLayers) { - l.setColors(this._colors); l.reset(); } } @@ -130,7 +128,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { this._runOperation(l => l.handleSelectionChanged(start, end, columnSelectMode)); // Selection foreground requires a full re-render - if (this._colors.selectionForeground) { + if (this._themeService.colors.selectionForeground) { this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 1bf7c6ff..ab8b1e66 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -10,7 +10,7 @@ import { CellData } from 'common/buffer/CellData'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; -import { ICoreBrowserService } from 'browser/services/Services'; +import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { Terminal } from 'xterm'; import { toDisposable } from 'common/Lifecycle'; @@ -37,15 +37,15 @@ export class CursorRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: ReadonlyColorSet, private readonly _onRequestRedraw: IEventEmitter, bufferService: IBufferService, optionsService: IOptionsService, private readonly _coreService: ICoreService, coreBrowserService: ICoreBrowserService, - decorationService: IDecorationService + decorationService: IDecorationService, + themeService: IThemeService ) { - super(terminal, container, 'cursor', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'cursor', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); this._state = { x: 0, y: 0, @@ -146,7 +146,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (!this._coreBrowserService.isFocused) { this._clearCursor(); this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor.css; + this._ctx.fillStyle = this._themeService.colors.cursor.css; const cursorStyle = this._optionsService.rawOptions.cursorStyle; if (cursorStyle && cursorStyle !== 'block') { this._cursorRenderers[cursorStyle](cursorX, viewportRelativeCursorY, this._cell); @@ -211,30 +211,30 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor.css; + this._ctx.fillStyle = this._themeService.colors.cursor.css; this._fillLeftLineAtCell(x, y, this._optionsService.rawOptions.cursorWidth); this._ctx.restore(); } private _renderBlockCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor.css; + this._ctx.fillStyle = this._themeService.colors.cursor.css; this._fillCells(x, y, cell.getWidth(), 1); - this._ctx.fillStyle = this._colors.cursorAccent.css; + this._ctx.fillStyle = this._themeService.colors.cursorAccent.css; this._fillCharTrueColor(cell, x, y); this._ctx.restore(); } private _renderUnderlineCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor.css; + this._ctx.fillStyle = this._themeService.colors.cursor.css; this._fillBottomLineAtCells(x, y); this._ctx.restore(); } private _renderBlurCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); - this._ctx.strokeStyle = this._colors.cursor.css; + this._ctx.strokeStyle = this._themeService.colors.cursor.css; this._strokeRectAtCell(x, y, cell.getWidth(), 1); this._ctx.restore(); } diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index 2ddeee75..2d4c217c 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -6,7 +6,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; -import { ICoreBrowserService } from 'browser/services/Services'; +import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ILinkifierEvent, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; @@ -19,14 +19,14 @@ export class LinkRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: ReadonlyColorSet, linkifier2: ILinkifier2, bufferService: IBufferService, optionsService: IOptionsService, decorationService: IDecorationService, - coreBrowserService: ICoreBrowserService + coreBrowserService: ICoreBrowserService, + themeService: IThemeService ) { - super(terminal, container, 'link', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'link', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); this.register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); this.register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); @@ -56,12 +56,12 @@ export class LinkRenderLayer extends BaseRenderLayer { private _handleShowLinkUnderline(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillStyle = this._themeService.colors.background.css; } else if (e.fg && is256Color(e.fg)) { // 256 color support - this._ctx.fillStyle = this._colors.ansi[e.fg].css; + this._ctx.fillStyle = this._themeService.colors.ansi[e.fg].css; } else { - this._ctx.fillStyle = this._colors.foreground.css; + this._ctx.fillStyle = this._themeService.colors.foreground.css; } if (e.y1 === e.y2) { diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 49b36813..1c96dcc8 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ICoreBrowserService } from 'browser/services/Services'; +import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { Terminal } from 'xterm'; interface ISelectionState { @@ -24,13 +24,13 @@ export class SelectionRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: ReadonlyColorSet, bufferService: IBufferService, coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, - optionsService: IOptionsService + optionsService: IOptionsService, + themeService: IThemeService ) { - super(terminal, container, 'selection', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'selection', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); this._clearState(); } @@ -102,8 +102,8 @@ export class SelectionRenderLayer extends BaseRenderLayer { } this._ctx.fillStyle = (this._coreBrowserService.isFocused - ? this._colors.selectionBackgroundTransparent - : this._colors.selectionInactiveBackgroundTransparent).css; + ? this._themeService.colors.selectionBackgroundTransparent + : this._themeService.colors.selectionInactiveBackgroundTransparent).css; if (columnSelectMode) { const startCol = start[0]; diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 0393b30a..ca9eae56 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -12,7 +12,7 @@ import { NULL_CELL_CODE, Content, UnderlineStyle } from 'common/buffer/Constants import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; -import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; +import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { color, css } from 'common/Color'; import { Terminal } from 'xterm'; @@ -35,15 +35,15 @@ export class TextRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: ReadonlyColorSet, alpha: boolean, bufferService: IBufferService, optionsService: IOptionsService, private readonly _characterJoinerService: ICharacterJoinerService, decorationService: IDecorationService, - coreBrowserService: ICoreBrowserService + coreBrowserService: ICoreBrowserService, + themeService: IThemeService ) { - super(terminal, container, 'text', zIndex, alpha, colors, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService); this._state = new GridCache(); } @@ -168,16 +168,16 @@ export class TextRenderLayer extends BaseRenderLayer { if (cell.isInverse()) { if (cell.isFgDefault()) { - nextFillStyle = this._colors.foreground.css; + nextFillStyle = this._themeService.colors.foreground.css; } else if (cell.isFgRGB()) { nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; } else { - nextFillStyle = this._colors.ansi[cell.getFgColor()].css; + nextFillStyle = this._themeService.colors.ansi[cell.getFgColor()].css; } } else if (cell.isBgRGB()) { nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; } else if (cell.isBgPalette()) { - nextFillStyle = this._colors.ansi[cell.getBgColor()].css; + nextFillStyle = this._themeService.colors.ansi[cell.getBgColor()].css; } // Apply dim to the background, this is relatively slow as the CSS is re-parsed but dim is diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index 4e9373c3..19b1b426 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -79,11 +79,6 @@ export interface IRenderLayer extends IDisposable { */ handleOptionsChanged(): void; - /** - * Called when the theme changes. - */ - setColors(colorSet: ReadonlyColorSet): void; - /** * Called when the data in the grid has changed (or needs to be rendered * again). diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index f0e0a0e9..71487315 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -43,8 +43,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon { const coreService: ICoreService = core.coreService; const decorationService: IDecorationService = core._decorationService; const themeService: IThemeService = core._themeService; - // TODO: Pass in theme service - this._renderer = this.register(new WebglRenderer(terminal, themeService.colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer)); + this._renderer = this.register(new WebglRenderer(terminal, themeService, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer)); this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); this.register(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 072e11a9..65361f67 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -8,7 +8,7 @@ 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 { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ITerminal, ReadonlyColorSet } from 'browser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; @@ -55,7 +55,7 @@ export class WebglRenderer extends Disposable implements IRenderer { constructor( private _terminal: Terminal, - private _colors: ReadonlyColorSet, + private readonly _themeService: IThemeService, private readonly _characterJoinerService: ICharacterJoinerService, private readonly _coreBrowserService: ICoreBrowserService, coreService: ICoreService, @@ -64,13 +64,15 @@ export class WebglRenderer extends Disposable implements IRenderer { ) { super(); - this._cellColorResolver = new CellColorResolver(this._terminal, this._colors, this._model.selection, this._decorationService, this._coreBrowserService); + this.register(this._themeService.onChangeColors(e => this._handleColorChange(e))); + + this._cellColorResolver = new CellColorResolver(this._terminal, this._model.selection, this._decorationService, this._coreBrowserService, this._themeService); this._core = (this._terminal as any)._core; 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 LinkRenderLayer(this._core.screenElement!, 2, this._themeService.colors, this._core, this._coreBrowserService), + new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._themeService.colors, this._onRequestRedraw, this._coreBrowserService, coreService) ]; this.dimensions = { scaledCharWidth: 0, @@ -145,15 +147,13 @@ export class WebglRenderer extends Disposable implements IRenderer { return this._charAtlas?.cacheCanvas; } - public setColors(colors: IColorSet): void { - this._colors = colors; + private _handleColorChange(colors: ReadonlyColorSet): void { // Clear layers and force a full render for (const l of this._renderLayers) { - l.setColors(this._terminal, this._colors); + l.setColors(this._terminal, colors); l.reset(this._terminal); } - this._cellColorResolver.setColors(colors); this._rectangleRenderer.setColors(); this._refreshCharAtlas(); @@ -254,7 +254,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer?.dispose(); this._glyphRenderer?.dispose(); - this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions)); + this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._themeService.colors, this._gl, this.dimensions)); this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._gl, this.dimensions)); // Update dimensions and acquire char atlas @@ -273,7 +273,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - const atlas = acquireTextureAtlas(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._themeService.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); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index d535f9a0..1a409f35 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -605,7 +605,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _createRenderer(): IRenderer { // TODO: Listen to theme service - return this._instantiationService.createInstance(DomRenderer, this._themeService!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); + return this._instantiationService.createInstance(DomRenderer, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); } /** diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 282d77a7..467c4544 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -5,11 +5,11 @@ 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 { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } 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'; +import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions, ColorIndex } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { Terminal } from 'browser/Terminal'; @@ -17,6 +17,7 @@ import { IUnicodeService, IOptionsService, ICoreService, ICoreMouseService } fro import { IFunctionIdentifier, IParams } from 'common/parser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; +import { css } from 'common/Color'; export class TestTerminal extends Terminal { public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; } @@ -505,3 +506,38 @@ export class MockSelectionService implements ISelectionService { return false; } } + +export class MockThemeService implements IThemeService{ + public serviceBrand: undefined; + public onChangeColors = new EventEmitter().event; + public restoreColor(slot?: ColorIndex | undefined): void { + throw new Error('Method not implemented.'); + } + public modifyColors(callback: (colors: IColorSet) => void): void { + throw new Error('Method not implemented.'); + } + public colors: ReadonlyColorSet = { + background: css.toColor('#010101'), + foreground: css.toColor('#020202'), + ansi: [ + // dark: + css.toColor('#2e3436'), + css.toColor('#cc0000'), + css.toColor('#4e9a06'), + css.toColor('#c4a000'), + css.toColor('#3465a4'), + css.toColor('#75507b'), + css.toColor('#06989a'), + css.toColor('#d3d7cf'), + // bright: + css.toColor('#555753'), + css.toColor('#ef2929'), + css.toColor('#8ae234'), + css.toColor('#fce94f'), + css.toColor('#729fcf'), + css.toColor('#ad7fa8'), + css.toColor('#34e2e2'), + css.toColor('#eeeeec') + ] + } as any; +} diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index d9d0268b..5d2729c0 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -8,7 +8,7 @@ import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSO import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; -import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; +import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'common/Color'; @@ -43,7 +43,6 @@ export class DomRenderer extends Disposable implements IRenderer { public readonly onRequestRedraw = this.register(new EventEmitter()).event; constructor( - private _colors: ReadonlyColorSet, private readonly _element: HTMLElement, private readonly _screenElement: HTMLElement, private readonly _viewportElement: HTMLElement, @@ -52,7 +51,8 @@ export class DomRenderer extends Disposable implements IRenderer { @ICharSizeService private readonly _charSizeService: ICharSizeService, @IOptionsService private readonly _optionsService: IOptionsService, @IBufferService private readonly _bufferService: IBufferService, - @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, + @IThemeService themeService: IThemeService ) { super(); this._rowContainer = document.createElement('div'); @@ -79,9 +79,11 @@ export class DomRenderer extends Disposable implements IRenderer { actualCellHeight: 0 }; this._updateDimensions(); - this._injectCss(); - this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document, this._colors); + this.register(themeService.onChangeColors(e => this._injectCss(e))); + this._injectCss(themeService.colors); + + this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document); this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass); this._screenElement.appendChild(this._rowContainer); @@ -142,12 +144,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._screenElement.style.height = `${this.dimensions.canvasHeight}px`; } - public setColors(colors: IColorSet): void { - this._colors = colors; - this._injectCss(); - } - - private _injectCss(): void { + private _injectCss(colors: ReadonlyColorSet): void { if (!this._themeStyleElement) { this._themeStyleElement = document.createElement('style'); this._screenElement.appendChild(this._themeStyleElement); @@ -156,7 +153,7 @@ export class DomRenderer extends Disposable implements IRenderer { // Base CSS let styles = `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + - ` color: ${this._colors.foreground.css};` + + ` color: ${colors.foreground.css};` + ` font-family: ${this._optionsService.rawOptions.fontFamily};` + ` font-size: ${this._optionsService.rawOptions.fontSize}px;` + `}`; @@ -181,18 +178,18 @@ export class DomRenderer extends Disposable implements IRenderer { styles += `@keyframes blink_block` + `_` + this._terminalClass + ` {` + ` 0% {` + - ` background-color: ${this._colors.cursor.css};` + - ` color: ${this._colors.cursorAccent.css};` + + ` background-color: ${colors.cursor.css};` + + ` color: ${colors.cursorAccent.css};` + ` }` + ` 50% {` + - ` background-color: ${this._colors.cursorAccent.css};` + - ` color: ${this._colors.cursor.css};` + + ` background-color: ${colors.cursorAccent.css};` + + ` color: ${colors.cursor.css};` + ` }` + `}`; // Cursor styles += `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + - ` outline: 1px solid ${this._colors.cursor.css};` + + ` outline: 1px solid ${colors.cursor.css};` + ` outline-offset: -1px;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` + @@ -202,14 +199,14 @@ export class DomRenderer extends Disposable implements IRenderer { ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` + - ` background-color: ${this._colors.cursor.css};` + - ` color: ${this._colors.cursorAccent.css};` + + ` background-color: ${colors.cursor.css};` + + ` color: ${colors.cursorAccent.css};` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` + - ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` + + ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` + - ` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` + + ` box-shadow: 0 -1px 0 ${colors.cursor.css} inset;` + `}`; // Selection styles += @@ -222,21 +219,21 @@ export class DomRenderer extends Disposable implements IRenderer { `}` + `${this._terminalSelector}.focus .${SELECTION_CLASS} div {` + ` position: absolute;` + - ` background-color: ${this._colors.selectionBackgroundOpaque.css};` + + ` background-color: ${colors.selectionBackgroundOpaque.css};` + `}` + `${this._terminalSelector} .${SELECTION_CLASS} div {` + ` position: absolute;` + - ` background-color: ${this._colors.selectionInactiveBackgroundOpaque.css};` + + ` background-color: ${colors.selectionInactiveBackgroundOpaque.css};` + `}`; // Colors - for (const [i, c] of this._colors.ansi.entries()) { + for (const [i, c] of 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}; }`; + `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(colors.background).css}; }` + + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${colors.foreground.css}; }`; this._themeStyleElement.textContent = styles; } @@ -348,7 +345,6 @@ export class DomRenderer extends Disposable implements IRenderer { public handleOptionsChanged(): void { // Force a refresh this._updateDimensions(); - this._injectCss(); } public clear(): void { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index b228f3d6..fbb48536 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -12,7 +12,7 @@ import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test'; import { css } from 'common/Color'; -import { MockCharacterJoinerService, MockCoreBrowserService } from 'browser/TestUtils.test'; +import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from 'browser/TestUtils.test'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; @@ -23,35 +23,12 @@ describe('DomRendererRowFactory', () => { dom = new jsdom.JSDOM(''); rowFactory = new DomRendererRowFactory( dom.window.document, - { - background: css.toColor('#010101'), - foreground: css.toColor('#020202'), - ansi: [ - // dark: - css.toColor('#2e3436'), - css.toColor('#cc0000'), - css.toColor('#4e9a06'), - css.toColor('#c4a000'), - css.toColor('#3465a4'), - css.toColor('#75507b'), - css.toColor('#06989a'), - css.toColor('#d3d7cf'), - // bright: - css.toColor('#555753'), - css.toColor('#ef2929'), - css.toColor('#8ae234'), - css.toColor('#fce94f'), - css.toColor('#729fcf'), - css.toColor('#ad7fa8'), - css.toColor('#34e2e2'), - css.toColor('#eeeeec') - ] - } as any, new MockCharacterJoinerService(), new MockOptionsService({ drawBoldTextInBrightColors: true }), new MockCoreBrowserService(), new MockCoreService(), - new MockDecorationService() + new MockDecorationService(), + new MockThemeService() ); lineData = createEmptyLineData(2); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 56b19601..14b26c92 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -10,7 +10,7 @@ import { CellData } from 'common/buffer/CellData'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { color, rgba } from 'common/Color'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; -import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; +import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -35,19 +35,15 @@ export class DomRendererRowFactory { constructor( private readonly _document: Document, - private _colors: ReadonlyColorSet, @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, @IOptionsService private readonly _optionsService: IOptionsService, @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, @ICoreService private readonly _coreService: ICoreService, - @IDecorationService private readonly _decorationService: IDecorationService + @IDecorationService private readonly _decorationService: IDecorationService, + @IThemeService private readonly _themeService: IThemeService ) { } - public setColors(colors: IColorSet): void { - this._colors = colors; - } - public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { this._selectionStart = start; this._selectionEnd = end; @@ -71,6 +67,8 @@ export class DomRendererRowFactory { } } + const colors = this._themeService.colors; + for (let x = 0; x < lineLength; x++) { lineData.loadCell(x, this._workCell); let width = this._workCell.getWidth(); @@ -176,7 +174,7 @@ export class DomRendererRowFactory { if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { fg += 8; } - charElement.style.textDecorationColor = this._colors.ansi[fg].css; + charElement.style.textDecorationColor = colors.ansi[fg].css; } } } @@ -224,17 +222,17 @@ export class DomRendererRowFactory { // Apply selection foreground if applicable const isInSelection = this._isCellInSelection(x, row); if (!isTop) { - if (this._colors.selectionForeground && isInSelection) { + if (colors.selectionForeground && isInSelection) { fgColorMode = Attributes.CM_RGB; - fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; - fgOverride = this._colors.selectionForeground; + fg = colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + fgOverride = colors.selectionForeground; } } // If in the selection, force the element to be above the selection to improve contrast and // support opaque selections if (isInSelection) { - bgOverride = this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque; + bgOverride = this._coreBrowserService.isFocused ? colors.selectionBackgroundOpaque : colors.selectionInactiveBackgroundOpaque; isTop = true; } @@ -248,7 +246,7 @@ export class DomRendererRowFactory { switch (bgColorMode) { case Attributes.CM_P16: case Attributes.CM_P256: - resolvedBg = this._colors.ansi[bg]; + resolvedBg = colors.ansi[bg]; charElement.classList.add(`xterm-bg-${bg}`); break; case Attributes.CM_RGB: @@ -258,10 +256,10 @@ export class DomRendererRowFactory { case Attributes.CM_DEFAULT: default: if (isInverse) { - resolvedBg = this._colors.foreground; + resolvedBg = colors.foreground; charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`); } else { - resolvedBg = this._colors.background; + resolvedBg = colors.background; } } @@ -279,7 +277,7 @@ export class DomRendererRowFactory { if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) { fg += 8; } - if (!this._applyMinimumContrast(charElement, resolvedBg, this._colors.ansi[fg], cell, bgOverride, undefined)) { + if (!this._applyMinimumContrast(charElement, resolvedBg, colors.ansi[fg], cell, bgOverride, undefined)) { charElement.classList.add(`xterm-fg-${fg}`); } break; @@ -295,7 +293,7 @@ export class DomRendererRowFactory { break; case Attributes.CM_DEFAULT: default: - if (!this._applyMinimumContrast(charElement, resolvedBg, this._colors.foreground, cell, bgOverride, undefined)) { + if (!this._applyMinimumContrast(charElement, resolvedBg, colors.foreground, cell, bgOverride, undefined)) { if (isInverse) { charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); } @@ -317,13 +315,13 @@ export class DomRendererRowFactory { // Try get from cache first, only use the cache when there are no decoration overrides let adjustedColor: IColor | undefined | null = undefined; if (!bgOverride && !fgOverride) { - adjustedColor = this._colors.contrastCache.getColor(bg.rgba, fg.rgba); + adjustedColor = this._themeService.colors.contrastCache.getColor(bg.rgba, fg.rgba); } // Calculate and store in cache if (adjustedColor === undefined) { adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, this._optionsService.rawOptions.minimumContrastRatio); - this._colors.contrastCache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null); + this._themeService.colors.contrastCache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null); } if (adjustedColor) { diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index eeb0edb6..ae5019e2 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -1,5 +1,5 @@ import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; -import { ICoreBrowserService } from 'browser/services/Services'; +import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; import { IDecorationService } from 'common/services/Services'; @@ -12,6 +12,7 @@ let $bg = 0; let $hasFg = false; let $hasBg = false; let $isSelected = false; +let $colors: ReadonlyColorSet | undefined; export class CellColorResolver { /** @@ -26,17 +27,13 @@ export class CellColorResolver { constructor( private readonly _terminal: Terminal, - private _colors: ReadonlyColorSet, private readonly _selectionRenderModel: ISelectionRenderModel, private readonly _decorationService: IDecorationService, - private readonly _coreBrowserService: ICoreBrowserService + private readonly _coreBrowserService: ICoreBrowserService, + private readonly _themeService: IThemeService ) { } - 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. @@ -54,6 +51,7 @@ export class CellColorResolver { $hasBg = false; $hasFg = false; $isSelected = false; + $colors = this._themeService.colors; // Apply decorations on the bottom layer this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { @@ -70,10 +68,10 @@ export class CellColorResolver { // 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; + $bg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; $hasBg = true; - if (this._colors.selectionForeground) { - $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + if ($colors.selectionForeground) { + $fg = $colors.selectionForeground.rgba >> 8 & 0xFFFFFF; $hasFg = true; } } @@ -112,7 +110,7 @@ export class CellColorResolver { 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; + $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | (($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); } @@ -121,7 +119,7 @@ export class CellColorResolver { 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; + $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | (($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); } @@ -129,6 +127,9 @@ export class CellColorResolver { } } + // Release object + $colors = undefined; + // Use the override if it exists this.result.bg = $hasBg ? $bg : this.result.bg; this.result.fg = $hasFg ? $fg : this.result.fg; diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index d5dd378b..1def0f77 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -61,7 +61,6 @@ export interface IRenderer extends IDisposable { readonly onRequestRedraw: IEvent; dispose(): void; - setColors(colors: ReadonlyColorSet): void; handleDevicePixelRatioChange(): void; handleResize(cols: number, rows: number): void; handleCharSizeChanged(): void; diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 585344f9..dd0c6b50 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -91,7 +91,7 @@ export class RenderService extends Disposable implements IRenderService { // matchMedia query. this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange())); - this.register(themeService.onChangeColors(e => this._handleChangeColors(e))); + this.register(themeService.onChangeColors(() => this._fullRefresh())); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so @@ -208,14 +208,6 @@ export class RenderService extends Disposable implements IRenderService { this._fullRefresh(); } - private _handleChangeColors(colors: ReadonlyColorSet): void { - if (!this._renderer) { - return; - } - this._renderer.setColors(colors); - this._fullRefresh(); - } - public handleDevicePixelRatioChange(): void { // Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable // when devicePixelRatio changes From 48f15cb5433a61fafd19d5078be5ebbdf8cfe933 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 14:07:28 -0700 Subject: [PATCH 69/87] Remove remaining setColors methods --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 7 ----- addons/xterm-addon-canvas/src/Types.d.ts | 1 - .../src/RectangleRenderer.ts | 28 ++++++++--------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 18 ++++------- .../src/renderLayer/BaseRenderLayer.ts | 23 +++++++------- .../src/renderLayer/CursorRenderLayer.ts | 20 ++++++------- .../src/renderLayer/LinkRenderLayer.ts | 30 +++++++++---------- .../src/renderLayer/Types.ts | 5 ---- src/browser/TestUtils.test.ts | 6 ---- 9 files changed, 56 insertions(+), 82 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 63ed4ed8..ff92e94b 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -92,13 +92,6 @@ export class CanvasRenderer extends Disposable implements IRenderer { } } - public setColors(colors: IColorSet): void { - // Clear layers and force a full render - for (const l of this._renderLayers) { - l.reset(); - } - } - public handleResize(cols: number, rows: number): void { // Update character and canvas dimensions this._updateDimensions(); diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index 19b1b426..1840284f 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -41,7 +41,6 @@ export interface IRenderer extends IDisposable { */ readonly onRequestRedraw: IEvent; - setColors(colors: IColorSet): void; handleDevicePixelRatioChange(): void; handleResize(cols: number, rows: number): void; handleCharSizeChanged(): void; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 62a61101..04368711 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -14,6 +14,7 @@ import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_P import { Disposable, toDisposable } from 'common/Lifecycle'; import { DIM_OPACITY } from 'browser/renderer/shared/Constants'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { IThemeService } from 'browser/services/Services'; const enum VertexAttribLocations { POSITION = 0, @@ -84,9 +85,9 @@ export class RectangleRenderer extends Disposable { constructor( private _terminal: Terminal, - private _colors: ReadonlyColorSet, private _gl: IWebGL2RenderingContext, - private _dimensions: IRenderDimensions + private _dimensions: IRenderDimensions, + private readonly _themeService: IThemeService ) { super(); @@ -133,7 +134,11 @@ export class RectangleRenderer extends Disposable { gl.vertexAttribPointer(VertexAttribLocations.COLOR, 4, gl.FLOAT, false, BYTES_PER_RECTANGLE, 4 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.COLOR, 1); - this._updateCachedColors(); + this._updateCachedColors(_themeService.colors); + this.register(this._themeService.onChangeColors(e => { + this._updateCachedColors(e); + this._updateViewportRectangle(); + })); } public render(): void { @@ -155,17 +160,12 @@ export class RectangleRenderer extends Disposable { this._updateViewportRectangle(); } - public setColors(): void { - this._updateCachedColors(); - this._updateViewportRectangle(); - } - public setDimensions(dimensions: IRenderDimensions): void { this._dimensions = dimensions; } - private _updateCachedColors(): void { - this._bgFloat = this._colorToFloat32Array(this._colors.background); + private _updateCachedColors(colors: ReadonlyColorSet): void { + this._bgFloat = this._colorToFloat32Array(colors.background); } private _updateViewportRectangle(): void { @@ -236,27 +236,27 @@ export class RectangleRenderer extends Disposable { switch (fg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: - $rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; + $rgba = this._themeService.colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: $rgba = (fg & Attributes.RGB_MASK) << 8; break; case Attributes.CM_DEFAULT: default: - $rgba = this._colors.foreground.rgba; + $rgba = this._themeService.colors.foreground.rgba; } } else { switch (bg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: - $rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; + $rgba = this._themeService.colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: $rgba = (bg & Attributes.RGB_MASK) << 8; break; case Attributes.CM_DEFAULT: default: - $rgba = this._colors.background.rgba; + $rgba = this._themeService.colors.background.rgba; $isDefault = true; } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 65361f67..ed99a225 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -64,15 +64,15 @@ export class WebglRenderer extends Disposable implements IRenderer { ) { super(); - this.register(this._themeService.onChangeColors(e => this._handleColorChange(e))); + this.register(this._themeService.onChangeColors(() => this._handleColorChange())); this._cellColorResolver = new CellColorResolver(this._terminal, this._model.selection, this._decorationService, this._coreBrowserService, this._themeService); this._core = (this._terminal as any)._core; this._renderLayers = [ - new LinkRenderLayer(this._core.screenElement!, 2, this._themeService.colors, this._core, this._coreBrowserService), - new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._themeService.colors, this._onRequestRedraw, this._coreBrowserService, coreService) + new LinkRenderLayer(this._core.screenElement!, 2, this._terminal, this._core.linkifier2, this._coreBrowserService, this._themeService), + new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._onRequestRedraw, this._coreBrowserService, coreService, this._themeService) ]; this.dimensions = { scaledCharWidth: 0, @@ -147,15 +147,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return this._charAtlas?.cacheCanvas; } - private _handleColorChange(colors: ReadonlyColorSet): void { - // Clear layers and force a full render - for (const l of this._renderLayers) { - l.setColors(this._terminal, colors); - l.reset(this._terminal); - } - - this._rectangleRenderer.setColors(); - + private _handleColorChange(): void { this._refreshCharAtlas(); // Force a full refresh @@ -254,7 +246,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer?.dispose(); this._glyphRenderer?.dispose(); - this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._themeService.colors, this._gl, this.dimensions)); + this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService)); this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._gl, this.dimensions)); // Update dimensions and acquire char atlas diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index d4a0a86c..e99a1464 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -8,7 +8,7 @@ import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { ICoreBrowserService } from 'browser/services/Services'; +import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { CellData } from 'common/buffer/CellData'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; @@ -27,12 +27,13 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected _charAtlas: ITextureAtlas | undefined; constructor( + terminal: Terminal, private _container: HTMLElement, id: string, zIndex: number, private _alpha: boolean, - protected _colors: ReadonlyColorSet, - protected readonly _coreBrowserService: ICoreBrowserService + protected readonly _coreBrowserService: ICoreBrowserService, + protected readonly _themeService: IThemeService ) { super(); this._canvas = document.createElement('canvas'); @@ -40,6 +41,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); this._container.appendChild(this._canvas); + this.register(this._themeService.onChangeColors(e => { + this._refreshCharAtlas(terminal, e); + this.reset(terminal); + })); this.register(toDisposable(() => { this._canvas.remove(); this._charAtlas?.dispose(); @@ -61,10 +66,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer public handleGridChanged(terminal: Terminal, startRow: number, endRow: number): void {} public handleSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {} - public setColors(terminal: Terminal, colorSet: ReadonlyColorSet): void { - this._refreshCharAtlas(terminal, colorSet); - } - protected _setTransparency(terminal: Terminal, alpha: boolean): void { // Do nothing when alpha doesn't change if (alpha === this._alpha) { @@ -80,7 +81,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._container.replaceChild(this._canvas, oldCanvas); // Regenerate char atlas and force a full redraw - this._refreshCharAtlas(terminal, this._colors); + this._refreshCharAtlas(terminal, this._themeService.colors); this.handleGridChanged(terminal, 0, terminal.rows - 1); } @@ -114,7 +115,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._clearAll(); } - this._refreshCharAtlas(terminal, this._colors); + this._refreshCharAtlas(terminal, this._themeService.colors); } public abstract reset(terminal: Terminal): void; @@ -184,7 +185,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer if (this._alpha) { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } else { - this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillStyle = this._themeService.colors.background.css; this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); } } @@ -204,7 +205,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer width * this._scaledCellWidth, height * this._scaledCellHeight); } else { - this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillStyle = this._themeService.colors.background.css; this._ctx.fillRect( x * this._scaledCellWidth, y * this._scaledCellHeight, diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index b1718d75..74801f4e 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -10,7 +10,7 @@ import { CellData } from 'common/buffer/CellData'; import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IEventEmitter } from 'common/EventEmitter'; -import { ICoreBrowserService } from 'browser/services/Services'; +import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ICoreService } from 'common/services/Services'; import { toDisposable } from 'common/Lifecycle'; @@ -37,12 +37,12 @@ export class CursorRenderLayer extends BaseRenderLayer { terminal: Terminal, container: HTMLElement, zIndex: number, - colors: ReadonlyColorSet, private _onRequestRefreshRowsEvent: IEventEmitter, coreBrowserService: ICoreBrowserService, - private readonly _coreService: ICoreService + private readonly _coreService: ICoreService, + themeService: IThemeService ) { - super(container, 'cursor', zIndex, true, colors, coreBrowserService); + super(terminal, container, 'cursor', zIndex, true, coreBrowserService, themeService); this._state = { x: 0, y: 0, @@ -146,7 +146,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (!this._coreBrowserService.isFocused) { this._clearCursor(); this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor.css; + this._ctx.fillStyle = this._themeService.colors.cursor.css; const cursorStyle = terminal.options.cursorStyle; if (cursorStyle && cursorStyle !== 'block') { this._cursorRenderers[cursorStyle](terminal, cursorX, viewportRelativeCursorY, this._cell); @@ -211,30 +211,30 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor.css; + this._ctx.fillStyle = this._themeService.colors.cursor.css; this._fillLeftLineAtCell(x, y, terminal.options.cursorWidth); this._ctx.restore(); } private _renderBlockCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor.css; + this._ctx.fillStyle = this._themeService.colors.cursor.css; this._fillCells(x, y, cell.getWidth(), 1); - this._ctx.fillStyle = this._colors.cursorAccent.css; + this._ctx.fillStyle = this._themeService.colors.cursorAccent.css; this._fillCharTrueColor(terminal, cell, x, y); this._ctx.restore(); } private _renderUnderlineCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); - this._ctx.fillStyle = this._colors.cursor.css; + this._ctx.fillStyle = this._themeService.colors.cursor.css; this._fillBottomLineAtCells(x, y); this._ctx.restore(); } private _renderBlurCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); - this._ctx.strokeStyle = this._colors.cursor.css; + this._ctx.strokeStyle = this._themeService.colors.cursor.css; this._strokeRectAtCell(x, y, cell.getWidth(), 1); this._ctx.restore(); } diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 9c841843..2d2af192 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -3,14 +3,13 @@ * @license MIT */ +import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; +import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; +import { ILinkifier2, ILinkifierEvent, ITerminal } from 'browser/Types'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; -import { ITerminal, IColorSet, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/shared/Types'; -import { ICoreBrowserService } from 'browser/services/Services'; -import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; -import { toDisposable } from 'common/Lifecycle'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; @@ -18,14 +17,15 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor( container: HTMLElement, zIndex: number, - colors: ReadonlyColorSet, - terminal: ITerminal, - coreBrowserService: ICoreBrowserService + terminal: Terminal, + linkifier2: ILinkifier2, + coreBrowserService: ICoreBrowserService, + themeService: IThemeService ) { - super(container, 'link', zIndex, true, colors, coreBrowserService); + super(terminal, container, 'link', zIndex, true, coreBrowserService, themeService); - this.register(terminal.linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); - this.register(terminal.linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); + this.register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); + this.register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); } public resize(terminal: Terminal, dim: IRenderDimensions): void { @@ -52,12 +52,12 @@ export class LinkRenderLayer extends BaseRenderLayer { private _handleShowLinkUnderline(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillStyle = this._themeService.colors.background.css; } else if (e.fg !== undefined && is256Color(e.fg)) { // 256 color support - this._ctx.fillStyle = this._colors.ansi[e.fg!].css; + this._ctx.fillStyle = this._themeService.colors.ansi[e.fg!].css; } else { - this._ctx.fillStyle = this._colors.foreground.css; + this._ctx.fillStyle = this._themeService.colors.foreground.css; } if (e.y1 === e.y2) { diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts index 32013ee3..089680ca 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/Types.ts @@ -28,11 +28,6 @@ export interface IRenderLayer extends IDisposable { */ handleOptionsChanged(terminal: Terminal): void; - /** - * Called when the theme changes. - */ - setColors(terminal: Terminal, colorSet: ReadonlyColorSet): void; - /** * Called when the data in the grid has changed (or needs to be rendered * again). diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 467c4544..a0837895 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -280,9 +280,6 @@ export class MockRenderer implements IRenderer { throw new Error('Method not implemented.'); } public dimensions!: IRenderDimensions; - public setColors(colors: IColorSet): void { - throw new Error('Method not implemented.'); - } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration { throw new Error('Method not implemented.'); } @@ -408,9 +405,6 @@ export class MockRenderService implements IRenderService { public setRenderer(renderer: IRenderer): void { throw new Error('Method not implemented.'); } - public setColors(colors: IColorSet): void { - throw new Error('Method not implemented.'); - } public handleDevicePixelRatioChange(): void { throw new Error('Method not implemented.'); } From 7f9e3b12257d7e827dd01864cb6c7695c53e22b9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 14:09:00 -0700 Subject: [PATCH 70/87] Remove resolved todo --- src/browser/Terminal.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 1a409f35..e0ba8a94 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -604,7 +604,6 @@ export class Terminal extends CoreTerminal implements ITerminal { } private _createRenderer(): IRenderer { - // TODO: Listen to theme service return this._instantiationService.createInstance(DomRenderer, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); } From a1050d1a1131df20adebfab63f2fb277a2ca1905 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 15:20:15 -0700 Subject: [PATCH 71/87] Merge ColorManager into ThemeService --- .../src/SerializeAddon.test.ts | 1 + src/browser/ColorManager.test.ts | 366 ----------------- src/browser/services/ThemeService.test.ts | 368 ++++++++++++++++++ src/browser/services/ThemeService.ts | 216 +++++++++- 4 files changed, 572 insertions(+), 379 deletions(-) delete mode 100644 src/browser/ColorManager.test.ts create mode 100644 src/browser/services/ThemeService.test.ts diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts index df4a72da..b47c7928 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts @@ -77,6 +77,7 @@ describe('xterm-addon-serialize', () => { terminal = new Terminal({ cols: 10, rows: 2, allowProposedApi: true }); terminal.loadAddon(serializeAddon); + // TODO: Fix color manager reference selectionService = new TestSelectionService((terminal as any)._core._bufferService); cm = new ColorManager(); (terminal as any)._core._colorManager = cm; diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts deleted file mode 100644 index cf60a1f5..00000000 --- a/src/browser/ColorManager.test.ts +++ /dev/null @@ -1,366 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import jsdom = require('jsdom'); -import { assert } from 'chai'; -import { ColorManager, DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; - -describe('ColorManager', () => { - let cm: ColorManager; - let dom: jsdom.JSDOM; - let document: Document; - let window: jsdom.DOMWindow; - - beforeEach(() => { - dom = new jsdom.JSDOM(''); - window = dom.window; - document = window.document; - (window as any).HTMLCanvasElement.prototype.getContext = () => ({ - createLinearGradient(): any { - return null; - }, - - fillRect(): void { }, - - getImageData(): any { - return {data: [0, 0, 0, 0xFF]}; - } - }); - cm = new ColorManager(); - }); - - describe('constructor', () => { - it('should fill all colors with values', () => { - for (const key of Object.keys(cm.colors)) { - if (key !== 'ansi' && key !== 'contrastCache' && key !== 'selectionForeground') { - // A #rrggbb or rgba(...) - assert.ok((cm.colors as any)[key].css.length >= 7); - } - } - assert.equal(cm.colors.ansi.length, 256); - }); - - it('should fill 240 colors with expected values', () => { - assert.equal(cm.colors.ansi[16].css, '#000000'); - assert.equal(cm.colors.ansi[17].css, '#00005f'); - assert.equal(cm.colors.ansi[18].css, '#000087'); - assert.equal(cm.colors.ansi[19].css, '#0000af'); - assert.equal(cm.colors.ansi[20].css, '#0000d7'); - assert.equal(cm.colors.ansi[21].css, '#0000ff'); - assert.equal(cm.colors.ansi[22].css, '#005f00'); - assert.equal(cm.colors.ansi[23].css, '#005f5f'); - assert.equal(cm.colors.ansi[24].css, '#005f87'); - assert.equal(cm.colors.ansi[25].css, '#005faf'); - assert.equal(cm.colors.ansi[26].css, '#005fd7'); - assert.equal(cm.colors.ansi[27].css, '#005fff'); - assert.equal(cm.colors.ansi[28].css, '#008700'); - assert.equal(cm.colors.ansi[29].css, '#00875f'); - assert.equal(cm.colors.ansi[30].css, '#008787'); - assert.equal(cm.colors.ansi[31].css, '#0087af'); - assert.equal(cm.colors.ansi[32].css, '#0087d7'); - assert.equal(cm.colors.ansi[33].css, '#0087ff'); - assert.equal(cm.colors.ansi[34].css, '#00af00'); - assert.equal(cm.colors.ansi[35].css, '#00af5f'); - assert.equal(cm.colors.ansi[36].css, '#00af87'); - assert.equal(cm.colors.ansi[37].css, '#00afaf'); - assert.equal(cm.colors.ansi[38].css, '#00afd7'); - assert.equal(cm.colors.ansi[39].css, '#00afff'); - assert.equal(cm.colors.ansi[40].css, '#00d700'); - assert.equal(cm.colors.ansi[41].css, '#00d75f'); - assert.equal(cm.colors.ansi[42].css, '#00d787'); - assert.equal(cm.colors.ansi[43].css, '#00d7af'); - assert.equal(cm.colors.ansi[44].css, '#00d7d7'); - assert.equal(cm.colors.ansi[45].css, '#00d7ff'); - assert.equal(cm.colors.ansi[46].css, '#00ff00'); - assert.equal(cm.colors.ansi[47].css, '#00ff5f'); - assert.equal(cm.colors.ansi[48].css, '#00ff87'); - assert.equal(cm.colors.ansi[49].css, '#00ffaf'); - assert.equal(cm.colors.ansi[50].css, '#00ffd7'); - assert.equal(cm.colors.ansi[51].css, '#00ffff'); - assert.equal(cm.colors.ansi[52].css, '#5f0000'); - assert.equal(cm.colors.ansi[53].css, '#5f005f'); - assert.equal(cm.colors.ansi[54].css, '#5f0087'); - assert.equal(cm.colors.ansi[55].css, '#5f00af'); - assert.equal(cm.colors.ansi[56].css, '#5f00d7'); - assert.equal(cm.colors.ansi[57].css, '#5f00ff'); - assert.equal(cm.colors.ansi[58].css, '#5f5f00'); - assert.equal(cm.colors.ansi[59].css, '#5f5f5f'); - assert.equal(cm.colors.ansi[60].css, '#5f5f87'); - assert.equal(cm.colors.ansi[61].css, '#5f5faf'); - assert.equal(cm.colors.ansi[62].css, '#5f5fd7'); - assert.equal(cm.colors.ansi[63].css, '#5f5fff'); - assert.equal(cm.colors.ansi[64].css, '#5f8700'); - assert.equal(cm.colors.ansi[65].css, '#5f875f'); - assert.equal(cm.colors.ansi[66].css, '#5f8787'); - assert.equal(cm.colors.ansi[67].css, '#5f87af'); - assert.equal(cm.colors.ansi[68].css, '#5f87d7'); - assert.equal(cm.colors.ansi[69].css, '#5f87ff'); - assert.equal(cm.colors.ansi[70].css, '#5faf00'); - assert.equal(cm.colors.ansi[71].css, '#5faf5f'); - assert.equal(cm.colors.ansi[72].css, '#5faf87'); - assert.equal(cm.colors.ansi[73].css, '#5fafaf'); - assert.equal(cm.colors.ansi[74].css, '#5fafd7'); - assert.equal(cm.colors.ansi[75].css, '#5fafff'); - assert.equal(cm.colors.ansi[76].css, '#5fd700'); - assert.equal(cm.colors.ansi[77].css, '#5fd75f'); - assert.equal(cm.colors.ansi[78].css, '#5fd787'); - assert.equal(cm.colors.ansi[79].css, '#5fd7af'); - assert.equal(cm.colors.ansi[80].css, '#5fd7d7'); - assert.equal(cm.colors.ansi[81].css, '#5fd7ff'); - assert.equal(cm.colors.ansi[82].css, '#5fff00'); - assert.equal(cm.colors.ansi[83].css, '#5fff5f'); - assert.equal(cm.colors.ansi[84].css, '#5fff87'); - assert.equal(cm.colors.ansi[85].css, '#5fffaf'); - assert.equal(cm.colors.ansi[86].css, '#5fffd7'); - assert.equal(cm.colors.ansi[87].css, '#5fffff'); - assert.equal(cm.colors.ansi[88].css, '#870000'); - assert.equal(cm.colors.ansi[89].css, '#87005f'); - assert.equal(cm.colors.ansi[90].css, '#870087'); - assert.equal(cm.colors.ansi[91].css, '#8700af'); - assert.equal(cm.colors.ansi[92].css, '#8700d7'); - assert.equal(cm.colors.ansi[93].css, '#8700ff'); - assert.equal(cm.colors.ansi[94].css, '#875f00'); - assert.equal(cm.colors.ansi[95].css, '#875f5f'); - assert.equal(cm.colors.ansi[96].css, '#875f87'); - assert.equal(cm.colors.ansi[97].css, '#875faf'); - assert.equal(cm.colors.ansi[98].css, '#875fd7'); - assert.equal(cm.colors.ansi[99].css, '#875fff'); - assert.equal(cm.colors.ansi[100].css, '#878700'); - assert.equal(cm.colors.ansi[101].css, '#87875f'); - assert.equal(cm.colors.ansi[102].css, '#878787'); - assert.equal(cm.colors.ansi[103].css, '#8787af'); - assert.equal(cm.colors.ansi[104].css, '#8787d7'); - assert.equal(cm.colors.ansi[105].css, '#8787ff'); - assert.equal(cm.colors.ansi[106].css, '#87af00'); - assert.equal(cm.colors.ansi[107].css, '#87af5f'); - assert.equal(cm.colors.ansi[108].css, '#87af87'); - assert.equal(cm.colors.ansi[109].css, '#87afaf'); - assert.equal(cm.colors.ansi[110].css, '#87afd7'); - assert.equal(cm.colors.ansi[111].css, '#87afff'); - assert.equal(cm.colors.ansi[112].css, '#87d700'); - assert.equal(cm.colors.ansi[113].css, '#87d75f'); - assert.equal(cm.colors.ansi[114].css, '#87d787'); - assert.equal(cm.colors.ansi[115].css, '#87d7af'); - assert.equal(cm.colors.ansi[116].css, '#87d7d7'); - assert.equal(cm.colors.ansi[117].css, '#87d7ff'); - assert.equal(cm.colors.ansi[118].css, '#87ff00'); - assert.equal(cm.colors.ansi[119].css, '#87ff5f'); - assert.equal(cm.colors.ansi[120].css, '#87ff87'); - assert.equal(cm.colors.ansi[121].css, '#87ffaf'); - assert.equal(cm.colors.ansi[122].css, '#87ffd7'); - assert.equal(cm.colors.ansi[123].css, '#87ffff'); - assert.equal(cm.colors.ansi[124].css, '#af0000'); - assert.equal(cm.colors.ansi[125].css, '#af005f'); - assert.equal(cm.colors.ansi[126].css, '#af0087'); - assert.equal(cm.colors.ansi[127].css, '#af00af'); - assert.equal(cm.colors.ansi[128].css, '#af00d7'); - assert.equal(cm.colors.ansi[129].css, '#af00ff'); - assert.equal(cm.colors.ansi[130].css, '#af5f00'); - assert.equal(cm.colors.ansi[131].css, '#af5f5f'); - assert.equal(cm.colors.ansi[132].css, '#af5f87'); - assert.equal(cm.colors.ansi[133].css, '#af5faf'); - assert.equal(cm.colors.ansi[134].css, '#af5fd7'); - assert.equal(cm.colors.ansi[135].css, '#af5fff'); - assert.equal(cm.colors.ansi[136].css, '#af8700'); - assert.equal(cm.colors.ansi[137].css, '#af875f'); - assert.equal(cm.colors.ansi[138].css, '#af8787'); - assert.equal(cm.colors.ansi[139].css, '#af87af'); - assert.equal(cm.colors.ansi[140].css, '#af87d7'); - assert.equal(cm.colors.ansi[141].css, '#af87ff'); - assert.equal(cm.colors.ansi[142].css, '#afaf00'); - assert.equal(cm.colors.ansi[143].css, '#afaf5f'); - assert.equal(cm.colors.ansi[144].css, '#afaf87'); - assert.equal(cm.colors.ansi[145].css, '#afafaf'); - assert.equal(cm.colors.ansi[146].css, '#afafd7'); - assert.equal(cm.colors.ansi[147].css, '#afafff'); - assert.equal(cm.colors.ansi[148].css, '#afd700'); - assert.equal(cm.colors.ansi[149].css, '#afd75f'); - assert.equal(cm.colors.ansi[150].css, '#afd787'); - assert.equal(cm.colors.ansi[151].css, '#afd7af'); - assert.equal(cm.colors.ansi[152].css, '#afd7d7'); - assert.equal(cm.colors.ansi[153].css, '#afd7ff'); - assert.equal(cm.colors.ansi[154].css, '#afff00'); - assert.equal(cm.colors.ansi[155].css, '#afff5f'); - assert.equal(cm.colors.ansi[156].css, '#afff87'); - assert.equal(cm.colors.ansi[157].css, '#afffaf'); - assert.equal(cm.colors.ansi[158].css, '#afffd7'); - assert.equal(cm.colors.ansi[159].css, '#afffff'); - assert.equal(cm.colors.ansi[160].css, '#d70000'); - assert.equal(cm.colors.ansi[161].css, '#d7005f'); - assert.equal(cm.colors.ansi[162].css, '#d70087'); - assert.equal(cm.colors.ansi[163].css, '#d700af'); - assert.equal(cm.colors.ansi[164].css, '#d700d7'); - assert.equal(cm.colors.ansi[165].css, '#d700ff'); - assert.equal(cm.colors.ansi[166].css, '#d75f00'); - assert.equal(cm.colors.ansi[167].css, '#d75f5f'); - assert.equal(cm.colors.ansi[168].css, '#d75f87'); - assert.equal(cm.colors.ansi[169].css, '#d75faf'); - assert.equal(cm.colors.ansi[170].css, '#d75fd7'); - assert.equal(cm.colors.ansi[171].css, '#d75fff'); - assert.equal(cm.colors.ansi[172].css, '#d78700'); - assert.equal(cm.colors.ansi[173].css, '#d7875f'); - assert.equal(cm.colors.ansi[174].css, '#d78787'); - assert.equal(cm.colors.ansi[175].css, '#d787af'); - assert.equal(cm.colors.ansi[176].css, '#d787d7'); - assert.equal(cm.colors.ansi[177].css, '#d787ff'); - assert.equal(cm.colors.ansi[178].css, '#d7af00'); - assert.equal(cm.colors.ansi[179].css, '#d7af5f'); - assert.equal(cm.colors.ansi[180].css, '#d7af87'); - assert.equal(cm.colors.ansi[181].css, '#d7afaf'); - assert.equal(cm.colors.ansi[182].css, '#d7afd7'); - assert.equal(cm.colors.ansi[183].css, '#d7afff'); - assert.equal(cm.colors.ansi[184].css, '#d7d700'); - assert.equal(cm.colors.ansi[185].css, '#d7d75f'); - assert.equal(cm.colors.ansi[186].css, '#d7d787'); - assert.equal(cm.colors.ansi[187].css, '#d7d7af'); - assert.equal(cm.colors.ansi[188].css, '#d7d7d7'); - assert.equal(cm.colors.ansi[189].css, '#d7d7ff'); - assert.equal(cm.colors.ansi[190].css, '#d7ff00'); - assert.equal(cm.colors.ansi[191].css, '#d7ff5f'); - assert.equal(cm.colors.ansi[192].css, '#d7ff87'); - assert.equal(cm.colors.ansi[193].css, '#d7ffaf'); - assert.equal(cm.colors.ansi[194].css, '#d7ffd7'); - assert.equal(cm.colors.ansi[195].css, '#d7ffff'); - assert.equal(cm.colors.ansi[196].css, '#ff0000'); - assert.equal(cm.colors.ansi[197].css, '#ff005f'); - assert.equal(cm.colors.ansi[198].css, '#ff0087'); - assert.equal(cm.colors.ansi[199].css, '#ff00af'); - assert.equal(cm.colors.ansi[200].css, '#ff00d7'); - assert.equal(cm.colors.ansi[201].css, '#ff00ff'); - assert.equal(cm.colors.ansi[202].css, '#ff5f00'); - assert.equal(cm.colors.ansi[203].css, '#ff5f5f'); - assert.equal(cm.colors.ansi[204].css, '#ff5f87'); - assert.equal(cm.colors.ansi[205].css, '#ff5faf'); - assert.equal(cm.colors.ansi[206].css, '#ff5fd7'); - assert.equal(cm.colors.ansi[207].css, '#ff5fff'); - assert.equal(cm.colors.ansi[208].css, '#ff8700'); - assert.equal(cm.colors.ansi[209].css, '#ff875f'); - assert.equal(cm.colors.ansi[210].css, '#ff8787'); - assert.equal(cm.colors.ansi[211].css, '#ff87af'); - assert.equal(cm.colors.ansi[212].css, '#ff87d7'); - assert.equal(cm.colors.ansi[213].css, '#ff87ff'); - assert.equal(cm.colors.ansi[214].css, '#ffaf00'); - assert.equal(cm.colors.ansi[215].css, '#ffaf5f'); - assert.equal(cm.colors.ansi[216].css, '#ffaf87'); - assert.equal(cm.colors.ansi[217].css, '#ffafaf'); - assert.equal(cm.colors.ansi[218].css, '#ffafd7'); - assert.equal(cm.colors.ansi[219].css, '#ffafff'); - assert.equal(cm.colors.ansi[220].css, '#ffd700'); - assert.equal(cm.colors.ansi[221].css, '#ffd75f'); - assert.equal(cm.colors.ansi[222].css, '#ffd787'); - assert.equal(cm.colors.ansi[223].css, '#ffd7af'); - assert.equal(cm.colors.ansi[224].css, '#ffd7d7'); - assert.equal(cm.colors.ansi[225].css, '#ffd7ff'); - assert.equal(cm.colors.ansi[226].css, '#ffff00'); - assert.equal(cm.colors.ansi[227].css, '#ffff5f'); - assert.equal(cm.colors.ansi[228].css, '#ffff87'); - assert.equal(cm.colors.ansi[229].css, '#ffffaf'); - assert.equal(cm.colors.ansi[230].css, '#ffffd7'); - assert.equal(cm.colors.ansi[231].css, '#ffffff'); - assert.equal(cm.colors.ansi[232].css, '#080808'); - assert.equal(cm.colors.ansi[233].css, '#121212'); - assert.equal(cm.colors.ansi[234].css, '#1c1c1c'); - assert.equal(cm.colors.ansi[235].css, '#262626'); - assert.equal(cm.colors.ansi[236].css, '#303030'); - assert.equal(cm.colors.ansi[237].css, '#3a3a3a'); - assert.equal(cm.colors.ansi[238].css, '#444444'); - assert.equal(cm.colors.ansi[239].css, '#4e4e4e'); - assert.equal(cm.colors.ansi[240].css, '#585858'); - assert.equal(cm.colors.ansi[241].css, '#626262'); - assert.equal(cm.colors.ansi[242].css, '#6c6c6c'); - assert.equal(cm.colors.ansi[243].css, '#767676'); - assert.equal(cm.colors.ansi[244].css, '#808080'); - assert.equal(cm.colors.ansi[245].css, '#8a8a8a'); - assert.equal(cm.colors.ansi[246].css, '#949494'); - assert.equal(cm.colors.ansi[247].css, '#9e9e9e'); - assert.equal(cm.colors.ansi[248].css, '#a8a8a8'); - assert.equal(cm.colors.ansi[249].css, '#b2b2b2'); - assert.equal(cm.colors.ansi[250].css, '#bcbcbc'); - assert.equal(cm.colors.ansi[251].css, '#c6c6c6'); - assert.equal(cm.colors.ansi[252].css, '#d0d0d0'); - assert.equal(cm.colors.ansi[253].css, '#dadada'); - assert.equal(cm.colors.ansi[254].css, '#e4e4e4'); - assert.equal(cm.colors.ansi[255].css, '#eeeeee'); - }); - }); - - describe('setTheme', () => { - it('should not throw when not setting all colors', () => { - assert.doesNotThrow(() => { - cm.setTheme({}); - }); - }); - - it('should set a partial set of colors, using the default if not present', () => { - assert.equal(cm.colors.background.css, '#000000'); - assert.equal(cm.colors.foreground.css, '#ffffff'); - cm.setTheme({ - background: '#FF0000', - foreground: '#00FF00' - }); - assert.equal(cm.colors.background.css, '#FF0000'); - assert.equal(cm.colors.foreground.css, '#00FF00'); - cm.setTheme({ - background: '#0000FF' - }); - assert.equal(cm.colors.background.css, '#0000FF'); - // FG reverts back to default - assert.equal(cm.colors.foreground.css, '#ffffff'); - }); - - it('should set all extended ansi colors in reverse order', () => { - cm.setTheme({ - extendedAnsi: DEFAULT_ANSI_COLORS.map(a => a.css).slice().reverse() - }); - - for (let ansiColor = 16; ansiColor <= 255; ansiColor++) { - assert.equal(cm.colors.ansi[ansiColor].css, DEFAULT_ANSI_COLORS[255 + 16 - ansiColor].css); - } - }); - - it('should set one extended ansi color and keep the other default', () => { - cm.setTheme({ - extendedAnsi: ['#ffffff'] - }); - - assert.equal(cm.colors.ansi[16].css, '#ffffff'); - assert.equal(cm.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css); - }); - - it('should set extended ansi colors to the default when they are unset', () => { - cm.setTheme({ - extendedAnsi: ['#ffffff'] - }); - assert.equal(cm.colors.ansi[16].css, '#ffffff'); - - cm.setTheme({ - extendedAnsi: [] - }); - assert.equal(cm.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css); - - cm.setTheme({ - extendedAnsi: ['#ffffff'] - }); - assert.equal(cm.colors.ansi[16].css, '#ffffff'); - - cm.setTheme({}); - assert.equal(cm.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css); - }); - - it('should set extended ansi colors to the default when they are partially unset', () => { - cm.setTheme({ - extendedAnsi: ['#ffffff', '#000000'] - }); - assert.equal(cm.colors.ansi[16].css, '#ffffff'); - assert.equal(cm.colors.ansi[17].css, '#000000'); - - cm.setTheme({ - extendedAnsi: ['#ffffff'] - }); - assert.equal(cm.colors.ansi[16].css, '#ffffff'); - assert.equal(cm.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css); - }); - }); -}); diff --git a/src/browser/services/ThemeService.test.ts b/src/browser/services/ThemeService.test.ts new file mode 100644 index 00000000..bebd0f56 --- /dev/null +++ b/src/browser/services/ThemeService.test.ts @@ -0,0 +1,368 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import jsdom = require('jsdom'); +import { assert } from 'chai'; +import { ThemeService, DEFAULT_ANSI_COLORS } from 'browser/services/ThemeService'; +import { MockOptionsService } from 'common/TestUtils.test'; +import { OptionsService } from 'common/services/OptionsService'; + +describe.only('ThemeService', () => { + let themeService: ThemeService; + let dom: jsdom.JSDOM; + let window: jsdom.DOMWindow; + let optionsService: OptionsService; + + beforeEach(() => { + dom = new jsdom.JSDOM(''); + window = dom.window; + (window as any).HTMLCanvasElement.prototype.getContext = () => ({ + createLinearGradient(): any { + return null; + }, + + fillRect(): void { }, + + getImageData(): any { + return {data: [0, 0, 0, 0xFF]}; + } + }); + optionsService = new OptionsService({}); + themeService = new ThemeService(optionsService); + }); + + describe('constructor', () => { + it('should fill all colors with values', () => { + for (const key of Object.keys(themeService.colors)) { + if (key !== 'ansi' && key !== 'contrastCache' && key !== 'selectionForeground') { + // A #rrggbb or rgba(...) + assert.ok((themeService.colors as any)[key].css.length >= 7); + } + } + assert.equal(themeService.colors.ansi.length, 256); + }); + + it('should fill 240 colors with expected values', () => { + assert.equal(themeService.colors.ansi[16].css, '#000000'); + assert.equal(themeService.colors.ansi[17].css, '#00005f'); + assert.equal(themeService.colors.ansi[18].css, '#000087'); + assert.equal(themeService.colors.ansi[19].css, '#0000af'); + assert.equal(themeService.colors.ansi[20].css, '#0000d7'); + assert.equal(themeService.colors.ansi[21].css, '#0000ff'); + assert.equal(themeService.colors.ansi[22].css, '#005f00'); + assert.equal(themeService.colors.ansi[23].css, '#005f5f'); + assert.equal(themeService.colors.ansi[24].css, '#005f87'); + assert.equal(themeService.colors.ansi[25].css, '#005faf'); + assert.equal(themeService.colors.ansi[26].css, '#005fd7'); + assert.equal(themeService.colors.ansi[27].css, '#005fff'); + assert.equal(themeService.colors.ansi[28].css, '#008700'); + assert.equal(themeService.colors.ansi[29].css, '#00875f'); + assert.equal(themeService.colors.ansi[30].css, '#008787'); + assert.equal(themeService.colors.ansi[31].css, '#0087af'); + assert.equal(themeService.colors.ansi[32].css, '#0087d7'); + assert.equal(themeService.colors.ansi[33].css, '#0087ff'); + assert.equal(themeService.colors.ansi[34].css, '#00af00'); + assert.equal(themeService.colors.ansi[35].css, '#00af5f'); + assert.equal(themeService.colors.ansi[36].css, '#00af87'); + assert.equal(themeService.colors.ansi[37].css, '#00afaf'); + assert.equal(themeService.colors.ansi[38].css, '#00afd7'); + assert.equal(themeService.colors.ansi[39].css, '#00afff'); + assert.equal(themeService.colors.ansi[40].css, '#00d700'); + assert.equal(themeService.colors.ansi[41].css, '#00d75f'); + assert.equal(themeService.colors.ansi[42].css, '#00d787'); + assert.equal(themeService.colors.ansi[43].css, '#00d7af'); + assert.equal(themeService.colors.ansi[44].css, '#00d7d7'); + assert.equal(themeService.colors.ansi[45].css, '#00d7ff'); + assert.equal(themeService.colors.ansi[46].css, '#00ff00'); + assert.equal(themeService.colors.ansi[47].css, '#00ff5f'); + assert.equal(themeService.colors.ansi[48].css, '#00ff87'); + assert.equal(themeService.colors.ansi[49].css, '#00ffaf'); + assert.equal(themeService.colors.ansi[50].css, '#00ffd7'); + assert.equal(themeService.colors.ansi[51].css, '#00ffff'); + assert.equal(themeService.colors.ansi[52].css, '#5f0000'); + assert.equal(themeService.colors.ansi[53].css, '#5f005f'); + assert.equal(themeService.colors.ansi[54].css, '#5f0087'); + assert.equal(themeService.colors.ansi[55].css, '#5f00af'); + assert.equal(themeService.colors.ansi[56].css, '#5f00d7'); + assert.equal(themeService.colors.ansi[57].css, '#5f00ff'); + assert.equal(themeService.colors.ansi[58].css, '#5f5f00'); + assert.equal(themeService.colors.ansi[59].css, '#5f5f5f'); + assert.equal(themeService.colors.ansi[60].css, '#5f5f87'); + assert.equal(themeService.colors.ansi[61].css, '#5f5faf'); + assert.equal(themeService.colors.ansi[62].css, '#5f5fd7'); + assert.equal(themeService.colors.ansi[63].css, '#5f5fff'); + assert.equal(themeService.colors.ansi[64].css, '#5f8700'); + assert.equal(themeService.colors.ansi[65].css, '#5f875f'); + assert.equal(themeService.colors.ansi[66].css, '#5f8787'); + assert.equal(themeService.colors.ansi[67].css, '#5f87af'); + assert.equal(themeService.colors.ansi[68].css, '#5f87d7'); + assert.equal(themeService.colors.ansi[69].css, '#5f87ff'); + assert.equal(themeService.colors.ansi[70].css, '#5faf00'); + assert.equal(themeService.colors.ansi[71].css, '#5faf5f'); + assert.equal(themeService.colors.ansi[72].css, '#5faf87'); + assert.equal(themeService.colors.ansi[73].css, '#5fafaf'); + assert.equal(themeService.colors.ansi[74].css, '#5fafd7'); + assert.equal(themeService.colors.ansi[75].css, '#5fafff'); + assert.equal(themeService.colors.ansi[76].css, '#5fd700'); + assert.equal(themeService.colors.ansi[77].css, '#5fd75f'); + assert.equal(themeService.colors.ansi[78].css, '#5fd787'); + assert.equal(themeService.colors.ansi[79].css, '#5fd7af'); + assert.equal(themeService.colors.ansi[80].css, '#5fd7d7'); + assert.equal(themeService.colors.ansi[81].css, '#5fd7ff'); + assert.equal(themeService.colors.ansi[82].css, '#5fff00'); + assert.equal(themeService.colors.ansi[83].css, '#5fff5f'); + assert.equal(themeService.colors.ansi[84].css, '#5fff87'); + assert.equal(themeService.colors.ansi[85].css, '#5fffaf'); + assert.equal(themeService.colors.ansi[86].css, '#5fffd7'); + assert.equal(themeService.colors.ansi[87].css, '#5fffff'); + assert.equal(themeService.colors.ansi[88].css, '#870000'); + assert.equal(themeService.colors.ansi[89].css, '#87005f'); + assert.equal(themeService.colors.ansi[90].css, '#870087'); + assert.equal(themeService.colors.ansi[91].css, '#8700af'); + assert.equal(themeService.colors.ansi[92].css, '#8700d7'); + assert.equal(themeService.colors.ansi[93].css, '#8700ff'); + assert.equal(themeService.colors.ansi[94].css, '#875f00'); + assert.equal(themeService.colors.ansi[95].css, '#875f5f'); + assert.equal(themeService.colors.ansi[96].css, '#875f87'); + assert.equal(themeService.colors.ansi[97].css, '#875faf'); + assert.equal(themeService.colors.ansi[98].css, '#875fd7'); + assert.equal(themeService.colors.ansi[99].css, '#875fff'); + assert.equal(themeService.colors.ansi[100].css, '#878700'); + assert.equal(themeService.colors.ansi[101].css, '#87875f'); + assert.equal(themeService.colors.ansi[102].css, '#878787'); + assert.equal(themeService.colors.ansi[103].css, '#8787af'); + assert.equal(themeService.colors.ansi[104].css, '#8787d7'); + assert.equal(themeService.colors.ansi[105].css, '#8787ff'); + assert.equal(themeService.colors.ansi[106].css, '#87af00'); + assert.equal(themeService.colors.ansi[107].css, '#87af5f'); + assert.equal(themeService.colors.ansi[108].css, '#87af87'); + assert.equal(themeService.colors.ansi[109].css, '#87afaf'); + assert.equal(themeService.colors.ansi[110].css, '#87afd7'); + assert.equal(themeService.colors.ansi[111].css, '#87afff'); + assert.equal(themeService.colors.ansi[112].css, '#87d700'); + assert.equal(themeService.colors.ansi[113].css, '#87d75f'); + assert.equal(themeService.colors.ansi[114].css, '#87d787'); + assert.equal(themeService.colors.ansi[115].css, '#87d7af'); + assert.equal(themeService.colors.ansi[116].css, '#87d7d7'); + assert.equal(themeService.colors.ansi[117].css, '#87d7ff'); + assert.equal(themeService.colors.ansi[118].css, '#87ff00'); + assert.equal(themeService.colors.ansi[119].css, '#87ff5f'); + assert.equal(themeService.colors.ansi[120].css, '#87ff87'); + assert.equal(themeService.colors.ansi[121].css, '#87ffaf'); + assert.equal(themeService.colors.ansi[122].css, '#87ffd7'); + assert.equal(themeService.colors.ansi[123].css, '#87ffff'); + assert.equal(themeService.colors.ansi[124].css, '#af0000'); + assert.equal(themeService.colors.ansi[125].css, '#af005f'); + assert.equal(themeService.colors.ansi[126].css, '#af0087'); + assert.equal(themeService.colors.ansi[127].css, '#af00af'); + assert.equal(themeService.colors.ansi[128].css, '#af00d7'); + assert.equal(themeService.colors.ansi[129].css, '#af00ff'); + assert.equal(themeService.colors.ansi[130].css, '#af5f00'); + assert.equal(themeService.colors.ansi[131].css, '#af5f5f'); + assert.equal(themeService.colors.ansi[132].css, '#af5f87'); + assert.equal(themeService.colors.ansi[133].css, '#af5faf'); + assert.equal(themeService.colors.ansi[134].css, '#af5fd7'); + assert.equal(themeService.colors.ansi[135].css, '#af5fff'); + assert.equal(themeService.colors.ansi[136].css, '#af8700'); + assert.equal(themeService.colors.ansi[137].css, '#af875f'); + assert.equal(themeService.colors.ansi[138].css, '#af8787'); + assert.equal(themeService.colors.ansi[139].css, '#af87af'); + assert.equal(themeService.colors.ansi[140].css, '#af87d7'); + assert.equal(themeService.colors.ansi[141].css, '#af87ff'); + assert.equal(themeService.colors.ansi[142].css, '#afaf00'); + assert.equal(themeService.colors.ansi[143].css, '#afaf5f'); + assert.equal(themeService.colors.ansi[144].css, '#afaf87'); + assert.equal(themeService.colors.ansi[145].css, '#afafaf'); + assert.equal(themeService.colors.ansi[146].css, '#afafd7'); + assert.equal(themeService.colors.ansi[147].css, '#afafff'); + assert.equal(themeService.colors.ansi[148].css, '#afd700'); + assert.equal(themeService.colors.ansi[149].css, '#afd75f'); + assert.equal(themeService.colors.ansi[150].css, '#afd787'); + assert.equal(themeService.colors.ansi[151].css, '#afd7af'); + assert.equal(themeService.colors.ansi[152].css, '#afd7d7'); + assert.equal(themeService.colors.ansi[153].css, '#afd7ff'); + assert.equal(themeService.colors.ansi[154].css, '#afff00'); + assert.equal(themeService.colors.ansi[155].css, '#afff5f'); + assert.equal(themeService.colors.ansi[156].css, '#afff87'); + assert.equal(themeService.colors.ansi[157].css, '#afffaf'); + assert.equal(themeService.colors.ansi[158].css, '#afffd7'); + assert.equal(themeService.colors.ansi[159].css, '#afffff'); + assert.equal(themeService.colors.ansi[160].css, '#d70000'); + assert.equal(themeService.colors.ansi[161].css, '#d7005f'); + assert.equal(themeService.colors.ansi[162].css, '#d70087'); + assert.equal(themeService.colors.ansi[163].css, '#d700af'); + assert.equal(themeService.colors.ansi[164].css, '#d700d7'); + assert.equal(themeService.colors.ansi[165].css, '#d700ff'); + assert.equal(themeService.colors.ansi[166].css, '#d75f00'); + assert.equal(themeService.colors.ansi[167].css, '#d75f5f'); + assert.equal(themeService.colors.ansi[168].css, '#d75f87'); + assert.equal(themeService.colors.ansi[169].css, '#d75faf'); + assert.equal(themeService.colors.ansi[170].css, '#d75fd7'); + assert.equal(themeService.colors.ansi[171].css, '#d75fff'); + assert.equal(themeService.colors.ansi[172].css, '#d78700'); + assert.equal(themeService.colors.ansi[173].css, '#d7875f'); + assert.equal(themeService.colors.ansi[174].css, '#d78787'); + assert.equal(themeService.colors.ansi[175].css, '#d787af'); + assert.equal(themeService.colors.ansi[176].css, '#d787d7'); + assert.equal(themeService.colors.ansi[177].css, '#d787ff'); + assert.equal(themeService.colors.ansi[178].css, '#d7af00'); + assert.equal(themeService.colors.ansi[179].css, '#d7af5f'); + assert.equal(themeService.colors.ansi[180].css, '#d7af87'); + assert.equal(themeService.colors.ansi[181].css, '#d7afaf'); + assert.equal(themeService.colors.ansi[182].css, '#d7afd7'); + assert.equal(themeService.colors.ansi[183].css, '#d7afff'); + assert.equal(themeService.colors.ansi[184].css, '#d7d700'); + assert.equal(themeService.colors.ansi[185].css, '#d7d75f'); + assert.equal(themeService.colors.ansi[186].css, '#d7d787'); + assert.equal(themeService.colors.ansi[187].css, '#d7d7af'); + assert.equal(themeService.colors.ansi[188].css, '#d7d7d7'); + assert.equal(themeService.colors.ansi[189].css, '#d7d7ff'); + assert.equal(themeService.colors.ansi[190].css, '#d7ff00'); + assert.equal(themeService.colors.ansi[191].css, '#d7ff5f'); + assert.equal(themeService.colors.ansi[192].css, '#d7ff87'); + assert.equal(themeService.colors.ansi[193].css, '#d7ffaf'); + assert.equal(themeService.colors.ansi[194].css, '#d7ffd7'); + assert.equal(themeService.colors.ansi[195].css, '#d7ffff'); + assert.equal(themeService.colors.ansi[196].css, '#ff0000'); + assert.equal(themeService.colors.ansi[197].css, '#ff005f'); + assert.equal(themeService.colors.ansi[198].css, '#ff0087'); + assert.equal(themeService.colors.ansi[199].css, '#ff00af'); + assert.equal(themeService.colors.ansi[200].css, '#ff00d7'); + assert.equal(themeService.colors.ansi[201].css, '#ff00ff'); + assert.equal(themeService.colors.ansi[202].css, '#ff5f00'); + assert.equal(themeService.colors.ansi[203].css, '#ff5f5f'); + assert.equal(themeService.colors.ansi[204].css, '#ff5f87'); + assert.equal(themeService.colors.ansi[205].css, '#ff5faf'); + assert.equal(themeService.colors.ansi[206].css, '#ff5fd7'); + assert.equal(themeService.colors.ansi[207].css, '#ff5fff'); + assert.equal(themeService.colors.ansi[208].css, '#ff8700'); + assert.equal(themeService.colors.ansi[209].css, '#ff875f'); + assert.equal(themeService.colors.ansi[210].css, '#ff8787'); + assert.equal(themeService.colors.ansi[211].css, '#ff87af'); + assert.equal(themeService.colors.ansi[212].css, '#ff87d7'); + assert.equal(themeService.colors.ansi[213].css, '#ff87ff'); + assert.equal(themeService.colors.ansi[214].css, '#ffaf00'); + assert.equal(themeService.colors.ansi[215].css, '#ffaf5f'); + assert.equal(themeService.colors.ansi[216].css, '#ffaf87'); + assert.equal(themeService.colors.ansi[217].css, '#ffafaf'); + assert.equal(themeService.colors.ansi[218].css, '#ffafd7'); + assert.equal(themeService.colors.ansi[219].css, '#ffafff'); + assert.equal(themeService.colors.ansi[220].css, '#ffd700'); + assert.equal(themeService.colors.ansi[221].css, '#ffd75f'); + assert.equal(themeService.colors.ansi[222].css, '#ffd787'); + assert.equal(themeService.colors.ansi[223].css, '#ffd7af'); + assert.equal(themeService.colors.ansi[224].css, '#ffd7d7'); + assert.equal(themeService.colors.ansi[225].css, '#ffd7ff'); + assert.equal(themeService.colors.ansi[226].css, '#ffff00'); + assert.equal(themeService.colors.ansi[227].css, '#ffff5f'); + assert.equal(themeService.colors.ansi[228].css, '#ffff87'); + assert.equal(themeService.colors.ansi[229].css, '#ffffaf'); + assert.equal(themeService.colors.ansi[230].css, '#ffffd7'); + assert.equal(themeService.colors.ansi[231].css, '#ffffff'); + assert.equal(themeService.colors.ansi[232].css, '#080808'); + assert.equal(themeService.colors.ansi[233].css, '#121212'); + assert.equal(themeService.colors.ansi[234].css, '#1c1c1c'); + assert.equal(themeService.colors.ansi[235].css, '#262626'); + assert.equal(themeService.colors.ansi[236].css, '#303030'); + assert.equal(themeService.colors.ansi[237].css, '#3a3a3a'); + assert.equal(themeService.colors.ansi[238].css, '#444444'); + assert.equal(themeService.colors.ansi[239].css, '#4e4e4e'); + assert.equal(themeService.colors.ansi[240].css, '#585858'); + assert.equal(themeService.colors.ansi[241].css, '#626262'); + assert.equal(themeService.colors.ansi[242].css, '#6c6c6c'); + assert.equal(themeService.colors.ansi[243].css, '#767676'); + assert.equal(themeService.colors.ansi[244].css, '#808080'); + assert.equal(themeService.colors.ansi[245].css, '#8a8a8a'); + assert.equal(themeService.colors.ansi[246].css, '#949494'); + assert.equal(themeService.colors.ansi[247].css, '#9e9e9e'); + assert.equal(themeService.colors.ansi[248].css, '#a8a8a8'); + assert.equal(themeService.colors.ansi[249].css, '#b2b2b2'); + assert.equal(themeService.colors.ansi[250].css, '#bcbcbc'); + assert.equal(themeService.colors.ansi[251].css, '#c6c6c6'); + assert.equal(themeService.colors.ansi[252].css, '#d0d0d0'); + assert.equal(themeService.colors.ansi[253].css, '#dadada'); + assert.equal(themeService.colors.ansi[254].css, '#e4e4e4'); + assert.equal(themeService.colors.ansi[255].css, '#eeeeee'); + }); + }); + + describe('setTheme', () => { + it('should not throw when not setting all colors', () => { + assert.doesNotThrow(() => { + optionsService.options.theme = {}; + }); + }); + + it('should set a partial set of colors, using the default if not present', () => { + assert.equal(themeService.colors.background.css, '#000000'); + assert.equal(themeService.colors.foreground.css, '#ffffff'); + optionsService.options.theme = { + background: '#FF0000', + foreground: '#00FF00' + }; + assert.equal(themeService.colors.background.css, '#FF0000'); + assert.equal(themeService.colors.foreground.css, '#00FF00'); + optionsService.options.theme = { + background: '#0000FF' + }; + assert.equal(themeService.colors.background.css, '#0000FF'); + // FG reverts back to default + assert.equal(themeService.colors.foreground.css, '#ffffff'); + }); + + it('should set all extended ansi colors in reverse order', () => { + optionsService.options.theme = { + extendedAnsi: DEFAULT_ANSI_COLORS.map(a => a.css).slice().reverse() + }; + + for (let ansiColor = 16; ansiColor <= 255; ansiColor++) { + assert.equal(themeService.colors.ansi[ansiColor].css, DEFAULT_ANSI_COLORS[255 + 16 - ansiColor].css); + } + }); + + it('should set one extended ansi color and keep the other default', () => { + optionsService.options.theme = { + extendedAnsi: ['#ffffff'] + }; + + assert.equal(themeService.colors.ansi[16].css, '#ffffff'); + assert.equal(themeService.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css); + }); + + it('should set extended ansi colors to the default when they are unset', () => { + optionsService.options.theme = { + extendedAnsi: ['#ffffff'] + }; + assert.equal(themeService.colors.ansi[16].css, '#ffffff'); + + optionsService.options.theme = { + extendedAnsi: [] + }; + assert.equal(themeService.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css); + + optionsService.options.theme = { + extendedAnsi: ['#ffffff'] + }; + assert.equal(themeService.colors.ansi[16].css, '#ffffff'); + + optionsService.options.theme = {}; + assert.equal(themeService.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css); + }); + + it('should set extended ansi colors to the default when they are partially unset', () => { + optionsService.options.theme = { + extendedAnsi: ['#ffffff', '#000000'] + }; + assert.equal(themeService.colors.ansi[16].css, '#ffffff'); + assert.equal(themeService.colors.ansi[17].css, '#000000'); + + optionsService.options.theme = { + extendedAnsi: ['#ffffff'] + }; + assert.equal(themeService.colors.ansi[16].css, '#ffffff'); + assert.equal(themeService.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css); + }); + }); +}); diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts index 7765eca6..e584a8d1 100644 --- a/src/browser/services/ThemeService.ts +++ b/src/browser/services/ThemeService.ts @@ -3,21 +3,88 @@ * @license MIT */ -import { ColorManager } from 'browser/ColorManager'; +import { ColorContrastCache } from 'browser/ColorContrastCache'; import { IThemeService } from 'browser/services/Services'; -import { IColorSet, ReadonlyColorSet } from 'browser/Types'; +import { IColorContrastCache, IColorSet, ReadonlyColorSet } from 'browser/Types'; +import { channels, color, css, NULL_COLOR } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IOptionsService, ITheme } from 'common/services/Services'; -import { ColorIndex } from 'common/Types'; +import { ColorIndex, IColor } from 'common/Types'; + +interface IRestoreColorSet { + foreground: IColor; + background: IColor; + cursor: IColor; + ansi: IColor[]; +} + + +const DEFAULT_FOREGROUND = css.toColor('#ffffff'); +const DEFAULT_BACKGROUND = css.toColor('#000000'); +const DEFAULT_CURSOR = css.toColor('#ffffff'); +const DEFAULT_CURSOR_ACCENT = css.toColor('#000000'); +const DEFAULT_SELECTION = { + css: 'rgba(255, 255, 255, 0.3)', + rgba: 0xFFFFFF4D +}; + +// An IIFE to generate DEFAULT_ANSI_COLORS. +export const DEFAULT_ANSI_COLORS = Object.freeze((() => { + const colors = [ + // dark: + css.toColor('#2e3436'), + css.toColor('#cc0000'), + css.toColor('#4e9a06'), + css.toColor('#c4a000'), + css.toColor('#3465a4'), + css.toColor('#75507b'), + css.toColor('#06989a'), + css.toColor('#d3d7cf'), + // bright: + css.toColor('#555753'), + css.toColor('#ef2929'), + css.toColor('#8ae234'), + css.toColor('#fce94f'), + css.toColor('#729fcf'), + css.toColor('#ad7fa8'), + css.toColor('#34e2e2'), + css.toColor('#eeeeec') + ]; + + // Fill in the remaining 240 ANSI colors. + // Generate colors (16-231) + const v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; + for (let i = 0; i < 216; i++) { + const r = v[(i / 36) % 6 | 0]; + const g = v[(i / 6) % 6 | 0]; + const b = v[i % 6]; + colors.push({ + css: channels.toCss(r, g, b), + rgba: channels.toRgba(r, g, b) + }); + } + + // Generate greys (232-255) + for (let i = 0; i < 24; i++) { + const c = 8 + i * 10; + colors.push({ + css: channels.toCss(c, c, c), + rgba: channels.toRgba(c, c, c) + }); + } + + return colors; +})()); export class ThemeService extends Disposable implements IThemeService { public serviceBrand: undefined; - // TODO: Merge color manager into theme service - private _colorManager = new ColorManager(); + private _colors: IColorSet; + private _contrastCache: IColorContrastCache; + private _restoreColors!: IRestoreColorSet; - public get colors(): ReadonlyColorSet { return this._colorManager.colors; } + public get colors(): ReadonlyColorSet { return this._colors; } private readonly _onChangeColors = this.register(new EventEmitter()); public readonly onChangeColors = this._onChangeColors.event; @@ -26,28 +93,151 @@ export class ThemeService extends Disposable implements IThemeService { @IOptionsService private readonly _optionsService: IOptionsService ) { super(); + + this._contrastCache = new ColorContrastCache(); + this._colors = { + foreground: DEFAULT_FOREGROUND, + background: DEFAULT_BACKGROUND, + cursor: DEFAULT_CURSOR, + cursorAccent: DEFAULT_CURSOR_ACCENT, + selectionForeground: undefined, + selectionBackgroundTransparent: DEFAULT_SELECTION, + selectionBackgroundOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), + selectionInactiveBackgroundTransparent: DEFAULT_SELECTION, + selectionInactiveBackgroundOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), + ansi: DEFAULT_ANSI_COLORS.slice(), + contrastCache: this._contrastCache + }; + this._updateRestoreColors(); + this._setTheme(this._optionsService.rawOptions.theme); + this.register(this._optionsService.onOptionChange(key => { - if (key === 'theme') { - this._setTheme(this._optionsService.rawOptions.theme); + switch (key) { + case 'minimumContrastRatio': + this._contrastCache.clear(); + break; + case 'theme': + this._setTheme(this._optionsService.rawOptions.theme); + break; } - this._colorManager.handleOptionsChange(key, this._optionsService.rawOptions[key]); })); - this._colorManager.setTheme(this._optionsService.rawOptions.theme); } + /** + * Sets the terminal's theme. + * @param theme The theme to use. If a partial theme is provided then default + * colors will be used where colors are not defined. + */ private _setTheme(theme: ITheme = {}): void { - this._colorManager.setTheme(theme); + const colors = this._colors; + colors.foreground = parseColor(theme.foreground, DEFAULT_FOREGROUND); + colors.background = parseColor(theme.background, DEFAULT_BACKGROUND); + colors.cursor = parseColor(theme.cursor, DEFAULT_CURSOR); + colors.cursorAccent = parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); + colors.selectionBackgroundTransparent = parseColor(theme.selectionBackground, DEFAULT_SELECTION); + colors.selectionBackgroundOpaque = color.blend(colors.background, colors.selectionBackgroundTransparent); + colors.selectionInactiveBackgroundTransparent = parseColor(theme.selectionInactiveBackground, colors.selectionBackgroundTransparent); + colors.selectionInactiveBackgroundOpaque = color.blend(colors.background, colors.selectionInactiveBackgroundTransparent); + colors.selectionForeground = theme.selectionForeground ? parseColor(theme.selectionForeground, NULL_COLOR) : undefined; + if (colors.selectionForeground === NULL_COLOR) { + colors.selectionForeground = undefined; + } + + /** + * If selection color is opaque, blend it with background with 0.3 opacity + * Issue #2737 + */ + if (color.isOpaque(colors.selectionBackgroundTransparent)) { + const opacity = 0.3; + colors.selectionBackgroundTransparent = color.opacity(colors.selectionBackgroundTransparent, opacity); + } + if (color.isOpaque(colors.selectionInactiveBackgroundTransparent)) { + const opacity = 0.3; + colors.selectionInactiveBackgroundTransparent = color.opacity(colors.selectionInactiveBackgroundTransparent, opacity); + } + colors.ansi = DEFAULT_ANSI_COLORS.slice(); + colors.ansi[0] = parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); + colors.ansi[1] = parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); + colors.ansi[2] = parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); + colors.ansi[3] = parseColor(theme.yellow, DEFAULT_ANSI_COLORS[3]); + colors.ansi[4] = parseColor(theme.blue, DEFAULT_ANSI_COLORS[4]); + colors.ansi[5] = parseColor(theme.magenta, DEFAULT_ANSI_COLORS[5]); + colors.ansi[6] = parseColor(theme.cyan, DEFAULT_ANSI_COLORS[6]); + colors.ansi[7] = parseColor(theme.white, DEFAULT_ANSI_COLORS[7]); + colors.ansi[8] = parseColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]); + colors.ansi[9] = parseColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]); + colors.ansi[10] = parseColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]); + colors.ansi[11] = parseColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]); + colors.ansi[12] = parseColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]); + colors.ansi[13] = parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]); + colors.ansi[14] = parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); + colors.ansi[15] = parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); + if (theme.extendedAnsi) { + const colorCount = Math.min(colors.ansi.length - 16, theme.extendedAnsi.length); + for (let i = 0; i < colorCount; i++) { + colors.ansi[i + 16] = parseColor(theme.extendedAnsi[i], DEFAULT_ANSI_COLORS[i + 16]); + } + } + // Clear our the cache + this._contrastCache.clear(); + this._updateRestoreColors(); this._onChangeColors.fire(this.colors); } public restoreColor(slot?: ColorIndex): void { - this._colorManager.restoreColor(slot); + this._restoreColor(slot); this._onChangeColors.fire(this.colors); } + private _restoreColor(slot: ColorIndex | undefined): void { + // unset slot restores all ansi colors + if (slot === undefined) { + for (let i = 0; i < this._restoreColors.ansi.length; ++i) { + this._colors.ansi[i] = this._restoreColors.ansi[i]; + } + return; + } + switch (slot) { + case ColorIndex.FOREGROUND: + this._colors.foreground = this._restoreColors.foreground; + break; + case ColorIndex.BACKGROUND: + this._colors.background = this._restoreColors.background; + break; + case ColorIndex.CURSOR: + this._colors.cursor = this._restoreColors.cursor; + break; + default: + this._colors.ansi[slot] = this._restoreColors.ansi[slot]; + } + } + public modifyColors(callback: (colors: IColorSet) => void): void { - callback(this._colorManager.colors); + callback(this._colors); // Assume the change happened this._onChangeColors.fire(this.colors); } + + private _updateRestoreColors(): void { + this._restoreColors = { + foreground: this._colors.foreground, + background: this._colors.background, + cursor: this._colors.cursor, + ansi: this._colors.ansi.slice() + }; + } +} + +function parseColor( + cssString: string | undefined, + fallback: IColor +): IColor { + if (cssString !== undefined) { + try { + return css.toColor(cssString); + } catch { + // no-op + } + } + return fallback; } From e0fb3b4d199f51a88c94b080636c7034cb979aa8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 15:23:29 -0700 Subject: [PATCH 72/87] Remove ColorManager --- .../src/SerializeAddon.test.ts | 13 +- .../src/SerializeAddon.ts | 2 +- src/browser/ColorManager.ts | 217 ------------------ src/browser/TestUtils.test.ts | 3 +- src/browser/Types.d.ts | 5 - src/browser/services/ThemeService.test.ts | 3 +- 6 files changed, 7 insertions(+), 236 deletions(-) delete mode 100644 src/browser/ColorManager.ts diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts index b47c7928..205927f6 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts @@ -7,9 +7,10 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { SerializeAddon } from './SerializeAddon'; import { Terminal } from 'browser/public/Terminal'; -import { ColorManager } from 'browser/ColorManager'; import { SelectionModel } from 'browser/selection/SelectionModel'; import { IBufferService } from 'common/services/Services'; +import { OptionsService } from 'common/services/OptionsService'; +import { ThemeService } from 'browser/services/ThemeService'; function sgr(...seq: string[]): string { return `\x1b[${seq.join(';')}m`; @@ -44,14 +45,11 @@ class TestSelectionService { } describe('xterm-addon-serialize', () => { - let cm: ColorManager; let dom: jsdom.JSDOM; - let document: Document; let window: jsdom.DOMWindow; let serializeAddon: SerializeAddon; let terminal: Terminal; - let selectionService: any; before(() => { serializeAddon = new SerializeAddon(); @@ -77,11 +75,8 @@ describe('xterm-addon-serialize', () => { terminal = new Terminal({ cols: 10, rows: 2, allowProposedApi: true }); terminal.loadAddon(serializeAddon); - // TODO: Fix color manager reference - selectionService = new TestSelectionService((terminal as any)._core._bufferService); - cm = new ColorManager(); - (terminal as any)._core._colorManager = cm; - (terminal as any)._core._selectionService = selectionService; + (terminal as any)._core._themeService = new ThemeService(new OptionsService({})); + (terminal as any)._core._selectionService = new TestSelectionService((terminal as any)._core._bufferService); }); describe('text', () => { diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index 99967374..b83ff160 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -544,7 +544,7 @@ export class HTMLSerializeHandler extends BaseSerializeHandler { super(buffer); // https://github.com/xtermjs/xterm.js/issues/3601 - this._colors = (_terminal as any)._core._colorManager.colors; + this._colors = (_terminal as any)._core._themeService.colors; } private _padStart(target: string, targetLength: number, padString: string): string { diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts deleted file mode 100644 index de5c828c..00000000 --- a/src/browser/ColorManager.ts +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IColorManager, IColorSet, IColorContrastCache } from 'browser/Types'; -import { ITheme } from 'common/services/Services'; -import { channels, color, css, NULL_COLOR } from 'common/Color'; -import { ColorContrastCache } from 'browser/ColorContrastCache'; -import { ColorIndex, IColor } from 'common/Types'; - - -interface IRestoreColorSet { - foreground: IColor; - background: IColor; - cursor: IColor; - ansi: IColor[]; -} - - -const DEFAULT_FOREGROUND = css.toColor('#ffffff'); -const DEFAULT_BACKGROUND = css.toColor('#000000'); -const DEFAULT_CURSOR = css.toColor('#ffffff'); -const DEFAULT_CURSOR_ACCENT = css.toColor('#000000'); -const DEFAULT_SELECTION = { - css: 'rgba(255, 255, 255, 0.3)', - rgba: 0xFFFFFF4D -}; - -// An IIFE to generate DEFAULT_ANSI_COLORS. -export const DEFAULT_ANSI_COLORS = Object.freeze((() => { - const colors = [ - // dark: - css.toColor('#2e3436'), - css.toColor('#cc0000'), - css.toColor('#4e9a06'), - css.toColor('#c4a000'), - css.toColor('#3465a4'), - css.toColor('#75507b'), - css.toColor('#06989a'), - css.toColor('#d3d7cf'), - // bright: - css.toColor('#555753'), - css.toColor('#ef2929'), - css.toColor('#8ae234'), - css.toColor('#fce94f'), - css.toColor('#729fcf'), - css.toColor('#ad7fa8'), - css.toColor('#34e2e2'), - css.toColor('#eeeeec') - ]; - - // Fill in the remaining 240 ANSI colors. - // Generate colors (16-231) - const v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; - for (let i = 0; i < 216; i++) { - const r = v[(i / 36) % 6 | 0]; - const g = v[(i / 6) % 6 | 0]; - const b = v[i % 6]; - colors.push({ - css: channels.toCss(r, g, b), - rgba: channels.toRgba(r, g, b) - }); - } - - // Generate greys (232-255) - for (let i = 0; i < 24; i++) { - const c = 8 + i * 10; - colors.push({ - css: channels.toCss(c, c, c), - rgba: channels.toRgba(c, c, c) - }); - } - - return colors; -})()); - -/** - * Manages the source of truth for a terminal's colors. - */ -export class ColorManager implements IColorManager { - public colors: IColorSet; - - private _contrastCache: IColorContrastCache; - private _restoreColors!: IRestoreColorSet; - - constructor() { - this._contrastCache = new ColorContrastCache(); - this.colors = { - foreground: DEFAULT_FOREGROUND, - background: DEFAULT_BACKGROUND, - cursor: DEFAULT_CURSOR, - cursorAccent: DEFAULT_CURSOR_ACCENT, - selectionForeground: undefined, - selectionBackgroundTransparent: DEFAULT_SELECTION, - selectionBackgroundOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), - selectionInactiveBackgroundTransparent: DEFAULT_SELECTION, - selectionInactiveBackgroundOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), - ansi: DEFAULT_ANSI_COLORS.slice(), - contrastCache: this._contrastCache - }; - this._updateRestoreColors(); - } - - public handleOptionsChange(key: string, value: any): void { - switch (key) { - case 'minimumContrastRatio': - this._contrastCache.clear(); - break; - } - } - - /** - * Sets the terminal's theme. - * @param theme The theme to use. If a partial theme is provided then default - * colors will be used where colors are not defined. - */ - public setTheme(theme: ITheme = {}): void { - this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND); - this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); - this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR); - this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); - this.colors.selectionBackgroundTransparent = this._parseColor(theme.selectionBackground, DEFAULT_SELECTION); - 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); - this.colors.selectionForeground = theme.selectionForeground ? this._parseColor(theme.selectionForeground, NULL_COLOR) : undefined; - if (this.colors.selectionForeground === NULL_COLOR) { - this.colors.selectionForeground = undefined; - } - - /** - * If selection color is opaque, blend it with background with 0.3 opacity - * Issue #2737 - */ - if (color.isOpaque(this.colors.selectionBackgroundTransparent)) { - const opacity = 0.3; - this.colors.selectionBackgroundTransparent = color.opacity(this.colors.selectionBackgroundTransparent, opacity); - } - if (color.isOpaque(this.colors.selectionInactiveBackgroundTransparent)) { - const opacity = 0.3; - this.colors.selectionInactiveBackgroundTransparent = color.opacity(this.colors.selectionInactiveBackgroundTransparent, opacity); - } - this.colors.ansi = DEFAULT_ANSI_COLORS.slice(); - this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); - this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); - this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); - this.colors.ansi[3] = this._parseColor(theme.yellow, DEFAULT_ANSI_COLORS[3]); - this.colors.ansi[4] = this._parseColor(theme.blue, DEFAULT_ANSI_COLORS[4]); - this.colors.ansi[5] = this._parseColor(theme.magenta, DEFAULT_ANSI_COLORS[5]); - this.colors.ansi[6] = this._parseColor(theme.cyan, DEFAULT_ANSI_COLORS[6]); - this.colors.ansi[7] = this._parseColor(theme.white, DEFAULT_ANSI_COLORS[7]); - this.colors.ansi[8] = this._parseColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]); - this.colors.ansi[9] = this._parseColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]); - this.colors.ansi[10] = this._parseColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]); - this.colors.ansi[11] = this._parseColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]); - this.colors.ansi[12] = this._parseColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]); - this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]); - this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); - this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); - if (theme.extendedAnsi) { - const colorCount = Math.min(this.colors.ansi.length - 16, theme.extendedAnsi.length); - for (let i = 0; i < colorCount; i++) { - this.colors.ansi[i + 16] = this._parseColor(theme.extendedAnsi[i], DEFAULT_ANSI_COLORS[i + 16]); - } - } - // Clear our the cache - this._contrastCache.clear(); - this._updateRestoreColors(); - } - - public restoreColor(slot?: ColorIndex): void { - // unset slot restores all ansi colors - if (slot === undefined) { - for (let i = 0; i < this._restoreColors.ansi.length; ++i) { - this.colors.ansi[i] = this._restoreColors.ansi[i]; - } - return; - } - switch (slot) { - case ColorIndex.FOREGROUND: - this.colors.foreground = this._restoreColors.foreground; - break; - case ColorIndex.BACKGROUND: - this.colors.background = this._restoreColors.background; - break; - case ColorIndex.CURSOR: - this.colors.cursor = this._restoreColors.cursor; - break; - default: - this.colors.ansi[slot] = this._restoreColors.ansi[slot]; - } - } - - private _updateRestoreColors(): void { - this._restoreColors = { - foreground: this.colors.foreground, - background: this.colors.background, - cursor: this.colors.cursor, - ansi: this.colors.ansi.slice() - }; - } - - private _parseColor( - cssString: string | undefined, - fallback: IColor - ): IColor { - if (cssString !== undefined) { - try { - return css.toColor(cssString); - } catch { - // no-op - } - } - return fallback; - } -} diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index a0837895..97ad90d8 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; -import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet } from 'browser/Types'; +import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions, ColorIndex } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -266,7 +266,6 @@ export class MockRenderer implements IRenderer { public dispose(): void { throw new Error('Method not implemented.'); } - public colorManager!: IColorManager; public on(type: string, listener: XtermListener): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index da1f358f..cfd7156f 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -106,11 +106,6 @@ export interface IBrowser { isWindows: boolean; } -export interface IColorManager { - colors: IColorSet; - handleOptionsChange(key: string, value: any): void; -} - export interface IColorSet { foreground: IColor; background: IColor; diff --git a/src/browser/services/ThemeService.test.ts b/src/browser/services/ThemeService.test.ts index bebd0f56..f2b2def3 100644 --- a/src/browser/services/ThemeService.test.ts +++ b/src/browser/services/ThemeService.test.ts @@ -6,10 +6,9 @@ import jsdom = require('jsdom'); import { assert } from 'chai'; import { ThemeService, DEFAULT_ANSI_COLORS } from 'browser/services/ThemeService'; -import { MockOptionsService } from 'common/TestUtils.test'; import { OptionsService } from 'common/services/OptionsService'; -describe.only('ThemeService', () => { +describe('ThemeService', () => { let themeService: ThemeService; let dom: jsdom.JSDOM; let window: jsdom.DOMWindow; From 97e084aa50704e8b8c6fef5eec3d8a309cd894b1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 8 Oct 2022 15:44:21 -0700 Subject: [PATCH 73/87] Remove document from test --- addons/xterm-addon-serialize/src/SerializeAddon.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts index 205927f6..4c5dd645 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts @@ -58,7 +58,6 @@ describe('xterm-addon-serialize', () => { beforeEach(() => { dom = new jsdom.JSDOM(''); window = dom.window; - document = window.document; (window as any).HTMLCanvasElement.prototype.getContext = () => ({ createLinearGradient(): any { From f983b3cac19aca040568144f3d439db0615f4e5a Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sun, 9 Oct 2022 10:15:13 +0000 Subject: [PATCH 74/87] Ubuntu 18.04 deprecation --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1710922e..7b015064 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,7 +9,7 @@ trigger: jobs: - job: Linux pool: - vmImage: 'ubuntu-18.04' + vmImage: 'ubuntu-20.04' steps: - task: NodeTool@0 inputs: From bd661fdb071db5adb132722f1f946c32c0320ffe Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sun, 9 Oct 2022 11:29:33 +0000 Subject: [PATCH 75/87] Fix demo again --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 4 +++- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +++- demo/client.ts | 15 ++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 1dc607e5..dcd35704 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -25,7 +25,9 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { public activate(terminal: Terminal): void { const core = (terminal as any)._core; if (!terminal.element) { - this.register(core.onWillOpen(() => this.activate(terminal))); + this.register(toDisposable(() => { + core.onWillOpen(() => this.activate(terminal)); + })); return; } diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 71487315..538713c2 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -33,7 +33,9 @@ export class WebglAddon extends Disposable implements ITerminalAddon { } const core = (terminal as any)._core; if (!terminal.element) { - this.register(core.onWillOpen(() => this.activate(terminal))); + this.register(toDisposable(() => { + core.onWillOpen(() => this.activate(terminal)); + })); return; } this._terminal = terminal; diff --git a/demo/client.ts b/demo/client.ts index ea55d6d9..3228bd7f 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -271,11 +271,16 @@ function createTerminal(): void { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; addons.fit.instance!.fit(); - typedTerm.loadAddon(addons.webgl.instance); - setTimeout(() => { - addTextureAtlas(addons.webgl.instance.textureAtlas); - addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); - }, 0); + try { // try-catch to allow the demo to load if webgl is not supported + typedTerm.loadAddon(addons.webgl.instance); + setTimeout(() => { + addTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + }, 0); + } + catch { + addons.webgl.instance = undefined; + } term.open(terminalContainer); term.focus(); From a87b6c3f2e056c66969eb185b1c21465a1abb8ea Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 07:09:35 -0700 Subject: [PATCH 76/87] Implement IOptionsService.onSpecificOptionChange Part of #4190 --- src/common/CoreTerminal.ts | 2 +- src/common/TestUtils.test.ts | 12 ++++++++++-- src/common/services/OptionsService.ts | 13 +++++++++++-- src/common/services/Services.ts | 18 +++++++++++++++++- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index ea2528db..5f42d8ea 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -261,7 +261,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.coreMouseService.reset(); } - protected _updateOptions(key: string): void { + protected _updateOptions(key: keyof ITerminalOptions): void { // TODO: These listeners should be owned by individual components switch (key) { case 'scrollback': diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index e302d3e4..050b1cec 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -9,7 +9,7 @@ import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData, IDisposable } from 'common/Types'; import { UnicodeV6 } from 'common/input/UnicodeV6'; import { IDecorationOptions, IDecoration } from 'xterm'; @@ -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 = new EventEmitter().event; constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { @@ -121,6 +121,14 @@ export class MockOptionsService implements IOptionsService { } } } + // eslint-disable-next-line @typescript-eslint/naming-convention + public onSpecificOptionChange(key: T, listener: (arg1: ITerminalOptions[T]) => any): IDisposable { + return this.onOptionChange(eventKey => { + if (eventKey === key) { + listener(this.rawOptions[key]); + } + }); + } public setOptions(options: ITerminalOptions): void { for (const key of Object.keys(options)) { this.options[key] = options[key]; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 336591e5..439d6a77 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -6,7 +6,7 @@ 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 { CursorStyle, IDisposable } from 'common/Types'; import { Disposable } from 'common/Lifecycle'; export const DEFAULT_OPTIONS: Readonly> = { @@ -58,7 +58,7 @@ export class OptionsService extends Disposable implements IOptionsService { public readonly rawOptions: Required; public options: Required; - private readonly _onOptionChange = this.register(new EventEmitter()); + private readonly _onOptionChange = this.register(new EventEmitter()); public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { @@ -82,6 +82,15 @@ export class OptionsService extends Disposable implements IOptionsService { this._setupOptions(); } + // eslint-disable-next-line @typescript-eslint/naming-convention + public onSpecificOptionChange(key: T, listener: (value: ITerminalOptions[T]) => any): IDisposable { + return this.onOptionChange(eventKey => { + if (eventKey === key) { + listener(this.rawOptions[key]); + } + }); + } + private _setupOptions(): void { const getter = (propName: string): any => { if (!(propName in DEFAULT_OPTIONS)) { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e2b517cd..9b591962 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -182,9 +182,25 @@ export interface IOptionsService { * internally. */ readonly rawOptions: Required; + + /** + * Options as exposed through the public API, this property uses getters and setters with + * validation which makes it safer but slower. {@link rawOptions} should be used for pretty much + * all internal usage for performance reasons. + */ readonly options: Required; - readonly onOptionChange: IEvent; + /** + * Adds an event listener for when any option changes. + */ + readonly onOptionChange: IEvent; + + /** + * Adds an event listener for when a specific option changes, this is a convenience method that is + * preferred over {@link onOptionChange} when only a single option is being listened to. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + onSpecificOptionChange(key: T, listener: (arg1: ITerminalOptions[T]) => any): IDisposable; } export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number; From cad9c4164cac2433b7e9b99f34c6693264e4ee31 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 07:16:56 -0700 Subject: [PATCH 77/87] onOptionChange/onSpecificOptionChange tests --- src/common/services/OptionsService.test.ts | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/common/services/OptionsService.test.ts b/src/common/services/OptionsService.test.ts index 3ff22ac0..a0351516 100644 --- a/src/common/services/OptionsService.test.ts +++ b/src/common/services/OptionsService.test.ts @@ -5,6 +5,7 @@ import { assert } from 'chai'; import { OptionsService, DEFAULT_OPTIONS } from 'common/services/OptionsService'; +import { IDisposable } from 'common/Types'; describe('OptionsService', () => { describe('constructor', () => { @@ -71,4 +72,44 @@ describe('OptionsService', () => { assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Wrong string literals should be reset to default'); }); }); + describe('onOptionChange', () => { + let service: OptionsService; + beforeEach(() => { + service = new OptionsService({}); + }); + it('should fire on any option change', async () => { + let disposable: IDisposable; + await new Promise(r => { + disposable = service.onOptionChange(e => { + assert.strictEqual(e, 'cursorWidth'); + r(); + }); + service.options.cursorWidth = 10; + }); + disposable!.dispose(); + await new Promise(r => { + service.onOptionChange(e => { + assert.strictEqual(e, 'scrollback'); + r(); + }); + service.options.scrollback = 20; + }); + }); + }); + describe('onSpecificOptionChange', () => { + let service: OptionsService; + beforeEach(() => { + service = new OptionsService({}); + }); + it('should fire only on a specific option change', async () => { + await new Promise(r => { + service.onSpecificOptionChange('scrollback', e => { + assert.strictEqual(e, 20); + r(); + }); + service.options.cursorWidth = 10; + service.options.scrollback = 20; + }); + }); + }); }); From 627410a53743a7fe420f99b7db7b48eef0b654eb Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sun, 9 Oct 2022 14:26:08 +0000 Subject: [PATCH 78/87] Add broken demo to investigate --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 4 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 ++ demo/client.ts | 48 ++++++++++--------- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index dcd35704..1dc607e5 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -25,9 +25,7 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { public activate(terminal: Terminal): void { const core = (terminal as any)._core; if (!terminal.element) { - this.register(toDisposable(() => { - core.onWillOpen(() => this.activate(terminal)); - })); + this.register(core.onWillOpen(() => this.activate(terminal))); return; } diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 538713c2..71487315 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -33,9 +33,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon { } const core = (terminal as any)._core; if (!terminal.element) { - this.register(toDisposable(() => { - core.onWillOpen(() => this.activate(terminal)); - })); + this.register(core.onWillOpen(() => this.activate(terminal))); return; } this._terminal = terminal; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index ed99a225..8f747c03 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -103,6 +103,9 @@ export class WebglRenderer extends Disposable implements IRenderer { throw new Error('WebGL2 not supported ' + this._gl); } + // TODO: Remove me. + throw new Error('WebGL2 not supported ' + this._gl); + this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { console.log('webglcontextlost event received'); // Prevent the default behavior in order to enable WebGL context restoration. diff --git a/demo/client.ts b/demo/client.ts index 3228bd7f..6d48b302 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -61,26 +61,26 @@ interface IDemoAddon { canChange: boolean; 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 + 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 ); } @@ -271,18 +271,20 @@ function createTerminal(): void { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; addons.fit.instance!.fit(); - try { // try-catch to allow the demo to load if webgl is not supported - typedTerm.loadAddon(addons.webgl.instance); - setTimeout(() => { + typedTerm.loadAddon(addons.webgl.instance); + setTimeout(() => { + if (addons.webgl.instance !== undefined) { addTextureAtlas(addons.webgl.instance.textureAtlas); addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); - }, 0); + } + }, 0); + + try { // try-catch to allow the demo to load if webgl is not supported + term.open(terminalContainer); } catch { addons.webgl.instance = undefined; } - - term.open(terminalContainer); term.focus(); addDomListener(paddingElement, 'change', setPadding); From 415100e8b843d748e2c117ceca4c8662eafc0041 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 07:46:10 -0700 Subject: [PATCH 79/87] Adopt onSpecificOptionChange --- src/browser/Terminal.ts | 7 ++---- .../decorations/OverviewRulerRenderer.ts | 10 ++------- src/browser/services/ThemeService.ts | 12 ++-------- src/common/CoreTerminal.ts | 22 ++++++------------- src/common/buffer/BufferSet.ts | 3 +++ src/common/services/BufferService.ts | 2 +- src/common/services/LogService.ts | 6 +---- src/headless/Terminal.ts | 9 -------- 8 files changed, 18 insertions(+), 53 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index e0ba8a94..abb5dfdf 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -265,8 +265,6 @@ export class Terminal extends CoreTerminal implements ITerminal { } protected _updateOptions(key: string): void { - super._updateOptions(key); - // TODO: These listeners should be owned by individual components switch (key) { case 'fontFamily': @@ -307,7 +305,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._accessibilityManager = undefined; } break; - case 'tabStopWidth': this.buffers.setupTabStops(); break; } } @@ -584,8 +581,8 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.options.overviewRulerWidth) { this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement)); } - this.optionsService.onOptionChange(() => { - if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) { + this.optionsService.onSpecificOptionChange('overviewRulerWidth', value => { + if (!this._overviewRulerRenderer && value && this._viewportElement && this.screenElement) { this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement)); } }); diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index 9251d2e3..90166960 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -110,15 +110,9 @@ export class OverviewRulerRenderer extends Disposable { } })); // overview ruler width changed - this.register(this._optionsService.onOptionChange(o => { - if (o === 'overviewRulerWidth') { - this._queueRefresh(true); - } - })); + this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true))); // device pixel ratio changed - this.register(addDisposableDomListener(this._coreBrowseService.window, 'resize', () => { - this._queueRefresh(true); - })); + this.register(addDisposableDomListener(this._coreBrowseService.window, 'resize', () => this._queueRefresh(true))); // set the canvas dimensions this._queueRefresh(true); } diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts index e584a8d1..ac0104d5 100644 --- a/src/browser/services/ThemeService.ts +++ b/src/browser/services/ThemeService.ts @@ -111,16 +111,8 @@ export class ThemeService extends Disposable implements IThemeService { this._updateRestoreColors(); this._setTheme(this._optionsService.rawOptions.theme); - this.register(this._optionsService.onOptionChange(key => { - switch (key) { - case 'minimumContrastRatio': - this._contrastCache.clear(); - break; - case 'theme': - this._setTheme(this._optionsService.rawOptions.theme); - break; - } - })); + this.register(this._optionsService.onSpecificOptionChange('minimumContrastRatio', () => this._contrastCache.clear())); + this.register(this._optionsService.onSpecificOptionChange('theme', () => this._setTheme(this._optionsService.rawOptions.theme))); } /** diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 5f42d8ea..8f1c2a0d 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -130,7 +130,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { 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.optionsService.onSpecificOptionChange('windowsMode', e => this._handleWindowsModeOptionChange(e))); this.register(this._bufferService.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); @@ -261,20 +261,12 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.coreMouseService.reset(); } - protected _updateOptions(key: keyof ITerminalOptions): void { - // TODO: These listeners should be owned by individual components - switch (key) { - case 'scrollback': - this.buffers.resize(this.cols, this.rows); - break; - case 'windowsMode': - if (this.optionsService.rawOptions.windowsMode) { - this._enableWindowsMode(); - } else { - this._windowsMode?.dispose(); - this._windowsMode = undefined; - } - break; + private _handleWindowsModeOptionChange(value: boolean | undefined): void { + if (value) { + this._enableWindowsMode(); + } else { + this._windowsMode?.dispose(); + this._windowsMode = undefined; } } diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index 46fcb097..bc7aa58e 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -32,6 +32,8 @@ export class BufferSet extends Disposable implements IBufferSet { ) { super(); this.reset(); + this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.resize(this._bufferService.cols, this._bufferService.rows))); + this.register(this._optionsService.onSpecificOptionChange('tabStopWidth', () => this.setupTabStops())); } public reset(): void { @@ -119,6 +121,7 @@ export class BufferSet extends Disposable implements IBufferSet { public resize(newCols: number, newRows: number): void { this._normal.resize(newCols, newRows); this._alt.resize(newCols, newRows); + this.setupTabStops(newCols); } /** diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index f238206c..3f15f242 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -43,7 +43,7 @@ export class BufferService extends Disposable implements IBufferService { this.cols = cols; this.rows = rows; this.buffers.resize(cols, rows); - this.buffers.setupTabStops(this.cols); + // TODO: This doesn't fire when scrollback changes - add a resize event to BufferSet and forward event this._onResize.fire({ cols, rows }); } diff --git a/src/common/services/LogService.ts b/src/common/services/LogService.ts index 854f85de..4b56a097 100644 --- a/src/common/services/LogService.ts +++ b/src/common/services/LogService.ts @@ -40,11 +40,7 @@ export class LogService extends Disposable implements ILogService { ) { super(); this._updateLogLevel(); - this.register(this._optionsService.onOptionChange(key => { - if (key === 'logLevel') { - this._updateLogLevel(); - } - })); + this.register(this._optionsService.onSpecificOptionChange('logLevel', () => this._updateLogLevel())); } private _updateLogLevel(): void { diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index f021c42b..2c244f21 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -78,15 +78,6 @@ export class Terminal extends CoreTerminal { return this.buffers.active; } - protected _updateOptions(key: string): void { - super._updateOptions(key); - - // TODO: These listeners should be owned by individual components - switch (key) { - case 'tabStopWidth': this.buffers.setupTabStops(); break; - } - } - // TODO: Support paste here? public get markers(): IMarker[] { From 1c345bef48977ce409f29313f1c442168c75f2aa Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sun, 9 Oct 2022 15:01:15 +0000 Subject: [PATCH 80/87] Handle onWillFire if it fails, so it loads dom renderer --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 --- src/browser/Terminal.ts | 5 ++++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 8f747c03..ed99a225 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -103,9 +103,6 @@ export class WebglRenderer extends Disposable implements IRenderer { throw new Error('WebGL2 not supported ' + this._gl); } - // TODO: Remove me. - throw new Error('WebGL2 not supported ' + this._gl); - this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { console.log('webglcontextlost event received'); // Prevent the default behavior in order to enable WebGL context restoration. diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index e0ba8a94..9b17c55c 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -515,7 +515,10 @@ export class Terminal extends CoreTerminal implements ITerminal { // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); - this._onWillOpen.fire(this.element); + try { + this._onWillOpen.fire(this.element); + } + catch { /* fails to load addon for some reason */ } if (!this._renderService.hasRenderer()) { this._renderService.setRenderer(this._createRenderer()); } From a4c62792df5b732e34bd25257fa2cfdf05d7c0a8 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sun, 9 Oct 2022 15:03:54 +0000 Subject: [PATCH 81/87] revert formatting changes --- demo/client.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 6d48b302..44a96eb3 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -61,26 +61,26 @@ interface IDemoAddon { canChange: boolean; 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 + 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 ); } From 49a0904857c9a44decc7fbed8f7d6633d1091456 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 08:07:43 -0700 Subject: [PATCH 82/87] Move onSpecificOptionChange adoption --- src/browser/Terminal.ts | 26 +++++++++++++------------- src/browser/Viewport.ts | 1 + src/common/CoreTerminal.ts | 2 +- src/common/services/Services.ts | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index abb5dfdf..37883ded 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -264,6 +264,17 @@ export class Terminal extends CoreTerminal implements ITerminal { } } + private _handleScreenReaderModeOptionChange(value: boolean): void { + if (value) { + if (!this._accessibilityManager && this._renderService) { + this._accessibilityManager = new AccessibilityManager(this, this._renderService); + } + } else { + this._accessibilityManager?.dispose(); + this._accessibilityManager = undefined; + } + } + protected _updateOptions(key: string): void { // TODO: These listeners should be owned by individual components switch (key) { @@ -285,6 +296,7 @@ export class Terminal extends CoreTerminal implements ITerminal { case 'fontWeight': case 'fontWeightBold': case 'minimumContrastRatio': + // TODO: move to render service // When the font changes the size of the cells may change which requires a renderer clear if (this._renderService) { this._renderService.clear(); @@ -292,19 +304,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.refresh(0, this.rows - 1); } break; - case 'scrollback': - this.viewport?.syncScrollArea(); - break; - case 'screenReaderMode': - if (this.optionsService.rawOptions.screenReaderMode) { - if (!this._accessibilityManager && this._renderService) { - this._accessibilityManager = new AccessibilityManager(this, this._renderService); - } - } else { - this._accessibilityManager?.dispose(); - this._accessibilityManager = undefined; - } - break; } } @@ -577,6 +576,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // ensure the correct order of the dprchange event this._accessibilityManager = new AccessibilityManager(this, this._renderService); } + this.register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e))); if (this.options.overviewRulerWidth) { this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement)); diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 8f88c559..700c9e22 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -75,6 +75,7 @@ export class Viewport extends Disposable implements IViewport { this._handleThemeChange(themeService.colors); this.register(themeService.onChangeColors(e => this._handleThemeChange(e))); + this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.syncScrollArea())); // Perform this async to ensure the ICharSizeService is ready. setTimeout(() => this.syncScrollArea(), 0); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 8f1c2a0d..f4a7d301 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -261,7 +261,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.coreMouseService.reset(); } - private _handleWindowsModeOptionChange(value: boolean | undefined): void { + private _handleWindowsModeOptionChange(value: boolean): void { if (value) { this._enableWindowsMode(); } else { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 9b591962..5e6751e9 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -200,7 +200,7 @@ export interface IOptionsService { * preferred over {@link onOptionChange} when only a single option is being listened to. */ // eslint-disable-next-line @typescript-eslint/naming-convention - onSpecificOptionChange(key: T, listener: (arg1: ITerminalOptions[T]) => any): IDisposable; + onSpecificOptionChange(key: T, listener: (arg1: Required[T]) => any): IDisposable; } export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number; From 7eead4404848878e5bd3cadd6fb88c0138ae3db5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 08:14:56 -0700 Subject: [PATCH 83/87] Add onMultipleOptionChange --- src/browser/services/CharSizeService.ts | 2 ++ src/common/TestUtils.test.ts | 8 +++++ src/common/services/OptionsService.test.ts | 35 ++++++++++++++++++++++ src/common/services/OptionsService.ts | 9 ++++++ src/common/services/Services.ts | 8 +++++ 5 files changed, 62 insertions(+) diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 267a361b..c75b8de0 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -27,6 +27,8 @@ export class CharSizeService extends Disposable implements ICharSizeService { ) { super(); this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService); + // TODO: ... + // this.register(this._optionsService.onSpecificOptionChange( } public measure(): void { diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 050b1cec..3aa0f694 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -129,6 +129,14 @@ export class MockOptionsService implements IOptionsService { } }); } + // eslint-disable-next-line @typescript-eslint/naming-convention + public onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable { + return this.onOptionChange(eventKey => { + if (keys.indexOf(eventKey) !== -1) { + listener(); + } + }); + } public setOptions(options: ITerminalOptions): void { for (const key of Object.keys(options)) { this.options[key] = options[key]; diff --git a/src/common/services/OptionsService.test.ts b/src/common/services/OptionsService.test.ts index a0351516..004f7316 100644 --- a/src/common/services/OptionsService.test.ts +++ b/src/common/services/OptionsService.test.ts @@ -112,4 +112,39 @@ describe('OptionsService', () => { }); }); }); + describe('onSpecificOptionChange', () => { + let service: OptionsService; + beforeEach(() => { + service = new OptionsService({}); + }); + it('should fire only on a specific option change', async () => { + await new Promise(r => { + service.onSpecificOptionChange('scrollback', e => { + assert.strictEqual(e, 20); + r(); + }); + service.options.cursorWidth = 10; + service.options.scrollback = 20; + }); + }); + }); + describe('onMultipleOptionChange', () => { + let service: OptionsService; + beforeEach(() => { + service = new OptionsService({}); + }); + it('should fire only for specific options', async () => { + await new Promise(r => { + let called = false; + service.onMultipleOptionChange(['scrollback'], () => { + called = true; + }); + service.options.cursorWidth = 10; + assert.notOk(called); + service.options.scrollback = 20; + assert.ok(called); + r(); + }); + }); + }); }); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 439d6a77..976cdf8d 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -91,6 +91,15 @@ export class OptionsService extends Disposable implements IOptionsService { }); } + // eslint-disable-next-line @typescript-eslint/naming-convention + public onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable { + return this.onOptionChange(eventKey => { + if (keys.indexOf(eventKey) !== -1) { + listener(); + } + }); + } + private _setupOptions(): void { const getter = (propName: string): any => { if (!(propName in DEFAULT_OPTIONS)) { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 5e6751e9..cc388063 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -201,6 +201,14 @@ export interface IOptionsService { */ // eslint-disable-next-line @typescript-eslint/naming-convention onSpecificOptionChange(key: T, listener: (arg1: Required[T]) => any): IDisposable; + + /** + * Adds an event listener for when a set of specific options change, this is a convenience method + * that is preferred over {@link onOptionChange} when multiple options are being listened to and + * handled the same way. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable; } export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number; From 8ac88bc6bf23795cd67a96c28ffe9617f9dd211c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 09:07:01 -0700 Subject: [PATCH 84/87] Move Terminal option updates into owning components --- src/browser/Terminal.ts | 32 ------------------------- src/browser/services/CharSizeService.ts | 4 ++-- src/browser/services/RenderService.ts | 24 +++++++++++++++++-- 3 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 37883ded..c8769e0b 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -275,38 +275,6 @@ export class Terminal extends CoreTerminal implements ITerminal { } } - protected _updateOptions(key: string): void { - // TODO: These listeners should be owned by individual components - switch (key) { - case 'fontFamily': - case 'fontSize': - // When the font changes the size of the cells may change which requires a renderer clear - this._renderService?.clear(); - this._charSizeService?.measure(); - break; - case 'cursorBlink': - case 'cursorStyle': - // The DOM renderer needs a row refresh to update the cursor styles - this.refresh(this.buffer.y, this.buffer.y); - break; - case 'customGlyphs': - case 'drawBoldTextInBrightColors': - case 'letterSpacing': - case 'lineHeight': - case 'fontWeight': - case 'fontWeightBold': - case 'minimumContrastRatio': - // TODO: move to render service - // When the font changes the size of the cells may change which requires a renderer clear - if (this._renderService) { - this._renderService.clear(); - this._renderService.handleResize(this.cols, this.rows); - this.refresh(0, this.rows - 1); - } - break; - } - } - /** * Binds the desired focus behavior on a given terminal object. */ diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index c75b8de0..45bbe840 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -7,6 +7,7 @@ import { IOptionsService } from 'common/services/Services'; import { EventEmitter } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; +import { ITerminalOptions } from 'common/Types'; export class CharSizeService extends Disposable implements ICharSizeService { public serviceBrand: undefined; @@ -27,8 +28,7 @@ export class CharSizeService extends Disposable implements ICharSizeService { ) { super(); this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService); - // TODO: ... - // this.register(this._optionsService.onSpecificOptionChange( + this.register(this._optionsService.onMultipleOptionChange(['fontFamily', 'fontSize'], () => this.measure())); } public measure(): void { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index dd0c6b50..1e1943df 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -84,8 +84,28 @@ export class RenderService extends Disposable implements IRenderService { this.register(decorationService.onDecorationRegistered(() => this._fullRefresh())); this.register(decorationService.onDecorationRemoved(() => this._fullRefresh())); - // No need to register this as renderer is explicitly disposed in RenderService.dispose - // this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); + // Clear the renderer when the a change that could affect glyphs occurs + this.register(optionsService.onMultipleOptionChange([ + 'customGlyphs', + 'drawBoldTextInBrightColors', + 'letterSpacing', + 'lineHeight', + 'fontFamily', + 'fontSize', + 'fontWeight', + 'fontWeightBold', + 'minimumContrastRatio' + ], () => { + this.clear(); + this.handleResize(bufferService.cols, bufferService.rows); + this._fullRefresh(); + })); + + // Refresh the cursor line when the cursor changes + this.register(optionsService.onMultipleOptionChange([ + 'cursorBlink', + 'cursorStyle' + ], () => this.refreshRows(bufferService.buffer.y, bufferService.buffer.y, true))); // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. From e47296c1b9f72596699f2cf1b2d82cc217915e99 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 09:28:18 -0700 Subject: [PATCH 85/87] Use options service directly in renderers --- addons/xterm-addon-canvas/src/CanvasRenderer.ts | 1 + addons/xterm-addon-webgl/src/WebglAddon.ts | 12 ++++++------ addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 +++- src/browser/renderer/dom/DomRenderer.ts | 1 + src/browser/renderer/shared/Types.d.ts | 1 - src/browser/services/RenderService.ts | 1 - 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index ff92e94b..d6f2f481 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -70,6 +70,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this.handleOptionsChanged(); + this.register(this._optionsService.onOptionChange(() => this.handleOptionsChanged())); this.register(toDisposable(() => { for (const l of this._renderLayers) { diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 71487315..295db320 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -3,14 +3,13 @@ * @license MIT */ -import { Terminal, ITerminalAddon, IEvent } from 'xterm'; -import { WebglRenderer } from './WebglRenderer'; import { ICharacterJoinerService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; -import { IColorSet } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; -import { isSafari } from 'common/Platform'; -import { ICoreService, IDecorationService } from 'common/services/Services'; import { Disposable, toDisposable } from 'common/Lifecycle'; +import { isSafari } from 'common/Platform'; +import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { ITerminalAddon, Terminal } from 'xterm'; +import { WebglRenderer } from './WebglRenderer'; export class WebglAddon extends Disposable implements ITerminalAddon { private _terminal?: Terminal; @@ -43,7 +42,8 @@ export class WebglAddon extends Disposable implements ITerminalAddon { const coreService: ICoreService = core.coreService; const decorationService: IDecorationService = core._decorationService; const themeService: IThemeService = core._themeService; - this._renderer = this.register(new WebglRenderer(terminal, themeService, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer)); + const optionsService: IOptionsService = core.optionsService; + this._renderer = this.register(new WebglRenderer(terminal, themeService, characterJoinerService, coreBrowserService, optionsService, coreService, decorationService, this._preserveDrawingBuffer)); this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); this.register(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 ed99a225..41cfa7e4 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -15,7 +15,7 @@ import { CellData } from 'common/buffer/CellData'; import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { ICoreService, IDecorationService } from 'common/services/Services'; +import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { CharData, IBufferLine, ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { GlyphRenderer } from './GlyphRenderer'; @@ -58,6 +58,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private readonly _themeService: IThemeService, private readonly _characterJoinerService: ICharacterJoinerService, private readonly _coreBrowserService: ICoreBrowserService, + optionsService: IOptionsService, coreService: ICoreService, private readonly _decorationService: IDecorationService, preserveDrawingBuffer?: boolean @@ -90,6 +91,7 @@ export class WebglRenderer extends Disposable implements IRenderer { }; this._devicePixelRatio = this._coreBrowserService.dpr; this._updateDimensions(); + this.register(optionsService.onOptionChange(() => this.handleOptionsChanged())); this._canvas = document.createElement('canvas'); diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 5d2729c0..a5ef7c78 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -79,6 +79,7 @@ export class DomRenderer extends Disposable implements IRenderer { actualCellHeight: 0 }; this._updateDimensions(); + this.register(this._optionsService.onOptionChange(() => this.handleOptionsChanged())); this.register(themeService.onChangeColors(e => this._injectCss(e))); this._injectCss(themeService.colors); diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 1def0f77..61a90890 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -68,7 +68,6 @@ export interface IRenderer extends IDisposable { handleFocus(): void; handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; handleCursorMove(): void; - handleOptionsChanged(): 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 1e1943df..190967db 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -177,7 +177,6 @@ export class RenderService extends Disposable implements IRenderService { if (!this._renderer) { return; } - this._renderer.handleOptionsChanged(); this.refreshRows(0, this._rowCount - 1); this._fireOnCanvasResize(); } From 8d7137417bc8200cab8bfb6eef9038dcddce7700 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 10:12:19 -0700 Subject: [PATCH 86/87] Split renderer safe vs unsafe api on activate --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 26 +++++++++++--------- addons/xterm-addon-webgl/src/WebglAddon.ts | 21 ++++++++++------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 1dc607e5..74248f1c 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -4,7 +4,7 @@ */ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, ITerminal } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; @@ -23,25 +23,27 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { } public activate(terminal: Terminal): void { - const core = (terminal as any)._core; + const core = (terminal as any)._core as ITerminal; + const unsafeCore = core as any; if (!terminal.element) { this.register(core.onWillOpen(() => this.activate(terminal))); return; } this._terminal = terminal; - const bufferService: IBufferService = core._bufferService; - const renderService: IRenderService = core._renderService; - const characterJoinerService: ICharacterJoinerService = core._characterJoinerService; - const charSizeService: ICharSizeService = core._charSizeService; - const coreService: ICoreService = core.coreService; - const coreBrowserService: ICoreBrowserService = core._coreBrowserService; - const decorationService: IDecorationService = core._decorationService; - const optionsService: IOptionsService = core.optionsService; - const themeService: IThemeService = core._themeService; - const screenElement: HTMLElement = core.screenElement; + const coreService = core.coreService; + const optionsService = core.optionsService; + const screenElement = core.screenElement!; const linkifier = core.linkifier2; + const bufferService: IBufferService = unsafeCore._bufferService; + const renderService: IRenderService = unsafeCore._renderService; + const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService; + const charSizeService: ICharSizeService = unsafeCore._charSizeService; + const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService; + const decorationService: IDecorationService = unsafeCore._decorationService; + const themeService: IThemeService = unsafeCore._themeService; + this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); renderService.setRenderer(this._renderer); diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 295db320..149a93d7 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -4,10 +4,12 @@ */ import { ICharacterJoinerService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; +import { ITerminal } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { isSafari } from 'common/Platform'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { ICoreTerminal } from 'common/Types'; import { ITerminalAddon, Terminal } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; @@ -30,19 +32,24 @@ export class WebglAddon extends Disposable implements ITerminalAddon { if (isSafari) { throw new Error('Webgl is not currently supported on Safari'); } - const core = (terminal as any)._core; + + const core = (terminal as any)._core as ITerminal; + const unsafeCore = core as any; if (!terminal.element) { - this.register(core.onWillOpen(() => this.activate(terminal))); + this.register(unsafeCore.onWillOpen(() => this.activate(terminal))); return; } + this._terminal = terminal; - const renderService: IRenderService = core._renderService; - const characterJoinerService: ICharacterJoinerService = core._characterJoinerService; - const coreBrowserService: ICoreBrowserService = core._coreBrowserService; const coreService: ICoreService = core.coreService; - const decorationService: IDecorationService = core._decorationService; - const themeService: IThemeService = core._themeService; const optionsService: IOptionsService = core.optionsService; + + const renderService: IRenderService = unsafeCore._renderService; + const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService; + const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService; + const decorationService: IDecorationService = unsafeCore._decorationService; + const themeService: IThemeService = unsafeCore._themeService; + this._renderer = this.register(new WebglRenderer(terminal, themeService, characterJoinerService, coreBrowserService, optionsService, coreService, decorationService, this._preserveDrawingBuffer)); this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); From a37ea14d10140c952f2749840e719d4511cd8c9e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 9 Oct 2022 10:57:49 -0700 Subject: [PATCH 87/87] Use options service in renderers --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 1 - addons/xterm-addon-canvas/src/CanvasAddon.ts | 2 +- addons/xterm-addon-canvas/src/CanvasRenderer.ts | 8 -------- addons/xterm-addon-canvas/src/CursorRenderLayer.ts | 5 +++-- addons/xterm-addon-canvas/src/TextRenderLayer.ts | 5 +---- addons/xterm-addon-canvas/src/Types.d.ts | 5 ----- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 ++-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 9 +++------ .../src/renderLayer/BaseRenderLayer.ts | 1 - .../src/renderLayer/CursorRenderLayer.ts | 12 +++++++----- .../src/renderLayer/LinkRenderLayer.ts | 2 +- addons/xterm-addon-webgl/src/renderLayer/Types.ts | 6 ------ src/browser/renderer/dom/DomRenderer.ts | 4 ++-- 13 files changed, 20 insertions(+), 44 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 54fea5f2..8f203400 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -79,7 +79,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer } } - public handleOptionsChanged(): void {} public handleBlur(): void {} public handleFocus(): void {} public handleCursorMove(): void {} diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 74248f1c..e39b56f1 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -24,7 +24,6 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { public activate(terminal: Terminal): void { const core = (terminal as any)._core as ITerminal; - const unsafeCore = core as any; if (!terminal.element) { this.register(core.onWillOpen(() => this.activate(terminal))); return; @@ -36,6 +35,7 @@ export class CanvasAddon extends Disposable implements ITerminalAddon { const screenElement = core.screenElement!; const linkifier = core.linkifier2; + const unsafeCore = core as any; const bufferService: IBufferService = unsafeCore._bufferService; const renderService: IRenderService = unsafeCore._renderService; const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService; diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index d6f2f481..b657270f 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -68,10 +68,6 @@ export class CanvasRenderer extends Disposable implements IRenderer { this._updateDimensions(); this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); - - this.handleOptionsChanged(); - this.register(this._optionsService.onOptionChange(() => this.handleOptionsChanged())); - this.register(toDisposable(() => { for (const l of this._renderLayers) { l.dispose(); @@ -131,10 +127,6 @@ export class CanvasRenderer extends Disposable implements IRenderer { this._runOperation(l => l.handleCursorMove()); } - public handleOptionsChanged(): void { - this._runOperation(l => l.handleOptionsChanged()); - } - public clear(): void { this._runOperation(l => l.reset()); } diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index ab8b1e66..83806697 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -58,6 +58,7 @@ export class CursorRenderLayer extends BaseRenderLayer { 'block': this._renderBlockCursor.bind(this), 'underline': this._renderUnderlineCursor.bind(this) }; + this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this.register(toDisposable(() => { this._cursorBlinkStateManager?.dispose(); this._cursorBlinkStateManager = undefined; @@ -79,7 +80,7 @@ export class CursorRenderLayer extends BaseRenderLayer { public reset(): void { this._clearCursor(); this._cursorBlinkStateManager?.restartBlinkAnimation(); - this.handleOptionsChanged(); + this._handleOptionsChanged(); } public handleBlur(): void { @@ -92,7 +93,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } - public handleOptionsChanged(): void { + private _handleOptionsChanged(): void { if (this._optionsService.rawOptions.cursorBlink) { if (!this._cursorBlinkStateManager) { this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => { diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index ca9eae56..e2a35751 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -45,6 +45,7 @@ export class TextRenderLayer extends BaseRenderLayer { ) { super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService); this._state = new GridCache(); + this.register(optionsService.onSpecificOptionChange('allowTransparency', value => this._setTransparency(value))); } public resize(dim: IRenderDimensions): void { @@ -251,10 +252,6 @@ export class TextRenderLayer extends BaseRenderLayer { this._drawForeground(firstRow, lastRow); } - public handleOptionsChanged(): void { - this._setTransparency(this._optionsService.rawOptions.allowTransparency); - } - /** * Whether a character is overlapping to the next cell. */ diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index 1840284f..753bd127 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -73,11 +73,6 @@ export interface IRenderLayer extends IDisposable { */ handleCursorMove(): void; - /** - * Called when options change. - */ - handleOptionsChanged(): void; - /** * Called when the data in the grid has changed (or needs to be rendered * again). diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 149a93d7..1f8e2616 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -34,9 +34,8 @@ export class WebglAddon extends Disposable implements ITerminalAddon { } const core = (terminal as any)._core as ITerminal; - const unsafeCore = core as any; if (!terminal.element) { - this.register(unsafeCore.onWillOpen(() => this.activate(terminal))); + this.register(core.onWillOpen(() => this.activate(terminal))); return; } @@ -44,6 +43,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon { const coreService: ICoreService = core.coreService; const optionsService: IOptionsService = core.optionsService; + const unsafeCore = core as any; const renderService: IRenderService = unsafeCore._renderService; const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService; const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 41cfa7e4..fddeb99c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -73,7 +73,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers = [ new LinkRenderLayer(this._core.screenElement!, 2, this._terminal, this._core.linkifier2, this._coreBrowserService, this._themeService), - new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._onRequestRedraw, this._coreBrowserService, coreService, this._themeService) + new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._onRequestRedraw, this._coreBrowserService, coreService, this._themeService, optionsService) ]; this.dimensions = { scaledCharWidth: 0, @@ -91,7 +91,7 @@ export class WebglRenderer extends Disposable implements IRenderer { }; this._devicePixelRatio = this._coreBrowserService.dpr; this._updateDimensions(); - this.register(optionsService.onOptionChange(() => this.handleOptionsChanged())); + this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this._canvas = document.createElement('canvas'); @@ -232,10 +232,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } } - public handleOptionsChanged(): void { - for (const l of this._renderLayers) { - l.handleOptionsChanged(this._terminal); - } + private _handleOptionsChanged(): void { this._updateDimensions(); this._refreshCharAtlas(); } diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index e99a1464..8ed12d86 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -59,7 +59,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer } } - public handleOptionsChanged(terminal: Terminal): void {} public handleBlur(terminal: Terminal): void {} public handleFocus(terminal: Terminal): void {} public handleCursorMove(terminal: Terminal): void {} diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 74801f4e..a6325dcb 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -11,7 +11,7 @@ import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { ICoreService } from 'common/services/Services'; +import { ICoreService, IOptionsService } from 'common/services/Services'; import { toDisposable } from 'common/Lifecycle'; interface ICursorState { @@ -40,7 +40,8 @@ export class CursorRenderLayer extends BaseRenderLayer { private _onRequestRefreshRowsEvent: IEventEmitter, coreBrowserService: ICoreBrowserService, private readonly _coreService: ICoreService, - themeService: IThemeService + themeService: IThemeService, + optionsService: IOptionsService ) { super(terminal, container, 'cursor', zIndex, true, coreBrowserService, themeService); this._state = { @@ -55,7 +56,8 @@ export class CursorRenderLayer extends BaseRenderLayer { 'block': this._renderBlockCursor.bind(this), 'underline': this._renderUnderlineCursor.bind(this) }; - this.handleOptionsChanged(terminal); + this._handleOptionsChanged(terminal); + this.register(optionsService.onOptionChange(() => this._handleOptionsChanged(terminal))); this.register(toDisposable(() => { this._cursorBlinkStateManager?.dispose(); this._cursorBlinkStateManager = undefined; @@ -77,7 +79,7 @@ export class CursorRenderLayer extends BaseRenderLayer { public reset(terminal: Terminal): void { this._clearCursor(); this._cursorBlinkStateManager?.restartBlinkAnimation(terminal); - this.handleOptionsChanged(terminal); + this._handleOptionsChanged(terminal); } public handleBlur(terminal: Terminal): void { @@ -90,7 +92,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY }); } - public handleOptionsChanged(terminal: Terminal): void { + private _handleOptionsChanged(terminal: Terminal): void { if (terminal.options.cursorBlink) { if (!this._cursorBlinkStateManager) { this._cursorBlinkStateManager = new CursorBlinkStateManager(() => { diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 2d2af192..77b02420 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -7,7 +7,7 @@ import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { ILinkifier2, ILinkifierEvent, ITerminal } from 'browser/Types'; +import { ILinkifier2, ILinkifierEvent } from 'browser/Types'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts index 089680ca..bad56091 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/Types.ts @@ -4,7 +4,6 @@ */ import { IDisposable, Terminal } from 'xterm'; -import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/shared/Types'; export interface IRenderLayer extends IDisposable { @@ -23,11 +22,6 @@ export interface IRenderLayer extends IDisposable { */ handleCursorMove(terminal: Terminal): void; - /** - * Called when options change. - */ - handleOptionsChanged(terminal: Terminal): void; - /** * Called when the data in the grid has changed (or needs to be rendered * again). diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index a5ef7c78..02738b5b 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -79,7 +79,7 @@ export class DomRenderer extends Disposable implements IRenderer { actualCellHeight: 0 }; this._updateDimensions(); - this.register(this._optionsService.onOptionChange(() => this.handleOptionsChanged())); + this.register(this._optionsService.onOptionChange(() => this._handleOptionsChanged())); this.register(themeService.onChangeColors(e => this._injectCss(e))); this._injectCss(themeService.colors); @@ -343,7 +343,7 @@ export class DomRenderer extends Disposable implements IRenderer { // No-op, the cursor is drawn when rows are drawn } - public handleOptionsChanged(): void { + private _handleOptionsChanged(): void { // Force a refresh this._updateDimensions(); }