From f983b3cac19aca040568144f3d439db0615f4e5a Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sun, 9 Oct 2022 10:15:13 +0000 Subject: [PATCH 01/95] 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 fb91b01217d6b75102b9bdf4a9fed7e40d7cc633 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sun, 9 Oct 2022 12:16:26 +0000 Subject: [PATCH 02/95] Adds logs again in server.js --- demo/server.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/demo/server.js b/demo/server.js index 0e82f9e9..2a48a21c 100644 --- a/demo/server.js +++ b/demo/server.js @@ -16,7 +16,8 @@ function startServer() { var app = express(); expressWs(app); - var terminals = {}; + var terminals = {}, + logs = {}; app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.get('/logo.png', (req, res) => { @@ -54,6 +55,10 @@ 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(); }); @@ -72,6 +77,7 @@ 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,7 +138,11 @@ function startServer() { // 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) { - send(data); + try { + send(data); + } catch (ex) { + // The WebSocket is not open, ignore + } }); ws.on('message', function(msg) { term.write(msg); @@ -143,6 +153,7 @@ function startServer() { console.log('Closed terminal ' + term.pid); // Clean things up delete terminals[term.pid]; + delete logs[term.pid]; }); }); 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 03/95] 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 04/95] 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 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 05/95] 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 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 06/95] 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 07/95] 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 08/95] 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 09/95] 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 10/95] 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 11/95] 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(); } From 28fcffe46c50bf0ed6089ea1c04b0cfcd829837c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 12 Oct 2022 10:16:26 -0700 Subject: [PATCH 12/95] Optimize _clipImageData This makes clip image data around twice as fast, it's a little tricky to measure it though. Changes: - Work on Uint32Array instead of Uint8 so it's 1 assignment per pixel instead of 1 per channel. - Perform the clipping in-place using the original image data to avoid allocation a new buffer. Fixes #4197 --- src/browser/renderer/shared/TextureAtlas.ts | 24 ++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index d450cfdc..ed2b25f0 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -770,20 +770,24 @@ export class TextureAtlas implements ITextureAtlas { } private _clipImageData(imageData: ImageData, boundingBox: IBoundingBox): ImageData { + // Operate on pixels instead of channels to reduce the amount of work + const originalData = new Uint32Array(imageData.data.buffer); + + // Create a new view on the same buffer for the clipped data. The clipping operation is done in + // place to avoid allocating another buffer const width = boundingBox.right - boundingBox.left + 1; const height = boundingBox.bottom - boundingBox.top + 1; - const clippedData = new Uint8ClampedArray(width * height * 4); - for (let y = boundingBox.top; y <= boundingBox.bottom; y++) { - for (let x = boundingBox.left; x <= boundingBox.right; x++) { - const oldOffset = y * this._tmpCanvas.width * 4 + x * 4; - const newOffset = (y - boundingBox.top) * width * 4 + (x - boundingBox.left) * 4; - clippedData[newOffset] = imageData.data[oldOffset]; - clippedData[newOffset + 1] = imageData.data[oldOffset + 1]; - clippedData[newOffset + 2] = imageData.data[oldOffset + 2]; - clippedData[newOffset + 3] = imageData.data[oldOffset + 3]; + const clippedData = new Uint32Array(imageData.data.buffer, 0, width * height); + + // Perform clipping and return the result + let x = 0; + let y = 0; + for (y = boundingBox.top; y <= boundingBox.bottom; y++) { + for (x = boundingBox.left; x <= boundingBox.right; x++) { + clippedData[(y - boundingBox.top) * width + (x - boundingBox.left)] = originalData[y * imageData.width + x]; } } - return new ImageData(clippedData, width, height); + return new ImageData(new Uint8ClampedArray(clippedData.buffer, clippedData.byteOffset, clippedData.byteLength), width, height); } } From 50d1834871bb4b4d336bd8d4650f8e5779c64841 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 13 Oct 2022 06:25:13 -0700 Subject: [PATCH 13/95] Remove clipImageData completely putImageData works a little differently to drawImage which confused me for a bit, you need to offset the destination as putImageData draws the 'dirty' parts of the texture using the same source image dimensions Fixes #4197 --- src/browser/renderer/shared/TextureAtlas.ts | 28 +++++++-------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index d450cfdc..162c7bdf 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -612,7 +612,6 @@ export class TextureAtlas implements ITextureAtlas { } const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, restrictedPowerlineGlyph, customGlyph, padding); - const clippedImageData = this._clipImageData(imageData, this._workBoundingBox); // Find the best atlas row to use let activeRow: ICharAtlasActiveRow; @@ -676,7 +675,15 @@ export class TextureAtlas implements ITextureAtlas { activeRow.x += rasterizedGlyph.size.x; // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us - this._cacheCtx.putImageData(clippedImageData, rasterizedGlyph.texturePosition.x, rasterizedGlyph.texturePosition.y); + this._cacheCtx.putImageData( + imageData, + rasterizedGlyph.texturePosition.x - this._workBoundingBox.left, + rasterizedGlyph.texturePosition.y - this._workBoundingBox.top, + this._workBoundingBox.left, + this._workBoundingBox.top, + rasterizedGlyph.size.x, + rasterizedGlyph.size.y + ); return rasterizedGlyph; } @@ -768,23 +775,6 @@ export class TextureAtlas implements ITextureAtlas { } }; } - - private _clipImageData(imageData: ImageData, boundingBox: IBoundingBox): ImageData { - const width = boundingBox.right - boundingBox.left + 1; - const height = boundingBox.bottom - boundingBox.top + 1; - const clippedData = new Uint8ClampedArray(width * height * 4); - for (let y = boundingBox.top; y <= boundingBox.bottom; y++) { - for (let x = boundingBox.left; x <= boundingBox.right; x++) { - const oldOffset = y * this._tmpCanvas.width * 4 + x * 4; - const newOffset = (y - boundingBox.top) * width * 4 + (x - boundingBox.left) * 4; - clippedData[newOffset] = imageData.data[oldOffset]; - clippedData[newOffset + 1] = imageData.data[oldOffset + 1]; - clippedData[newOffset + 2] = imageData.data[oldOffset + 2]; - clippedData[newOffset + 3] = imageData.data[oldOffset + 3]; - } - } - return new ImageData(clippedData, width, height); - } } /** From 6bbe5a82a88eaefeea0f09d26a39088d1c6b2f60 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sat, 15 Oct 2022 07:28:21 +0000 Subject: [PATCH 14/95] Remove check proposed api checks --- src/browser/public/Terminal.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index c693950c..7263d06d 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -78,7 +78,6 @@ export class Terminal implements ITerminalApi { public get element(): HTMLElement | undefined { return this._core.element; } public get parser(): IParser { - this._checkProposedApi(); if (!this._parser) { this._parser = new ParserApi(this._core); } @@ -92,7 +91,6 @@ export class Terminal implements ITerminalApi { public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } public get buffer(): IBufferNamespaceApi { - this._checkProposedApi(); if (!this._buffer) { this._buffer = new BufferNamespaceApi(this._core); } @@ -148,7 +146,6 @@ export class Terminal implements ITerminalApi { this._core.attachCustomKeyEventHandler(customKeyEventHandler); } public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { - this._checkProposedApi(); return this._core.registerLinkProvider(linkProvider); } public registerCharacterJoiner(handler: (text: string) => [number, number][]): number { From 073aa6d439b7e025d355d0be9ebc84677f3f7208 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Sat, 15 Oct 2022 10:25:39 +0200 Subject: [PATCH 15/95] Fix test "Proposed API check" --- src/headless/public/Terminal.test.ts | 2 +- test/api/Terminal.api.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/headless/public/Terminal.test.ts b/src/headless/public/Terminal.test.ts index 43aaf628..c7acc818 100644 --- a/src/headless/public/Terminal.test.ts +++ b/src/headless/public/Terminal.test.ts @@ -22,7 +22,7 @@ describe('Headless API Tests', function (): void { it('Proposed API check', async () => { term = new Terminal({ allowProposedApi: false }); - throws(() => term.buffer, (error) => error.message === 'You must set the allowProposedApi option to true to use proposed API'); + throws(() => term.markers, (error) => error.message === 'You must set the allowProposedApi option to true to use proposed API'); }); it('write', async () => { diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 502f438c..27fb51a7 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -35,7 +35,7 @@ describe('API Integration Tests', function(): void { await openTerminal(page, { allowProposedApi: false }); await page.evaluate(` try { - window.term.buffer; + window.term.markers; } catch (e) { window.throwMessage = e.message; } From a055948937e2260a59d96d436463e16e9f223479 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sat, 15 Oct 2022 10:45:23 +0000 Subject: [PATCH 16/95] unsentOutput instead of logs and dispose temporary listener --- demo/server.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/demo/server.js b/demo/server.js index 2a48a21c..856dac9e 100644 --- a/demo/server.js +++ b/demo/server.js @@ -17,7 +17,8 @@ function startServer() { expressWs(app); var terminals = {}, - logs = {}; + unsentOutput = {}, + temporaryDisposable = {}; app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.get('/logo.png', (req, res) => { @@ -55,9 +56,9 @@ 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; + unsentOutput[term.pid] = ''; + temporaryDisposable[term.pid] = term.onData(function(data) { + unsentOutput[term.pid] += data; }); res.send(term.pid.toString()); res.end(); @@ -77,7 +78,10 @@ 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]); + temporaryDisposable[term.pid].dispose(); + delete temporaryDisposable[term.pid]; + ws.send(unsentOutput[term.pid]); + // unbuffered delivery after user input let userInput = false; @@ -137,7 +141,7 @@ function startServer() { // 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) { + term.onData(function(data) { try { send(data); } catch (ex) { @@ -153,7 +157,8 @@ function startServer() { console.log('Closed terminal ' + term.pid); // Clean things up delete terminals[term.pid]; - delete logs[term.pid]; + delete unsentOutput[term.pid]; + delete temporaryDisposable[term.pid]; }); }); From cc82f605fe073fa9f7b0924e4fddeb37a16bd795 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sat, 15 Oct 2022 10:47:59 +0000 Subject: [PATCH 17/95] delete clean up --- demo/server.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/demo/server.js b/demo/server.js index 856dac9e..03689f20 100644 --- a/demo/server.js +++ b/demo/server.js @@ -17,8 +17,8 @@ function startServer() { expressWs(app); var terminals = {}, - unsentOutput = {}, - temporaryDisposable = {}; + unsentOutput = {}, + temporaryDisposable = {}; app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.get('/logo.png', (req, res) => { @@ -81,7 +81,7 @@ function startServer() { temporaryDisposable[term.pid].dispose(); delete temporaryDisposable[term.pid]; ws.send(unsentOutput[term.pid]); - + delete unsentOutput[term.pid]; // unbuffered delivery after user input let userInput = false; @@ -157,8 +157,6 @@ function startServer() { console.log('Closed terminal ' + term.pid); // Clean things up delete terminals[term.pid]; - delete unsentOutput[term.pid]; - delete temporaryDisposable[term.pid]; }); }); From fb4a4e6127ad365f0507d2786b0be2ac7a3ab6d8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 07:54:31 -0700 Subject: [PATCH 18/95] Draw quads with triangle strip This reduces the number of needed vertices from 6 to 4, it may not have a noticeable performance impact but it's less data and simpler --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 7 ++++--- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 511c9381..88475f18 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -127,8 +127,9 @@ export class GlyphRenderer extends Disposable { gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0); // Setup the unit quad element array buffer, this points to indices in - // unitQuadVertices to allow is to draw 2 triangles from the vertices - const unitQuadElementIndices = new Uint8Array([0, 1, 3, 0, 2, 3]); + // unitQuadVertices to allow is to draw 2 triangles from the vertices via a + // triangle strip + const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]); const elementIndicesBuffer = gl.createBuffer(); this.register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer))); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); @@ -317,7 +318,7 @@ export class GlyphRenderer extends Disposable { gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); // Draw the viewport - gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL); + gl.drawElementsInstanced(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL); } public setAtlas(atlas: ITextureAtlas): void { diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 04368711..ca5cb9a9 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -113,8 +113,9 @@ export class RectangleRenderer extends Disposable { gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0); // Setup the unit quad element array buffer, this points to indices in - // unitQuadVertices to allow is to draw 2 triangles from the vertices - const unitQuadElementIndices = new Uint8Array([0, 1, 3, 0, 2, 3]); + // unitQuadVertices to allow is to draw 2 triangles from the vertices via a + // triangle strip + const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]); const elementIndicesBuffer = gl.createBuffer(); this.register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer))); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); @@ -153,7 +154,7 @@ export class RectangleRenderer extends Disposable { // Bind attributes buffer and draw gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); gl.bufferData(gl.ARRAY_BUFFER, this._vertices.attributes, gl.DYNAMIC_DRAW); - gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count); + gl.drawElementsInstanced(this._gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, this._vertices.count); } public handleResize(): void { From 114924f3fa60d9a05c5a6e4337ceccd41c817330 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 08:38:14 -0700 Subject: [PATCH 19/95] Add eslint-plugin-jsdoc and enable check-param-names --- .eslintrc.json | 4 +- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 3 +- addons/xterm-addon-search/src/SearchAddon.ts | 12 ++-- .../src/WebLinkProvider.ts | 3 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 - .../src/renderLayer/BaseRenderLayer.ts | 1 - demo/server.js | 2 +- package.json | 1 + src/browser/Clipboard.ts | 6 -- src/browser/Lifecycle.ts | 3 + src/browser/Terminal.ts | 7 +- src/browser/input/Mouse.ts | 6 +- src/browser/input/MoveToCell.ts | 18 +++--- src/browser/services/SelectionService.ts | 19 +++--- src/common/CoreTerminal.ts | 1 + src/common/InputHandler.ts | 11 ++-- src/common/buffer/Buffer.ts | 4 +- src/common/buffer/BufferReflow.ts | 3 + src/common/buffer/BufferSet.ts | 1 - src/common/services/BufferService.ts | 1 + yarn.lock | 64 +++++++++++++++++++ 21 files changed, 120 insertions(+), 52 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index e6db42e2..8064820e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -39,7 +39,8 @@ "**/*.js" ], "plugins": [ - "@typescript-eslint" + "@typescript-eslint", + "jsdoc" ], "rules": { "no-extra-semi": "error", @@ -141,6 +142,7 @@ "warn", "always" ], + "jsdoc/check-param-names": 1, "keyword-spacing": "warn", "new-parens": "warn", "no-duplicate-imports": "warn", diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 8f203400..872d8cfc 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, IThemeService } from 'browser/services/Services'; -import { IColorSet, ReadonlyColorSet } from 'browser/Types'; +import { 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'; @@ -329,7 +329,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * @param cell The cell data for the character to draw. * @param x The column to draw at. * @param y The row to draw at. - * @param color The color of the character. */ protected _fillCharTrueColor(cell: CellData, x: number, y: number): void { this._ctx.font = this._getFont(false, false); diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 249dd594..f76d28c2 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -477,7 +477,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { * started on an earlier line then it is skipped since it will be properly searched when the terminal line that the * text starts on is searched. * @param term The search term. - * @param position The position to start the search. + * @param searchPosition The position to start the search. * @param searchOptions Search options. * @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left. * @return The search result if it was found. @@ -627,7 +627,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { * Wide characters will count as two columns in the resulting string. This * function is useful for getting the actual text underneath the raw selection * position. - * @param line The line being translated. + * @param lineIndex The index of the line being translated. * @param trimRight Whether to trim whitespace to the right. */ private _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): LineCacheEntry { @@ -702,10 +702,10 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } /** - * Applies styles to the decoration when it is rendered - * @param element the decoration's element - * @param backgroundColor the background color to apply - * @param borderColor the border color to apply + * Applies styles to the decoration when it is rendered. + * @param element The decoration's element. + * @param borderColor The border color to apply. + * @param isActiveResult Whether the element is part of the active search result. * @returns */ private _applyStyles(element: HTMLElement, borderColor: string | undefined, isActiveResult: boolean): void { diff --git a/addons/xterm-addon-web-links/src/WebLinkProvider.ts b/addons/xterm-addon-web-links/src/WebLinkProvider.ts index 8e9a8408..2f2ccddf 100644 --- a/addons/xterm-addon-web-links/src/WebLinkProvider.ts +++ b/addons/xterm-addon-web-links/src/WebLinkProvider.ts @@ -105,9 +105,8 @@ export class LinkComputer { /** * Gets the entire line for the buffer line - * @param line The line being translated. + * @param lineIndex The index of the line being translated. * @param trimRight Whether to trim whitespace to the right. - * @param terminal The terminal */ private static _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean, terminal: Terminal): [string, number] { let lineString = ''; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index fddeb99c..ded9130a 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -254,8 +254,6 @@ export class WebglRenderer extends Disposable implements IRenderer { /** * Refreshes the char atlas, aquiring a new one if necessary. - * @param terminal The terminal. - * @param colorSet The color set to use for the char atlas. */ private _refreshCharAtlas(): void { if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) { diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 8ed12d86..aa08b583 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -221,7 +221,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * @param cell The cell data for the character to draw. * @param x The column to draw at. * @param y The row to draw at. - * @param color The color of the character. */ protected _fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void { this._ctx.font = this._getFont(terminal, false, false); diff --git a/demo/server.js b/demo/server.js index 0e82f9e9..a152a8b8 100644 --- a/demo/server.js +++ b/demo/server.js @@ -43,7 +43,7 @@ function startServer() { env['COLORTERM'] = 'truecolor'; var cols = parseInt(req.query.cols), rows = parseInt(req.query.rows), - term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], { + term = pty.spawn(process.platform === 'win32' ? 'pwsh.exe' : 'bash', [], { name: 'xterm-256color', cols: cols || 80, rows: rows || 24, diff --git a/package.json b/package.json index a36ad5c9..d8f1141b 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "cross-env": "^7.0.3", "deep-equal": "^2.0.5", "eslint": "^8.1.0", + "eslint-plugin-jsdoc": "^39.3.6", "express": "^4.17.1", "express-ws": "^5.0.2", "glob": "^7.2.0", diff --git a/src/browser/Clipboard.ts b/src/browser/Clipboard.ts index 29e865c8..1f9ea9ec 100644 --- a/src/browser/Clipboard.ts +++ b/src/browser/Clipboard.ts @@ -39,8 +39,6 @@ export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionServ /** * Redirect the clipboard's data to the terminal's input handler. - * @param ev The original paste event to be handled - * @param term The terminal on which to apply the handled paste event */ export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService): void { ev.stopPropagation(); @@ -81,10 +79,6 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA /** * Bind to right-click event and allow right-click copy and paste. - * @param ev The original right click event to be handled. - * @param textarea The terminal's textarea. - * @param selectionService The terminal's selection manager. - * @param shouldSelectWord If true and there is no selection the current word will be selected */ export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionService: ISelectionService, shouldSelectWord: boolean): void { moveTextAreaUnderMouseCursor(ev, textarea, screenElement); diff --git a/src/browser/Lifecycle.ts b/src/browser/Lifecycle.ts index 6e841794..8e0272b2 100644 --- a/src/browser/Lifecycle.ts +++ b/src/browser/Lifecycle.ts @@ -7,8 +7,11 @@ import { IDisposable } from 'common/Types'; /** * Adds a disposable listener to a node in the DOM, returning the disposable. + * @param node The node to add a listener to. * @param type The event type. * @param handler The handler for the listener. + * @param options The boolean or options object to pass on to the event + * listener. */ export function addDisposableDomListener( node: Element | Window | Document, diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9056cbe3..b5262b1e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -979,10 +979,9 @@ export class Terminal extends CoreTerminal implements ITerminal { } /** - * Handle a keydown event - * Key Resources: - * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent - * @param ev The keydown event to be handled. + * Handle a keydown [KeyboardEvent]. + * + * [KeyboardEvent]: https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent */ protected _keyDown(event: KeyboardEvent): boolean | undefined { this._keyDownHandled = false; diff --git a/src/browser/input/Mouse.ts b/src/browser/input/Mouse.ts index c34e8370..309d9265 100644 --- a/src/browser/input/Mouse.ts +++ b/src/browser/input/Mouse.ts @@ -18,15 +18,19 @@ export function getCoordsRelativeToElement(window: Pick, event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined { +export function getCoords(window: Pick, event: Pick, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined { // Coordinates cannot be measured if there are no valid if (!hasValidCharSize) { return undefined; diff --git a/src/browser/input/MoveToCell.ts b/src/browser/input/MoveToCell.ts index 82e767cd..c88db7b2 100644 --- a/src/browser/input/MoveToCell.ts +++ b/src/browser/input/MoveToCell.ts @@ -68,7 +68,7 @@ function resetStartingRow(startX: number, startY: number, targetX: number, targe } return repeat(bufferLine( startX, startY, startX, - startY - wrappedRowsForRow(bufferService, startY), false, bufferService + startY - wrappedRowsForRow(startY, bufferService), false, bufferService ).length, sequence(Direction.LEFT, applicationCursor)); } @@ -77,8 +77,8 @@ function resetStartingRow(startX: number, startY: number, targetX: number, targe * ignoring wrapped rows */ function moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { - const startRow = startY - wrappedRowsForRow(bufferService, startY); - const endRow = targetY - wrappedRowsForRow(bufferService, targetY); + const startRow = startY - wrappedRowsForRow(startY, bufferService); + const endRow = targetY - wrappedRowsForRow(targetY, bufferService); const rowsToMove = Math.abs(startRow - endRow) - wrappedRowsCount(startY, targetY, bufferService); @@ -91,7 +91,7 @@ function moveToRequestedRow(startY: number, targetY: number, bufferService: IBuf function moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string { let startRow; if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) { - startRow = targetY - wrappedRowsForRow(bufferService, targetY); + startRow = targetY - wrappedRowsForRow(targetY, bufferService); } else { startRow = startY; } @@ -115,8 +115,8 @@ function moveToRequestedCol(startX: number, startY: number, targetX: number, tar */ function wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number { let wrappedRows = 0; - const startRow = startY - wrappedRowsForRow(bufferService, startY); - const endRow = targetY - wrappedRowsForRow(bufferService, targetY); + const startRow = startY - wrappedRowsForRow(startY, bufferService); + const endRow = targetY - wrappedRowsForRow(targetY, bufferService); for (let i = 0; i < Math.abs(startRow - endRow); i++) { const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1; @@ -133,7 +133,7 @@ function wrappedRowsCount(startY: number, targetY: number, bufferService: IBuffe * Calculates the number of wrapped rows that make up a given row. * @param currentRow The row to determine how many wrapped rows make it up */ -function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number { +function wrappedRowsForRow(currentRow: number, bufferService: IBufferService): number { let rowCount = 0; let line = bufferService.buffer.lines.get(currentRow); let lineWraps = line?.isWrapped; @@ -157,7 +157,7 @@ function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): n function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction { let startRow; if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) { - startRow = targetY - wrappedRowsForRow(bufferService, targetY); + startRow = targetY - wrappedRowsForRow(targetY, bufferService); } else { startRow = startY; } @@ -237,7 +237,7 @@ function sequence(direction: Direction, applicationCursor: boolean): string { * Returns a string repeated a given number of times * Polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat * @param count The number of times to repeat the string - * @param string The string that is to be repeated + * @param str The string that is to be repeated */ function repeat(count: number, str: string): string { count = Math.floor(count); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 868fadda..763f938b 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -757,19 +757,20 @@ export class SelectionService extends Disposable implements ISelectionService { } /** - * Converts a viewport column to the character index on the buffer line, the - * latter takes into account wide characters. - * @param coords The coordinates to find the 2 index for. + * Converts a viewport column (0 to cols - 1) to the character index on the + * buffer line, the latter takes into account wide and null characters. + * @param bufferLine The buffer line to use. + * @param x The x index in the buffer line to convert. */ - private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number { - let charIndex = coords[0]; - for (let i = 0; coords[0] >= i; i++) { + private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, x: number): number { + let charIndex = x; + for (let i = 0; x >= i; i++) { const length = bufferLine.loadCell(i, this._workCell).getChars().length; if (this._workCell.getWidth() === 0) { // Wide characters aren't included in the line string so decrement the // index so the index is back on the wide character. charIndex--; - } else if (length > 1 && coords[0] !== i) { + } else if (length > 1 && x !== i) { // Emojis take up multiple characters, so adjust accordingly. For these // we don't want ot include the character at the column as we're // returning the start index in the string, not the end index. @@ -816,7 +817,7 @@ export class SelectionService extends Disposable implements ISelectionService { const line = buffer.translateBufferLineToString(coords[1], false); // Get actual index, taking into consideration wide characters - let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords); + let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords[0]); let endIndex = startIndex; // Record offset to be used later @@ -1000,7 +1001,7 @@ export class SelectionService extends Disposable implements ISelectionService { /** * Gets whether the character is considered a word separator by the select * word logic. - * @param char The character to check. + * @param cell The cell to check. */ private _isCharWordSeparator(cell: CellData): boolean { // Zero width characters are never separators as they are always to the diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index f4a7d301..c5182554 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -184,6 +184,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { /** * Scroll the terminal down 1 row, creating a blank line. + * @param eraseAttr The attribute data to use the for blank line. * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index b91b446c..95ec00c1 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 } 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'; @@ -1116,10 +1116,11 @@ export class InputHandler extends Disposable implements IInputHandler { /** * Helper method to erase cells in a terminal row. * The cell gets replaced with the eraseChar of the terminal. - * @param y row index - * @param start first cell index to be erased - * @param end end - 1 is last erased cell - * @param cleanWrap clear the isWrapped flag + * @param y The row index relative to the viewport. + * @param start The start x index of the range to be erased. + * @param end The end x index of the range to be erased (exclusive). + * @param clearWrap clear the isWrapped flag + * @param respectProtect Whether to respect the protection attribute (DECSCA). */ private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false, respectProtect: boolean = false): void { const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index c8b0d1b2..7bee6dfd 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -486,7 +486,7 @@ export class Buffer implements IBuffer { * TODO: respect trim flag after fixing #1685 * @param lineIndex line index the string was retrieved from * @param stringIndex index within the string - * @param startCol column offset the string was retrieved from + * @param trimRight Whether to trim whitespace to the right. */ public stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight: boolean = false): BufferIndex { while (stringIndex) { @@ -515,7 +515,7 @@ export class Buffer implements IBuffer { * Wide characters will count as two columns in the resulting string. This * function is useful for getting the actual text underneath the raw selection * position. - * @param line The line being translated. + * @param lineIndex The absolute index of the line being translated. * @param trimRight Whether to trim whitespace to the right. * @param startCol The column to start at. * @param endCol The column to end at. diff --git a/src/common/buffer/BufferReflow.ts b/src/common/buffer/BufferReflow.ts index ece9a96e..af1c6473 100644 --- a/src/common/buffer/BufferReflow.ts +++ b/src/common/buffer/BufferReflow.ts @@ -16,7 +16,10 @@ export interface INewLayoutResult { * Evaluates and returns indexes to be removed after a reflow larger occurs. Lines will be removed * when a wrapped line unwraps. * @param lines The buffer lines. + * @param oldCols The columns before resize * @param newCols The columns after resize. + * @param bufferAbsoluteY The absolute y position of the cursor (baseY + cursorY). + * @param nullCell The cell data to use when filling in empty cells. */ export function reflowLargerGetLinesToRemove(lines: CircularList, oldCols: number, newCols: number, bufferAbsoluteY: number, nullCell: ICellData): number[] { // Gather all BufferLines that need to be removed from the Buffer here so that they can be diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index bc7aa58e..da902aff 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -24,7 +24,6 @@ export class BufferSet extends Disposable implements IBufferSet { /** * Create a new BufferSet for the given terminal. - * @param _terminal - The terminal the BufferSet will belong to */ constructor( private readonly _optionsService: IOptionsService, diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 3f15f242..528d2674 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -54,6 +54,7 @@ export class BufferService extends Disposable implements IBufferService { /** * Scroll the terminal down 1 row, creating a blank line. + * @param eraseAttr The attribute data to use the for blank line. * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void { diff --git a/yarn.lock b/yarn.lock index 315dddab..8e2dd19d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -189,6 +189,15 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== +"@es-joy/jsdoccomment@~0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.31.0.tgz#dbc342cc38eb6878c12727985e693eaef34302bc" + integrity sha512-tc1/iuQcnaiSIUVad72PBierDFpsxdUHtEF/OrfqvM1CBAsIoMP51j52jTMb3dXriwhieTo289InzZj72jL3EQ== + dependencies: + comment-parser "1.3.1" + esquery "^1.4.0" + jsdoc-type-pratt-parser "~3.1.0" + "@eslint/eslintrc@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.3.tgz#41f08c597025605f672251dcc4e8be66b5ed7366" @@ -1278,6 +1287,11 @@ commander@^7.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +comment-parser@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" + integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -1392,6 +1406,13 @@ debug@^4.3.2: dependencies: ms "2.1.2" +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1647,6 +1668,19 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" +eslint-plugin-jsdoc@^39.3.6: + version "39.3.6" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.3.6.tgz#6ba29f32368d72a51335a3dc9ccd22ad0437665d" + integrity sha512-R6dZ4t83qPdMhIOGr7g2QII2pwCjYyKP+z0tPOfO1bbAbQyKC20Y2Rd6z1te86Lq3T7uM8bNo+VD9YFpE8HU/g== + dependencies: + "@es-joy/jsdoccomment" "~0.31.0" + comment-parser "1.3.1" + debug "^4.3.4" + escape-string-regexp "^4.0.0" + esquery "^1.4.0" + semver "^7.3.7" + spdx-expression-parse "^3.0.1" + eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -2597,6 +2631,11 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsdoc-type-pratt-parser@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e" + integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw== + jsdom@^18.0.1: version "18.0.1" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-18.0.1.tgz#7317c91be425f31ff25814ad427eed8a2a310b61" @@ -3434,6 +3473,13 @@ semver@^7.2.1, semver@^7.3.4, semver@^7.3.5: dependencies: lru-cache "^6.0.0" +semver@^7.3.7: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -3579,6 +3625,24 @@ spawn-wrap@^2.0.0: signal-exit "^3.0.2" which "^2.0.1" +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.12" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" + integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" From 49e0480f785151760b07efa7c22d388a515b1258 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 08:39:32 -0700 Subject: [PATCH 20/95] Add rule jsdoc/check-alignment --- .eslintrc.json | 1 + addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 10 +++++----- src/common/parser/Constants.ts | 4 ++-- src/common/parser/Types.d.ts | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8064820e..58082186 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -142,6 +142,7 @@ "warn", "always" ], + "jsdoc/check-alignment": 1, "jsdoc/check-param-names": 1, "keyword-spacing": "warn", "new-parens": "warn", diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 872d8cfc..29c85558 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -162,11 +162,11 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer } /** - * Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the - * existing fillStyle on the context. - * @param x The column to fill. - * @param y The row to fill. - */ + * Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void { const cellOffset = Math.ceil(this._scaledCellHeight * 0.5); this._ctx.fillRect( diff --git a/src/common/parser/Constants.ts b/src/common/parser/Constants.ts index 85156c3e..7fe24f34 100644 --- a/src/common/parser/Constants.ts +++ b/src/common/parser/Constants.ts @@ -24,8 +24,8 @@ export const enum ParserState { } /** -* Internal actions of EscapeSequenceParser. -*/ + * Internal actions of EscapeSequenceParser. + */ export const enum ParserAction { IGNORE = 0, ERROR = 1, diff --git a/src/common/parser/Types.d.ts b/src/common/parser/Types.d.ts index 3a621eab..a1ea0ec2 100644 --- a/src/common/parser/Types.d.ts +++ b/src/common/parser/Types.d.ts @@ -142,8 +142,8 @@ export type PrintFallbackHandlerType = PrintHandlerType; /** -* EscapeSequenceParser interface. -*/ + * EscapeSequenceParser interface. + */ export interface IEscapeSequenceParser extends IDisposable { /** * Preceding codepoint to get REP working correctly. From 9f1a71485cfb6b62fa8faa78e4c38ecca7e4c8b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 08:41:49 -0700 Subject: [PATCH 21/95] Fix invalid jsdoc tags --- addons/xterm-addon-search/src/SearchAddon.ts | 8 ++++---- src/browser/input/CompositionHelper.ts | 2 +- src/browser/selection/SelectionModel.ts | 2 +- src/common/CircularList.ts | 4 ++-- typings/xterm-headless.d.ts | 8 ++++---- typings/xterm.d.ts | 10 +++++----- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index f76d28c2..3f71af29 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -126,7 +126,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { * doesn't exist, do nothing. * @param term The search term. * @param searchOptions Search options. - * @return Whether a result was found. + * @returns Whether a result was found. */ public findNext(term: string, searchOptions?: ISearchOptions): boolean { if (!this._terminal) { @@ -307,7 +307,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { * doesn't exist, do nothing. * @param term The search term. * @param searchOptions Search options. - * @return Whether a result was found. + * @returns Whether a result was found. */ public findPrevious(term: string, searchOptions?: ISearchOptions): boolean { if (!this._terminal) { @@ -480,7 +480,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { * @param searchPosition The position to start the search. * @param searchOptions Search options. * @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left. - * @return The search result if it was found. + * @returns The search result if it was found. */ protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { const terminal = this._terminal!; @@ -662,7 +662,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { /** * Selects and scrolls to a result. * @param result The result to select. - * @return Whether a result was selected. + * @returns Whether a result was selected. */ private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean { const terminal = this._terminal!; diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts index 39ccaa23..ba7d4b6a 100644 --- a/src/browser/input/CompositionHelper.ts +++ b/src/browser/input/CompositionHelper.ts @@ -89,7 +89,7 @@ export class CompositionHelper { /** * Handles the keydown event, routing any necessary events to the CompositionHelper functions. * @param ev The keydown event. - * @return Whether the Terminal should continue processing the keydown event. + * @returns Whether the Terminal should continue processing the keydown event. */ public keydown(ev: KeyboardEvent): boolean { if (this._isComposing || this._isSendingComposition) { diff --git a/src/browser/selection/SelectionModel.ts b/src/browser/selection/SelectionModel.ts index 041c7b24..b26cf944 100644 --- a/src/browser/selection/SelectionModel.ts +++ b/src/browser/selection/SelectionModel.ts @@ -118,7 +118,7 @@ export class SelectionModel { /** * Handle the buffer being trimmed, adjust the selection position. * @param amount The amount the buffer is being trimmed. - * @return Whether a refresh is necessary. + * @returns Whether a refresh is necessary. */ public handleTrim(amount: number): boolean { // Adjust the selection position based on the trimmed amount. diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index b7e1e075..8ab80d56 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -82,7 +82,7 @@ export class CircularList extends Disposable implements ICircularList { * Note that for performance reasons there is no bounds checking here, the index reference is * circular so this should always return a value and never throw. * @param index The index of the value to get. - * @return The value corresponding to the index. + * @returns The value corresponding to the index. */ public get(index: number): T | undefined { return this._array[this._getCyclicIndex(index)]; @@ -138,7 +138,7 @@ export class CircularList extends Disposable implements ICircularList { /** * Removes and returns the last value on the list. - * @return The popped value. + * @returns The popped value. */ public pop(): T | undefined { return this._array[this._getCyclicIndex(this._length-- - 1)]; diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 7453b7e2..af55ffac 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -1080,7 +1080,7 @@ declare module 'xterm-headless' { * Return true if the sequence was handled; false if we should try * a previous handler (set by addCsiHandler or setCsiHandler). * The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. + * @returns An IDisposable you can call to remove this handler. */ registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable; @@ -1099,7 +1099,7 @@ declare module 'xterm-headless' { * Return true if the sequence was handled; false if we should try * a previous handler (set by addDcsHandler or setDcsHandler). * The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. + * @returns An IDisposable you can call to remove this handler. */ registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable; @@ -1112,7 +1112,7 @@ declare module 'xterm-headless' { * Return true if the sequence was handled; false if we should try * a previous handler (set by addEscHandler or setEscHandler). * The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. + * @returns An IDisposable you can call to remove this handler. */ registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable; @@ -1130,7 +1130,7 @@ declare module 'xterm-headless' { * Return true if the sequence was handled; false if we should try * a previous handler (set by addOscHandler or setOscHandler). * The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. + * @returns An IDisposable you can call to remove this handler. */ registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0e2b0357..aec0fdbc 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -905,7 +905,7 @@ declare module 'xterm' { * with a string of text that is eligible for joining and returns an array * where each entry is an array containing the start (inclusive) and end * (exclusive) indexes of ranges that should be rendered as a single unit. - * @return The ID of the new joiner, this can be used to deregister + * @returns The ID of the new joiner, this can be used to deregister */ registerCharacterJoiner(handler: (text: string) => [number, number][]): number; @@ -1558,7 +1558,7 @@ declare module 'xterm' { * array will contain subarrays with their numercial values. * Return `true` if the sequence was handled, `false` if the parser should try * a previous handler. The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. + * @returns An IDisposable you can call to remove this handler. */ registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise): IDisposable; @@ -1576,7 +1576,7 @@ declare module 'xterm' { * The function gets the payload and numerical parameters as arguments. * Return `true` if the sequence was handled, `false` if the parser should try * a previous handler. The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. + * @returns An IDisposable you can call to remove this handler. */ registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise): IDisposable; @@ -1588,7 +1588,7 @@ declare module 'xterm' { * @param callback The function to handle the sequence. * Return `true` if the sequence was handled, `false` if the parser should try * a previous handler. The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. + * @returns An IDisposable you can call to remove this handler. */ registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise): IDisposable; @@ -1605,7 +1605,7 @@ declare module 'xterm' { * The callback is called with OSC data string. * Return `true` if the sequence was handled, `false` if the parser should try * a previous handler. The most recently added handler is tried first. - * @return An IDisposable you can call to remove this handler. + * @returns An IDisposable you can call to remove this handler. */ registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; } From 5204374494d5ff1b24191bb977cd3845be86746e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 08:43:06 -0700 Subject: [PATCH 22/95] Add no-multi-asterisks rule --- .eslintrc.json | 1 + addons/xterm-addon-canvas/src/Types.d.ts | 2 +- addons/xterm-addon-webgl/src/renderLayer/Types.ts | 2 +- src/common/buffer/BufferLine.ts | 2 +- src/common/buffer/CellData.ts | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 58082186..822ee4ba 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -144,6 +144,7 @@ ], "jsdoc/check-alignment": 1, "jsdoc/check-param-names": 1, + "jsdoc/no-multi-asterisks": 1, "keyword-spacing": "warn", "new-parens": "warn", "no-duplicate-imports": "warn", diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index 753bd127..edd5c2ea 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -64,7 +64,7 @@ export interface IRenderLayer extends IDisposable { handleBlur(): void; /** - * * Called when the terminal gets focus. + * Called when the terminal gets focus. */ handleFocus(): void; diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts index bad56091..3dbdfd9c 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/Types.ts @@ -13,7 +13,7 @@ export interface IRenderLayer extends IDisposable { handleBlur(terminal: Terminal): void; /** - * * Called when the terminal gets focus. + * Called when the terminal gets focus. */ handleFocus(terminal: Terminal): void; diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 875ac6c9..5a192203 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -130,7 +130,7 @@ export class BufferLine implements IBufferLine { * Test whether contains any chars. * Basically an empty has no content, but other cells might differ in FG/BG * from real empty cells. - * */ + */ public hasContent(index: number): number { return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK; } diff --git a/src/common/buffer/CellData.ts b/src/common/buffer/CellData.ts index a87b5795..9454c553 100644 --- a/src/common/buffer/CellData.ts +++ b/src/common/buffer/CellData.ts @@ -47,7 +47,7 @@ export class CellData extends AttributeData implements ICellData { * Note this returns the UTF32 codepoint of single chars, * if content is a combined string it returns the codepoint * of the last char in string to be in line with code in CharData. - * */ + */ public getCode(): number { return (this.isCombined()) ? this.combinedData.charCodeAt(this.combinedData.length - 1) From ddc4e755834f298eacf01cbfd0530dc268163b81 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 10:31:43 -0700 Subject: [PATCH 23/95] Add new dimensions properties, helper creation function Part of #3925 --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 16 +------ addons/xterm-addon-webgl/src/WebglRenderer.ts | 16 +------ src/browser/TestUtils.test.ts | 16 +------ src/browser/renderer/dom/DomRenderer.ts | 16 +------ src/browser/renderer/shared/RendererUtils.ts | 40 +++++++++++++++++ src/browser/renderer/shared/Types.d.ts | 44 ++++++++++++++----- 6 files changed, 80 insertions(+), 68 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index b657270f..405cabec 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -5,6 +5,7 @@ import { removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; +import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; @@ -50,20 +51,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { 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, - scaledCharHeight: 0, - scaledCellWidth: 0, - scaledCellHeight: 0, - scaledCharLeft: 0, - scaledCharTop: 0, - scaledCanvasWidth: 0, - scaledCanvasHeight: 0, - canvasWidth: 0, - canvasHeight: 0, - actualCellWidth: 0, - actualCellHeight: 0 - }; + this.dimensions = createRenderDimensions(); this._devicePixelRatio = this._coreBrowserService.dpr; this._updateDimensions(); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index ded9130a..1b3c068d 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -7,6 +7,7 @@ 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 { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IColorSet, ITerminal, ReadonlyColorSet } from 'browser/Types'; @@ -75,20 +76,7 @@ export class WebglRenderer extends Disposable implements IRenderer { 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, optionsService) ]; - this.dimensions = { - scaledCharWidth: 0, - scaledCharHeight: 0, - scaledCellWidth: 0, - scaledCellHeight: 0, - scaledCharLeft: 0, - scaledCharTop: 0, - scaledCanvasWidth: 0, - scaledCanvasHeight: 0, - canvasWidth: 0, - canvasHeight: 0, - actualCellWidth: 0, - actualCellHeight: 0 - }; + this.dimensions = createRenderDimensions(); this._devicePixelRatio = this._coreBrowserService.dpr; this._updateDimensions(); this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 97ad90d8..d55e961f 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -18,6 +18,7 @@ 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'; +import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; export class TestTerminal extends Terminal { public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; } @@ -372,20 +373,7 @@ export class MockRenderService implements IRenderService { 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, - scaledCellWidth: 0, - scaledCellHeight: 0, - scaledCharLeft: 0, - scaledCharTop: 0, - scaledCanvasWidth: 0, - scaledCanvasHeight: 0, - canvasWidth: 0, - canvasHeight: 0, - actualCellWidth: 0, - actualCellHeight: 0 - }; + public dimensions: IRenderDimensions = createRenderDimensions(); public refreshRows(start: number, end: number): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 02738b5b..f1508631 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,6 +13,7 @@ import { IOptionsService, IBufferService, IInstantiationService } from 'common/s import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; +import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -64,20 +65,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._selectionContainer.classList.add(SELECTION_CLASS); this._selectionContainer.setAttribute('aria-hidden', 'true'); - this.dimensions = { - scaledCharWidth: 0, - scaledCharHeight: 0, - scaledCellWidth: 0, - scaledCellHeight: 0, - scaledCharLeft: 0, - scaledCharTop: 0, - scaledCanvasWidth: 0, - scaledCanvasHeight: 0, - canvasWidth: 0, - canvasHeight: 0, - actualCellWidth: 0, - actualCellHeight: 0 - }; + this.dimensions = createRenderDimensions(); this._updateDimensions(); this.register(this._optionsService.onOptionChange(() => this._handleOptionsChanged())); diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 0f60dc29..37743277 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -3,6 +3,8 @@ * @license MIT */ +import { IDimensions, IOffset, IRenderDimensions } from 'browser/renderer/shared/Types'; + export function throwIfFalsy(value: T | undefined | null): T { if (!value) { throw new Error('value must not be falsy'); @@ -28,3 +30,41 @@ function isBoxOrBlockGlyph(codepoint: number): boolean { export function excludeFromContrastRatioDemands(codepoint: number): boolean { return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint); } + +export function createRenderDimensions(): IRenderDimensions { + return { + css: { + canvas: createDimension(), + cell: createDimension() + }, + device: { + canvas: createDimension(), + cell: createDimension(), + char: { + width: 0, + height: 0, + left: 0, + top: 0 + } + }, + scaledCharWidth: 0, + scaledCharHeight: 0, + scaledCellWidth: 0, + scaledCellHeight: 0, + scaledCharLeft: 0, + scaledCharTop: 0, + scaledCanvasWidth: 0, + scaledCanvasHeight: 0, + canvasWidth: 0, + canvasHeight: 0, + actualCellWidth: 0, + actualCellHeight: 0 + }; +} + +function createDimension(): IDimensions { + return { + width: 0, + height: 0 + }; +} diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 61a90890..5c94fe61 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -27,19 +27,39 @@ export interface ICharAtlasConfig { colors: IColorSet; } +export interface IDimensions { + width: number; + height: number; +} + +export interface IOffset { + top: number; + left: number; +} + 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; + css: { + canvas: IDimensions; + cell: IDimensions; + }; + device: { + canvas: IDimensions; + cell: IDimensions; + char: IDimensions & IOffset; + }; + + /** @deprecated */ scaledCharWidth: number; + /** @deprecated */ scaledCharHeight: number; + /** @deprecated */ scaledCellWidth: number; + /** @deprecated */ scaledCellHeight: number; + /** @deprecated */ scaledCharLeft: number; + /** @deprecated */ scaledCharTop: number; + /** @deprecated */ scaledCanvasWidth: number; + /** @deprecated */ scaledCanvasHeight: number; + /** @deprecated */ canvasWidth: number; + /** @deprecated */ canvasHeight: number; + /** @deprecated */ actualCellWidth: number; + /** @deprecated */ actualCellHeight: number; } export interface IRequestRedrawEvent { From 65f41a151ecd09128f2e9d0daa69a6d047fe6f36 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 13:15:12 -0700 Subject: [PATCH 24/95] Use new render dimensions in webglrenderer --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 40 ++++++++++++++----- src/browser/renderer/shared/Types.d.ts | 6 +++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1b3c068d..fbe4e5a5 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -10,7 +10,7 @@ import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixe import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { IColorSet, ITerminal, ReadonlyColorSet } from 'browser/Types'; +import { 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'; @@ -41,7 +41,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _rectangleRenderer!: RectangleRenderer; private _glyphRenderer!: GlyphRenderer; - public dimensions: IRenderDimensions; + public readonly dimensions: IRenderDimensions; private _core: ITerminal; private _isAttached: boolean; @@ -165,14 +165,14 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Resize the canvas - this._canvas.width = this.dimensions.scaledCanvasWidth; - this._canvas.height = this.dimensions.scaledCanvasHeight; - this._canvas.style.width = `${this.dimensions.canvasWidth}px`; - this._canvas.style.height = `${this.dimensions.canvasHeight}px`; + this._canvas.width = this.dimensions.device.canvas.width; + this._canvas.height = this.dimensions.device.canvas.height; + this._canvas.style.width = `${this.dimensions.css.canvas.width}px`; + this._canvas.style.height = `${this.dimensions.css.canvas.height}px`; // Resize the screen - this._core.screenElement!.style.width = `${this.dimensions.canvasWidth}px`; - this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`; + this._core.screenElement!.style.width = `${this.dimensions.css.canvas.width}px`; + this._core.screenElement!.style.height = `${this.dimensions.css.canvas.height}px`; this._rectangleRenderer.setDimensions(this.dimensions); this._rectangleRenderer.handleResize(); @@ -244,13 +244,21 @@ export class WebglRenderer extends Disposable implements IRenderer { * Refreshes the char atlas, aquiring a new one if necessary. */ private _refreshCharAtlas(): void { - if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) { + if (this.dimensions.device.char.width <= 0 && this.dimensions.device.char.height <= 0) { // Mark as not attached so char atlas gets refreshed on next render this._isAttached = false; return; } - const atlas = acquireTextureAtlas(this._terminal, this._themeService.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.device.cell.width, + this.dimensions.device.cell.width, + this.dimensions.device.char.width, + this.dimensions.device.char.height, + this._coreBrowserService.dpr + ); if (this._charAtlas !== atlas) { this._onChangeTextureAtlas.fire(atlas.cacheCanvas); } @@ -442,32 +450,40 @@ export class WebglRenderer extends Disposable implements IRenderer { // Calculate the scaled character width. Width is floored as it must be drawn to an integer grid // in order for the char atlas glyphs to not be blurry. this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); + this.dimensions.device.char.width = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case devicePixelRatio is a // floating point number in order to ensure there is enough space to draw the character to the // cell. this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); + this.dimensions.device.char.height = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); // Calculate the scaled cell height, if lineHeight is _not_ 1, the resulting value will be // floored since lineHeight can never be lower then 1, this guarentees the scaled cell height // will always be larger than scaled char height. this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._terminal.options.lineHeight); // Calculate the y offset within a cell that glyph should draw at in order for it to be centered // correctly within the cell. this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.device.char.top = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2); // Calculate the scaled cell width, taking the letterSpacing into account. this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); + this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._terminal.options.letterSpacing); // Calculate the x offset with a cell that text should draw from in order for it to be centered // correctly within the cell. this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); + this.dimensions.device.char.left = Math.floor(this._terminal.options.letterSpacing / 2); // Recalculate the canvas dimensions, the scaled dimensions define the actual number of pixel in // the canvas this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height; + this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width; // The the size of the canvas on the page. It's important that this rounds to nearest integer // and not ceils as browsers often have floating point precision issues where @@ -476,6 +492,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // large for the canvas element size. this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); + this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio); + this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio); // Get the CSS dimensions of an individual cell. This needs to be derived from the calculated // device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the @@ -483,6 +501,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // size on the canvas can differ. this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; + this.dimensions.css.cell.height = this.dimensions.device.cell.height / this._devicePixelRatio; + this.dimensions.css.cell.width = this.dimensions.device.cell.width / this._devicePixelRatio; } private _setCanvasDevicePixelDimensions(width: number, height: number): void { diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 5c94fe61..4da1d8d2 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -38,10 +38,16 @@ export interface IOffset { } export interface IRenderDimensions { + /** + * Dimensions measured in CSS pixels (ie. device pixels / device pixel ratio). + */ css: { canvas: IDimensions; cell: IDimensions; }; + /** + * Dimensions measured in actual pixels as rendered to the device. + */ device: { canvas: IDimensions; cell: IDimensions; From b393008b542191d863c041eda2af6329a1a047f3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 13:19:04 -0700 Subject: [PATCH 25/95] Use new render dimensions in canvasrenderer --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 18 +++++++++++++++--- addons/xterm-addon-canvas/src/Types.d.ts | 18 +----------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 405cabec..f8ab3eef 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -8,7 +8,7 @@ import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixe import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { IColorSet, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; +import { ILinkifier2 } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -152,22 +152,34 @@ export class CanvasRenderer extends Disposable implements IRenderer { // See the WebGL renderer for an explanation of this section. const dpr = this._coreBrowserService.dpr; this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * dpr); + this.dimensions.device.char.width = Math.floor(this._charSizeService.width * dpr); this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * dpr); + this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr); this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); + this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight); this.dimensions.scaledCharTop = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2); this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); + this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing); this.dimensions.scaledCharLeft = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); + this.dimensions.device.char.left = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight; + this.dimensions.device.canvas.height = this._bufferService.rows * this.dimensions.device.cell.height; this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth; + this.dimensions.device.canvas.width = this._bufferService.cols * this.dimensions.device.cell.width; this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / dpr); + this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / dpr); + this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr); this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; + this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows; this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; + this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols; } private _setCanvasDevicePixelDimensions(width: number, height: number): void { - this.dimensions.scaledCanvasHeight = height; - this.dimensions.scaledCanvasWidth = width; + this.dimensions.device.canvas.height = height; + this.dimensions.device.canvas.width = width; // Resize all render layers for (const l of this._renderLayers) { l.resize(this.dimensions); diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index edd5c2ea..7e582535 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -4,24 +4,8 @@ */ import { IDisposable } from 'common/Types'; -import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { IEvent } from 'common/EventEmitter'; - -// TODO: Use core interfaces -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; -} +import { IRenderDimensions } from 'browser/renderer/shared/Types'; export interface IRequestRedrawEvent { start: number; From b0f1c38fd21d75773bca6a77079e5a0ecc64d658 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 13:44:52 -0700 Subject: [PATCH 26/95] Move all other refs in canvas over --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 126 +++++++++--------- .../xterm-addon-canvas/src/TextRenderLayer.ts | 4 +- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 29c85558..ccd1f56f 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -24,12 +24,12 @@ import { Disposable, toDisposable } from 'common/Lifecycle'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; - private _scaledCharWidth: number = 0; - private _scaledCharHeight: number = 0; - private _scaledCellWidth: number = 0; - private _scaledCellHeight: number = 0; - private _scaledCharLeft: number = 0; - private _scaledCharTop: number = 0; + private _deviceCharWidth: number = 0; + private _deviceCharHeight: number = 0; + private _deviceCellWidth: number = 0; + private _deviceCellHeight: number = 0; + private _deviceCharLeft: number = 0; + private _deviceCharTop: number = 0; protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); private _cellColorResolver: CellColorResolver; @@ -112,25 +112,25 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * @param colorSet The color set to use for the char atlas. */ private _refreshCharAtlas(colorSet: ReadonlyColorSet): void { - if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { + if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) { return; } - this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); this._bitmapGenerator = new BitmapGenerator(this._charAtlas.cacheCanvas); } public resize(dim: IRenderDimensions): void { - this._scaledCellWidth = dim.scaledCellWidth; - this._scaledCellHeight = dim.scaledCellHeight; - this._scaledCharWidth = dim.scaledCharWidth; - this._scaledCharHeight = dim.scaledCharHeight; - this._scaledCharLeft = dim.scaledCharLeft; - this._scaledCharTop = dim.scaledCharTop; - this._canvas.width = dim.scaledCanvasWidth; - this._canvas.height = dim.scaledCanvasHeight; - this._canvas.style.width = `${dim.canvasWidth}px`; - this._canvas.style.height = `${dim.canvasHeight}px`; + this._deviceCellWidth = dim.device.cell.width; + this._deviceCellHeight = dim.device.cell.height; + this._deviceCharWidth = dim.device.char.width; + this._deviceCharHeight = dim.device.char.height; + this._deviceCharLeft = dim.device.char.left; + this._deviceCharTop = dim.device.char.top; + this._canvas.width = dim.device.canvas.width; + this._canvas.height = dim.device.canvas.height; + this._canvas.style.width = `${dim.css.canvas.width}px`; + this._canvas.style.height = `${dim.css.canvas.height}px`; // Draw the background if this is an opaque layer if (!this._alpha) { @@ -155,10 +155,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer */ protected _fillCells(x: number, y: number, width: number, height: number): void { this._ctx.fillRect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, - width * this._scaledCellWidth, - height * this._scaledCellHeight); + x * this._deviceCellWidth, + y * this._deviceCellHeight, + width * this._deviceCellWidth, + height * this._deviceCellHeight); } /** @@ -168,11 +168,11 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * @param y The row to fill. */ protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void { - const cellOffset = Math.ceil(this._scaledCellHeight * 0.5); + const cellOffset = Math.ceil(this._deviceCellHeight * 0.5); this._ctx.fillRect( - x * this._scaledCellWidth, - (y + 1) * this._scaledCellHeight - cellOffset - this._coreBrowserService.dpr, - width * this._scaledCellWidth, + x * this._deviceCellWidth, + (y + 1) * this._deviceCellHeight - cellOffset - this._coreBrowserService.dpr, + width * this._deviceCellWidth, this._coreBrowserService.dpr); } @@ -184,9 +184,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer */ protected _fillBottomLineAtCells(x: number, y: number, width: number = 1, pixelOffset: number = 0): void { this._ctx.fillRect( - x * this._scaledCellWidth, - (y + 1) * this._scaledCellHeight + pixelOffset - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */, - width * this._scaledCellWidth, + x * this._deviceCellWidth, + (y + 1) * this._deviceCellHeight + pixelOffset - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */, + width * this._deviceCellWidth, this._coreBrowserService.dpr); } @@ -197,10 +197,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer const lineWidth = this._coreBrowserService.dpr; this._ctx.lineWidth = lineWidth; for (let xOffset = 0; xOffset < width; xOffset++) { - const xLeft = (x + xOffset) * this._scaledCellWidth; - const xMid = (x + xOffset + 0.5) * this._scaledCellWidth; - const xRight = (x + xOffset + 1) * this._scaledCellWidth; - const yMid = (y + 1) * this._scaledCellHeight - lineWidth - 1; + const xLeft = (x + xOffset) * this._deviceCellWidth; + const xMid = (x + xOffset + 0.5) * this._deviceCellWidth; + const xRight = (x + xOffset + 1) * this._deviceCellWidth; + const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1; const yMidBot = yMid - lineWidth; const yMidTop = yMid + lineWidth; this._ctx.moveTo(xLeft, yMid); @@ -226,12 +226,12 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer const lineWidth = this._coreBrowserService.dpr; this._ctx.lineWidth = lineWidth; this._ctx.setLineDash([lineWidth * 2, lineWidth]); - const xLeft = x * this._scaledCellWidth; - const yMid = (y + 1) * this._scaledCellHeight - lineWidth - 1; + const xLeft = x * this._deviceCellWidth; + const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1; this._ctx.moveTo(xLeft, yMid); for (let xOffset = 0; xOffset < width; xOffset++) { - // const xLeft = x * this._scaledCellWidth; - const xRight = (x + width + xOffset) * this._scaledCellWidth; + // const xLeft = x * this._deviceCellWidth; + const xRight = (x + width + xOffset) * this._deviceCellWidth; this._ctx.lineTo(xRight, yMid); } this._ctx.stroke(); @@ -246,9 +246,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer const lineWidth = this._coreBrowserService.dpr; this._ctx.lineWidth = lineWidth; this._ctx.setLineDash([lineWidth * 4, lineWidth * 3]); - const xLeft = x * this._scaledCellWidth; - const xRight = (x + width) * this._scaledCellWidth; - const yMid = (y + 1) * this._scaledCellHeight - lineWidth - 1; + const xLeft = x * this._deviceCellWidth; + const xRight = (x + width) * this._deviceCellWidth; + const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1; this._ctx.moveTo(xLeft, yMid); this._ctx.lineTo(xRight, yMid); this._ctx.stroke(); @@ -264,10 +264,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer */ protected _fillLeftLineAtCell(x: number, y: number, width: number): void { this._ctx.fillRect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, + x * this._deviceCellWidth, + y * this._deviceCellHeight, this._coreBrowserService.dpr * width, - this._scaledCellHeight); + this._deviceCellHeight); } /** @@ -280,10 +280,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer const lineWidth = this._coreBrowserService.dpr; this._ctx.lineWidth = lineWidth; this._ctx.strokeRect( - x * this._scaledCellWidth + lineWidth / 2, - y * this._scaledCellHeight + (lineWidth / 2), - width * this._scaledCellWidth - lineWidth, - (height * this._scaledCellHeight) - lineWidth); + x * this._deviceCellWidth + lineWidth / 2, + y * this._deviceCellHeight + (lineWidth / 2), + width * this._deviceCellWidth - lineWidth, + (height * this._deviceCellHeight) - lineWidth); } /** @@ -308,17 +308,17 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected _clearCells(x: number, y: number, width: number, height: number): void { if (this._alpha) { this._ctx.clearRect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, - width * this._scaledCellWidth, - height * this._scaledCellHeight); + x * this._deviceCellWidth, + y * this._deviceCellHeight, + width * this._deviceCellWidth, + height * this._deviceCellHeight); } else { this._ctx.fillStyle = this._themeService.colors.background.css; this._ctx.fillRect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, - width * this._scaledCellWidth, - height * this._scaledCellHeight); + x * this._deviceCellWidth, + y * this._deviceCellHeight, + width * this._deviceCellWidth, + height * this._deviceCellHeight); } } @@ -338,15 +338,15 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer // 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); + drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._deviceCellWidth, y * this._deviceCellHeight, this._deviceCellWidth, this._deviceCellHeight, 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); + x * this._deviceCellWidth + this._deviceCharLeft, + y * this._deviceCellHeight + this._deviceCharTop + this._deviceCharHeight); } } @@ -376,8 +376,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer glyph.texturePosition.y, glyph.size.x, glyph.size.y, - x * this._scaledCellWidth - glyph.offset.x, - y * this._scaledCellHeight - glyph.offset.y, + x * this._deviceCellWidth - glyph.offset.x, + y * this._deviceCellHeight - glyph.offset.y, glyph.size.x, glyph.size.y ); @@ -392,9 +392,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._ctx.beginPath(); this._ctx.rect( 0, - y * this._scaledCellHeight, - this._bufferService.cols * this._scaledCellWidth, - this._scaledCellHeight); + y * this._deviceCellHeight, + this._bufferService.cols * this._deviceCellWidth, + this._deviceCellHeight); this._ctx.clip(); } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index e2a35751..66fc5106 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -53,8 +53,8 @@ export class TextRenderLayer extends BaseRenderLayer { // Clear the character width cache if the font or width has changed const terminalFont = this._getFont(false, false); - if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) { - this._characterWidth = dim.scaledCharWidth; + if (this._characterWidth !== dim.device.char.width || this._characterFont !== terminalFont) { + this._characterWidth = dim.device.char.width; this._characterFont = terminalFont; this._characterOverlapCache = {}; } From 2c9e711cae3fe9a9db90c4f0528885c85449da7c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 13:48:48 -0700 Subject: [PATCH 27/95] Move all other refs in webgl over --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 18 ++-- .../src/RectangleRenderer.ts | 26 +++--- addons/xterm-addon-webgl/src/WebglRenderer.ts | 16 ++-- .../src/renderLayer/BaseRenderLayer.ts | 92 +++++++++---------- .../src/renderLayer/CursorRenderLayer.ts | 1 - 5 files changed, 76 insertions(+), 77 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 88475f18..4bce546a 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -204,15 +204,15 @@ export class GlyphRenderer extends Disposable { $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext); } - $leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2); + $leftCellPadding = Math.floor((this._dimensions.device.cell.width - this._dimensions.device.char.width) / 2); if (bg !== lastBg && $glyph.offset.x > $leftCellPadding) { $clippedPixels = $glyph.offset.x - $leftCellPadding; // a_origin - array[$i ] = -($glyph.offset.x - $clippedPixels) + this._dimensions.scaledCharLeft; - array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop; + array[$i ] = -($glyph.offset.x - $clippedPixels) + this._dimensions.device.char.left; + array[$i + 1] = -$glyph.offset.y + this._dimensions.device.char.top; // a_size - array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.scaledCanvasWidth; - array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight; + array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.device.canvas.width; + array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height; // a_texcoord array[$i + 4] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width; array[$i + 5] = $glyph.texturePositionClipSpace.y; @@ -221,11 +221,11 @@ export class GlyphRenderer extends Disposable { array[$i + 7] = $glyph.sizeClipSpace.y; } else { // a_origin - array[$i ] = -$glyph.offset.x + this._dimensions.scaledCharLeft; - array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop; + array[$i ] = -$glyph.offset.x + this._dimensions.device.char.left; + array[$i + 1] = -$glyph.offset.y + this._dimensions.device.char.top; // a_size - array[$i + 2] = $glyph.size.x / this._dimensions.scaledCanvasWidth; - array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight; + array[$i + 2] = $glyph.size.x / this._dimensions.device.canvas.width; + array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height; // a_texcoord array[$i + 4] = $glyph.texturePositionClipSpace.x; array[$i + 5] = $glyph.texturePositionClipSpace.y; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ca5cb9a9..f45ae3df 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -176,8 +176,8 @@ export class RectangleRenderer extends Disposable { 0, 0, 0, - this._terminal.cols * this._dimensions.scaledCellWidth, - this._terminal.rows * this._dimensions.scaledCellHeight, + this._terminal.cols * this._dimensions.device.cell.width, + this._terminal.rows * this._dimensions.device.cell.height, this._bgFloat ); } @@ -265,21 +265,21 @@ export class RectangleRenderer extends Disposable { if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); } - $x1 = startX * this._dimensions.scaledCellWidth; - $y1 = y * this._dimensions.scaledCellHeight; + $x1 = startX * this._dimensions.device.cell.width; + $y1 = y * this._dimensions.device.cell.height; $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, $x1, $y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, $r, $g, $b, $a); + this._addRectangle(vertices.attributes, offset, $x1, $y1, (endX - startX) * this._dimensions.device.cell.width, this._dimensions.device.cell.height, $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 { - array[offset ] = x1 / this._dimensions.scaledCanvasWidth; - array[offset + 1] = y1 / this._dimensions.scaledCanvasHeight; - array[offset + 2] = width / this._dimensions.scaledCanvasWidth; - array[offset + 3] = height / this._dimensions.scaledCanvasHeight; + array[offset ] = x1 / this._dimensions.device.canvas.width; + array[offset + 1] = y1 / this._dimensions.device.canvas.height; + array[offset + 2] = width / this._dimensions.device.canvas.width; + array[offset + 3] = height / this._dimensions.device.canvas.height; array[offset + 4] = r; array[offset + 5] = g; array[offset + 6] = b; @@ -287,10 +287,10 @@ export class RectangleRenderer extends Disposable { } private _addRectangleFloat(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, color: Float32Array): void { - array[offset ] = x1 / this._dimensions.scaledCanvasWidth; - array[offset + 1] = y1 / this._dimensions.scaledCanvasHeight; - array[offset + 2] = width / this._dimensions.scaledCanvasWidth; - array[offset + 3] = height / this._dimensions.scaledCanvasHeight; + array[offset ] = x1 / this._dimensions.device.canvas.width; + array[offset + 1] = y1 / this._dimensions.device.canvas.height; + array[offset + 2] = width / this._dimensions.device.canvas.width; + array[offset + 3] = height / this._dimensions.device.canvas.height; array[offset + 4] = color[0]; array[offset + 5] = color[1]; array[offset + 6] = color[2]; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index fbe4e5a5..70795dda 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -447,20 +447,20 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - // Calculate the scaled character width. Width is floored as it must be drawn to an integer grid + // Calculate the device character width. Width is floored as it must be drawn to an integer grid // in order for the char atlas glyphs to not be blurry. this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); this.dimensions.device.char.width = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); - // Calculate the scaled character height. Height is ceiled in case devicePixelRatio is a + // Calculate the device character height. Height is ceiled in case devicePixelRatio is a // floating point number in order to ensure there is enough space to draw the character to the // cell. this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); this.dimensions.device.char.height = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); - // Calculate the scaled cell height, if lineHeight is _not_ 1, the resulting value will be - // floored since lineHeight can never be lower then 1, this guarentees the scaled cell height - // will always be larger than scaled char height. + // Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be + // floored since lineHeight can never be lower then 1, this guarentees the device cell height + // will always be larger than device char height. this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._terminal.options.lineHeight); @@ -469,7 +469,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); this.dimensions.device.char.top = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2); - // Calculate the scaled cell width, taking the letterSpacing into account. + // Calculate the device cell width, taking the letterSpacing into account. this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._terminal.options.letterSpacing); @@ -478,7 +478,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); this.dimensions.device.char.left = Math.floor(this._terminal.options.letterSpacing / 2); - // Recalculate the canvas dimensions, the scaled dimensions define the actual number of pixel in + // Recalculate the canvas dimensions, the device dimensions define the actual number of pixel in // the canvas this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; @@ -509,7 +509,7 @@ export class WebglRenderer extends Disposable implements IRenderer { if (this._canvas.width === width && this._canvas.height === height) { return; } - // While the actual canvas size has changed, keep scaledCanvasWidth/Height as the value before + // While the actual canvas size has changed, keep device canvas dimensions as the value before // the change as it's an exact multiple of the cell sizes. this._canvas.width = width; this._canvas.height = height; diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index aa08b583..e30ef25b 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -17,12 +17,12 @@ import { Disposable, toDisposable } from 'common/Lifecycle'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; - private _scaledCharWidth: number = 0; - private _scaledCharHeight: number = 0; - private _scaledCellWidth: number = 0; - private _scaledCellHeight: number = 0; - private _scaledCharLeft: number = 0; - private _scaledCharTop: number = 0; + private _deviceCharWidth: number = 0; + private _deviceCharHeight: number = 0; + private _deviceCellWidth: number = 0; + private _deviceCellHeight: number = 0; + private _deviceCharLeft: number = 0; + private _deviceCharTop: number = 0; protected _charAtlas: ITextureAtlas | undefined; @@ -90,24 +90,24 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer * @param colorSet The color set to use for the char atlas. */ private _refreshCharAtlas(terminal: Terminal, colorSet: ReadonlyColorSet): void { - if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { + if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) { return; } - this._charAtlas = acquireTextureAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + this._charAtlas = acquireTextureAtlas(terminal, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } public resize(terminal: Terminal, dim: IRenderDimensions): void { - this._scaledCellWidth = dim.scaledCellWidth; - this._scaledCellHeight = dim.scaledCellHeight; - this._scaledCharWidth = dim.scaledCharWidth; - this._scaledCharHeight = dim.scaledCharHeight; - this._scaledCharLeft = dim.scaledCharLeft; - this._scaledCharTop = dim.scaledCharTop; - this._canvas.width = dim.scaledCanvasWidth; - this._canvas.height = dim.scaledCanvasHeight; - this._canvas.style.width = `${dim.canvasWidth}px`; - this._canvas.style.height = `${dim.canvasHeight}px`; + this._deviceCellWidth = dim.device.cell.width; + this._deviceCellHeight = dim.device.cell.height; + this._deviceCharWidth = dim.device.char.width; + this._deviceCharHeight = dim.device.char.height; + this._deviceCharLeft = dim.device.char.left; + this._deviceCharTop = dim.device.char.top; + this._canvas.width = dim.device.canvas.width; + this._canvas.height = dim.device.canvas.height; + this._canvas.style.width = `${dim.css.canvas.width}px`; + this._canvas.style.height = `${dim.css.canvas.height}px`; // Draw the background if this is an opaque layer if (!this._alpha) { @@ -128,10 +128,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer */ protected _fillCells(x: number, y: number, width: number, height: number): void { this._ctx.fillRect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, - width * this._scaledCellWidth, - height * this._scaledCellHeight); + x * this._deviceCellWidth, + y * this._deviceCellHeight, + width * this._deviceCellWidth, + height * this._deviceCellHeight); } /** @@ -142,9 +142,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer */ protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void { this._ctx.fillRect( - x * this._scaledCellWidth, - (y + 1) * this._scaledCellHeight - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */, - width * this._scaledCellWidth, + x * this._deviceCellWidth, + (y + 1) * this._deviceCellHeight - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */, + width * this._deviceCellWidth, this._coreBrowserService.dpr); } @@ -156,10 +156,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer */ protected _fillLeftLineAtCell(x: number, y: number, width: number): void { this._ctx.fillRect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, + x * this._deviceCellWidth, + y * this._deviceCellHeight, this._coreBrowserService.dpr * width, - this._scaledCellHeight); + this._deviceCellHeight); } /** @@ -171,10 +171,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void { this._ctx.lineWidth = this._coreBrowserService.dpr; this._ctx.strokeRect( - x * this._scaledCellWidth + this._coreBrowserService.dpr / 2, - y * this._scaledCellHeight + (this._coreBrowserService.dpr / 2), - width * this._scaledCellWidth - this._coreBrowserService.dpr, - (height * this._scaledCellHeight) - this._coreBrowserService.dpr); + x * this._deviceCellWidth + this._coreBrowserService.dpr / 2, + y * this._deviceCellHeight + (this._coreBrowserService.dpr / 2), + width * this._deviceCellWidth - this._coreBrowserService.dpr, + (height * this._deviceCellHeight) - this._coreBrowserService.dpr); } /** @@ -199,17 +199,17 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected _clearCells(x: number, y: number, width: number, height: number): void { if (this._alpha) { this._ctx.clearRect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, - width * this._scaledCellWidth, - height * this._scaledCellHeight); + x * this._deviceCellWidth, + y * this._deviceCellHeight, + width * this._deviceCellWidth, + height * this._deviceCellHeight); } else { this._ctx.fillStyle = this._themeService.colors.background.css; this._ctx.fillRect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, - width * this._scaledCellWidth, - height * this._scaledCellHeight); + x * this._deviceCellWidth, + y * this._deviceCellHeight, + width * this._deviceCellWidth, + height * this._deviceCellHeight); } } @@ -228,8 +228,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._clipCell(x, y, cell.getWidth()); this._ctx.fillText( cell.getChars(), - x * this._scaledCellWidth + this._scaledCharLeft, - y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight); + x * this._deviceCellWidth + this._deviceCharLeft, + y * this._deviceCellHeight + this._deviceCharTop + this._deviceCharHeight); } /** @@ -241,10 +241,10 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer private _clipCell(x: number, y: number, width: number): void { this._ctx.beginPath(); this._ctx.rect( - x * this._scaledCellWidth, - y * this._scaledCellHeight, - width * this._scaledCellWidth, - this._scaledCellHeight); + x * this._deviceCellWidth, + y * this._deviceCellHeight, + width * this._deviceCellWidth, + this._deviceCellHeight); this._ctx.clip(); } diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index a6325dcb..cb288f24 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -7,7 +7,6 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; 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, IThemeService } from 'browser/services/Services'; From 89e0b87eace779df37b4e3d75076182b2af6dd17 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 14:22:04 -0700 Subject: [PATCH 28/95] Move to device/css in domrenderer --- src/browser/Viewport.ts | 8 ++-- src/browser/renderer/dom/DomRenderer.ts | 52 +++++++++++++++---------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 700c9e22..0ba7c459 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -26,7 +26,7 @@ interface ISmoothScrollState { export class Viewport extends Disposable implements IViewport { public scrollBarWidth: number = 0; private _currentRowHeight: number = 0; - private _currentScaledCellHeight: number = 0; + private _currentDeviceCellHeight: number = 0; private _lastRecordedBufferLength: number = 0; private _lastRecordedViewportHeight: number = 0; private _lastRecordedBufferHeight: number = 0; @@ -104,8 +104,8 @@ export class Viewport extends Disposable implements IViewport { private _innerRefresh(): void { if (this._charSizeService.height > 0) { - this._currentRowHeight = this._renderService.dimensions.scaledCellHeight / this._coreBrowserService.dpr; - this._currentScaledCellHeight = this._renderService.dimensions.scaledCellHeight; + this._currentRowHeight = this._renderService.dimensions.device.cell.height / this._coreBrowserService.dpr; + this._currentDeviceCellHeight = this._renderService.dimensions.device.cell.height; this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.canvasHeight); if (this._lastRecordedBufferHeight !== newBufferHeight) { @@ -150,7 +150,7 @@ export class Viewport extends Disposable implements IViewport { } // If row height changed - if (this._renderDimensions.scaledCellHeight !== this._currentScaledCellHeight) { + if (this._renderDimensions.device.cell.height !== this._currentDeviceCellHeight) { this._refresh(immediate); return; } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index f1508631..51e9dc3e 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -3,17 +3,17 @@ * @license MIT */ -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, toDisposable } from 'common/Lifecycle'; -import { IColorSet, ILinkifierEvent, ILinkifier2, ReadonlyColorSet } from 'browser/Types'; -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'; import { removeElementFromParent } from 'browser/Dom'; +import { BOLD_CLASS, CURSOR_BLINK_CLASS, CURSOR_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory, ITALIC_CLASS } from 'browser/renderer/dom/DomRendererRowFactory'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; +import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; +import { ILinkifier2, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types'; +import { color } from 'common/Color'; +import { EventEmitter } from 'common/EventEmitter'; +import { Disposable, toDisposable } from 'common/Lifecycle'; +import { IBufferService, IInstantiationService, IOptionsService } from 'common/services/Services'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; const ROW_CONTAINER_CLASS = 'xterm-rows'; @@ -93,22 +93,34 @@ export class DomRenderer extends Disposable implements IRenderer { private _updateDimensions(): void { const dpr = this._coreBrowserService.dpr; this.dimensions.scaledCharWidth = this._charSizeService.width * dpr; + this.dimensions.device.char.width = this._charSizeService.width * dpr; this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * dpr); + this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr); this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); + this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing); this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); + this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight); this.dimensions.scaledCharLeft = 0; + this.dimensions.device.char.left = 0; this.dimensions.scaledCharTop = 0; + this.dimensions.device.char.top = 0; this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols; + this.dimensions.device.canvas.width = this.dimensions.device.cell.width * this._bufferService.cols; this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._bufferService.rows; + this.dimensions.device.canvas.height = this.dimensions.device.cell.height * this._bufferService.rows; this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / dpr); + this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr); this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / dpr); + this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr); this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; + this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols; this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; + this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows; for (const element of this._rowElements) { - element.style.width = `${this.dimensions.canvasWidth}px`; - element.style.height = `${this.dimensions.actualCellHeight}px`; - element.style.lineHeight = `${this.dimensions.actualCellHeight}px`; + element.style.width = `${this.dimensions.css.canvas.width}px`; + element.style.height = `${this.dimensions.css.cell.height}px`; + element.style.lineHeight = `${this.dimensions.css.cell.height}px`; // Make sure rows don't overflow onto following row element.style.overflow = 'hidden'; } @@ -123,14 +135,14 @@ export class DomRenderer extends Disposable implements IRenderer { ` display: inline-block;` + ` height: 100%;` + ` vertical-align: top;` + - ` width: ${this.dimensions.actualCellWidth}px` + + ` width: ${this.dimensions.css.cell.width}px` + `}`; this._dimensionsStyleElement.textContent = styles; this._selectionContainer.style.height = this._viewportElement.style.height; - this._screenElement.style.width = `${this.dimensions.canvasWidth}px`; - this._screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`; + this._screenElement.style.height = `${this.dimensions.css.canvas.height}px`; } private _injectCss(colors: ReadonlyColorSet): void { @@ -320,10 +332,10 @@ export class DomRenderer extends Disposable implements IRenderer { */ private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement { const element = document.createElement('div'); - element.style.height = `${rowCount * this.dimensions.actualCellHeight}px`; - element.style.top = `${row * this.dimensions.actualCellHeight}px`; - element.style.left = `${colStart * this.dimensions.actualCellWidth}px`; - element.style.width = `${this.dimensions.actualCellWidth * (colEnd - colStart)}px`; + element.style.height = `${rowCount * this.dimensions.css.cell.height}px`; + element.style.top = `${row * this.dimensions.css.cell.height}px`; + element.style.left = `${colStart * this.dimensions.css.cell.width}px`; + element.style.width = `${this.dimensions.css.cell.width * (colEnd - colStart)}px`; return element; } @@ -353,7 +365,7 @@ export class DomRenderer extends Disposable implements IRenderer { const row = y + this._bufferService.buffer.ydisp; const lineData = this._bufferService.buffer.lines.get(row); const cursorStyle = this._optionsService.rawOptions.cursorStyle; - rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols)); + rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.css.cell.width, this._bufferService.cols)); } } From 171ba446bdb99074583a276c3778ae129fc9184b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 15:32:02 -0700 Subject: [PATCH 29/95] Update other files --- .../test/WebglRenderer.api.ts | 12 ++-- src/browser/renderer/shared/CharAtlasCache.ts | 10 ++-- src/browser/renderer/shared/CharAtlasUtils.ts | 14 ++--- src/browser/renderer/shared/CustomGlyphs.ts | 42 ++++++------- src/browser/renderer/shared/TextureAtlas.ts | 60 +++++++++---------- src/browser/renderer/shared/Types.d.ts | 10 ++-- test/api/Terminal.api.ts | 20 +------ 7 files changed, 77 insertions(+), 91 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 53073092..b8c4f028 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -992,8 +992,8 @@ async function getCellColor(col: number, row: number): Promise { window.result = new Uint8Array(4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( - Math.floor((${col - 0.5}) * window.d.scaledCellWidth), - Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.scaledCellHeight), + Math.floor((${col - 0.5}) * window.d.canvas.cell.width), + Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.canvas.cell.height), 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result ); `); @@ -1003,12 +1003,12 @@ async function getCellColor(col: number, row: number): Promise { async function getCellPixels(col: number, row: number): Promise { await page.evaluate(` window.gl = window.term._core._renderService._renderer._gl; - window.result = new Uint8Array(window.d.scaledCellWidth * window.d.scaledCellHeight * 4); + window.result = new Uint8Array(window.d.canvas.cell.width * window.d.canvas.cell.height * 4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( - Math.floor(${col - 1} * window.d.scaledCellWidth), - Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.scaledCellHeight), - window.d.scaledCellWidth, window.d.scaledCellHeight, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result + Math.floor(${col - 1} * window.d.canvas.cell.width), + Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.canvas.cell.height), + window.d.canvas.cell.width, window.d.canvas.cell.height, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result ); `); return await page.evaluate(`Array.from(window.result)`); diff --git a/src/browser/renderer/shared/CharAtlasCache.ts b/src/browser/renderer/shared/CharAtlasCache.ts index e953dc46..48368a16 100644 --- a/src/browser/renderer/shared/CharAtlasCache.ts +++ b/src/browser/renderer/shared/CharAtlasCache.ts @@ -28,13 +28,13 @@ const charAtlasCache: ITextureAtlasCacheEntry[] = []; export function acquireTextureAtlas( terminal: Terminal, colors: ReadonlyColorSet, - scaledCellWidth: number, - scaledCellHeight: number, - scaledCharWidth: number, - scaledCharHeight: number, + deviceCellWidth: number, + deviceCellHeight: number, + deviceCharWidth: number, + deviceCharHeight: number, devicePixelRatio: number ): ITextureAtlas { - const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); + const newConfig = generateConfig(deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, terminal, colors, devicePixelRatio); // Check to see if the terminal already owns this config for (let i = 0; i < charAtlasCache.length; i++) { diff --git a/src/browser/renderer/shared/CharAtlasUtils.ts b/src/browser/renderer/shared/CharAtlasUtils.ts index e443168e..e69b476a 100644 --- a/src/browser/renderer/shared/CharAtlasUtils.ts +++ b/src/browser/renderer/shared/CharAtlasUtils.ts @@ -9,7 +9,7 @@ import { Terminal } from 'xterm'; 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: ReadonlyColorSet, devicePixelRatio: number): ICharAtlasConfig { +export function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, terminal: Terminal, colors: ReadonlyColorSet, devicePixelRatio: number): ICharAtlasConfig { // null out some fields that don't matter const clonedColors: IColorSet = { foreground: colors.foreground, @@ -31,10 +31,10 @@ export function generateConfig(scaledCellWidth: number, scaledCellHeight: number devicePixelRatio, letterSpacing: terminal.options.letterSpacing, lineHeight: terminal.options.lineHeight, - scaledCellWidth, - scaledCellHeight, - scaledCharWidth, - scaledCharHeight, + deviceCellWidth: deviceCellWidth, + deviceCellHeight: deviceCellHeight, + deviceCharWidth: deviceCharWidth, + deviceCharHeight: deviceCharHeight, fontFamily: terminal.options.fontFamily, fontSize: terminal.options.fontSize, fontWeight: terminal.options.fontWeight, @@ -61,8 +61,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.fontWeight === b.fontWeight && a.fontWeightBold === b.fontWeightBold && a.allowTransparency === b.allowTransparency && - a.scaledCharWidth === b.scaledCharWidth && - a.scaledCharHeight === b.scaledCharHeight && + a.deviceCharWidth === b.deviceCharWidth && + a.deviceCharHeight === b.deviceCharHeight && a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors && a.minimumContrastRatio === b.minimumContrastRatio && a.colors.foreground.rgba === b.colors.foreground.rgba && diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index b8725685..fd56376b 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -381,32 +381,32 @@ export function tryDrawCustomChar( c: string, xOffset: number, yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number, + deviceCellWidth: number, + deviceCellHeight: number, fontSize: number, devicePixelRatio: number ): boolean { const blockElementDefinition = blockElementDefinitions[c]; if (blockElementDefinition) { - drawBlockElementChar(ctx, blockElementDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); + drawBlockElementChar(ctx, blockElementDefinition, xOffset, yOffset, deviceCellWidth, deviceCellHeight); return true; } const patternDefinition = patternCharacterDefinitions[c]; if (patternDefinition) { - drawPatternChar(ctx, patternDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); + drawPatternChar(ctx, patternDefinition, xOffset, yOffset, deviceCellWidth, deviceCellHeight); return true; } const boxDrawingDefinition = boxDrawingDefinitions[c]; if (boxDrawingDefinition) { - drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight, devicePixelRatio); + drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, deviceCellWidth, deviceCellHeight, devicePixelRatio); return true; } const powerlineDefinition = powerlineDefinitions[c]; if (powerlineDefinition) { - drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight, fontSize, devicePixelRatio); + drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, deviceCellWidth, deviceCellHeight, fontSize, devicePixelRatio); return true; } @@ -418,13 +418,13 @@ function drawBlockElementChar( charDefinition: IBlockVector[], xOffset: number, yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number + deviceCellWidth: number, + deviceCellHeight: number ): void { for (let i = 0; i < charDefinition.length; i++) { const box = charDefinition[i]; - const xEighth = scaledCellWidth / 8; - const yEighth = scaledCellHeight / 8; + const xEighth = deviceCellWidth / 8; + const yEighth = deviceCellHeight / 8; ctx.fillRect( xOffset + box.x * xEighth, yOffset + box.y * yEighth, @@ -441,8 +441,8 @@ function drawPatternChar( charDefinition: number[][], xOffset: number, yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number + deviceCellWidth: number, + deviceCellHeight: number ): void { let patternSet = cachedPatterns.get(charDefinition); if (!patternSet) { @@ -492,7 +492,7 @@ function drawPatternChar( patternSet.set(fillStyle, pattern); } ctx.fillStyle = pattern; - ctx.fillRect(xOffset, yOffset, scaledCellWidth, scaledCellHeight); + ctx.fillRect(xOffset, yOffset, deviceCellWidth, deviceCellHeight); } /** @@ -540,8 +540,8 @@ function drawBoxDrawingChar( charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) }, xOffset: number, yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number, + deviceCellWidth: number, + deviceCellHeight: number, devicePixelRatio: number ): void { ctx.strokeStyle = ctx.fillStyle; @@ -551,7 +551,7 @@ function drawBoxDrawingChar( let actualInstructions: string; if (typeof instructions === 'function') { const xp = .15; - const yp = .15 / scaledCellHeight * scaledCellWidth; + const yp = .15 / deviceCellHeight * deviceCellWidth; actualInstructions = instructions(xp, yp); } else { actualInstructions = instructions; @@ -567,7 +567,7 @@ function drawBoxDrawingChar( if (!args[0] || !args[1]) { continue; } - f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, true, devicePixelRatio)); + f(ctx, translateArgs(args, deviceCellWidth, deviceCellHeight, xOffset, yOffset, true, devicePixelRatio)); } ctx.stroke(); ctx.closePath(); @@ -579,8 +579,8 @@ function drawPowerlineChar( charDefinition: IVectorShape, xOffset: number, yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number, + deviceCellWidth: number, + deviceCellHeight: number, fontSize: number, devicePixelRatio: number ): void { @@ -601,8 +601,8 @@ function drawPowerlineChar( } f(ctx, translateArgs( args, - scaledCellWidth, - scaledCellHeight, + deviceCellWidth, + deviceCellHeight, xOffset, yOffset, false, diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 162c7bdf..23deb8a3 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -98,8 +98,8 @@ export class TextureAtlas implements ITextureAtlas { this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', { alpha: true })); this._tmpCanvas = document.createElement('canvas'); - this._tmpCanvas.width = this._config.scaledCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2; - this._tmpCanvas.height = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 2; + this._tmpCanvas.width = this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2; + this._tmpCanvas.height = this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 2; this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency, willReadFrequently: true @@ -345,12 +345,12 @@ export class TextureAtlas implements ITextureAtlas { // Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used // to draw the glyph to the canvas as well as to restrict the bounding box search to ensure // giant ligatures (eg. =====>) don't impact overall performance. - const allowedWidth = this._config.scaledCellWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2; + const allowedWidth = this._config.deviceCellWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2; if (this._tmpCanvas.width < allowedWidth) { this._tmpCanvas.width = allowedWidth; } // Include line height when drawing glyphs - const allowedHeight = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 4; + const allowedHeight = this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 4; if (this._tmpCanvas.height < allowedHeight) { this._tmpCanvas.height = allowedHeight; } @@ -411,7 +411,7 @@ export class TextureAtlas implements ITextureAtlas { // Draw custom characters if applicable let customGlyph = false; if (this._config.customGlyphs !== false) { - customGlyph = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight, this._config.fontSize, this._config.devicePixelRatio); + customGlyph = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.fontSize, this._config.devicePixelRatio); } // Whether to clear pixels based on a threshold difference between the glyph color and the @@ -452,15 +452,15 @@ export class TextureAtlas implements ITextureAtlas { // Underline style/stroke this._tmpCtx.beginPath(); const xLeft = padding; - const yTop = Math.ceil(padding + this._config.scaledCharHeight) - yOffset; - const yMid = padding + this._config.scaledCharHeight + lineWidth - yOffset; - const yBot = Math.ceil(padding + this._config.scaledCharHeight + lineWidth * 2) - yOffset; + const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset; + const yMid = padding + this._config.deviceCharHeight + lineWidth - yOffset; + const yBot = Math.ceil(padding + this._config.deviceCharHeight + lineWidth * 2) - yOffset; for (let i = 0; i < chWidth; i++) { this._tmpCtx.save(); - const xChLeft = xLeft + i * this._config.scaledCellWidth; - const xChRight = xLeft + (i + 1) * this._config.scaledCellWidth; - const xChMid = xChLeft + this._config.scaledCellWidth / 2; + const xChLeft = xLeft + i * this._config.deviceCellWidth; + const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth; + const xChMid = xChLeft + this._config.deviceCellWidth / 2; switch (this._workAttributeData.extended.underlineStyle) { case UnderlineStyle.DOUBLE: this._tmpCtx.moveTo(xChLeft, yTop); @@ -471,18 +471,18 @@ export class TextureAtlas implements ITextureAtlas { case UnderlineStyle.CURLY: // Choose the bezier top and bottom based on the device pixel ratio, the curly line is // made taller when the line width is as otherwise it's not very clear otherwise. - const yCurlyBot = lineWidth <= 1 ? yBot : Math.ceil(padding + this._config.scaledCharHeight - lineWidth / 2) - yOffset; - const yCurlyTop = lineWidth <= 1 ? yTop : Math.ceil(padding + this._config.scaledCharHeight + lineWidth / 2) - yOffset; + const yCurlyBot = lineWidth <= 1 ? yBot : Math.ceil(padding + this._config.deviceCharHeight - lineWidth / 2) - yOffset; + const yCurlyTop = lineWidth <= 1 ? yTop : Math.ceil(padding + this._config.deviceCharHeight + lineWidth / 2) - yOffset; // Clip the left and right edges of the underline such that it can be drawn just outside // the edge of the cell to ensure a continuous stroke when there are multiple underlined // glyphs adjacent to one another. const clipRegion = new Path2D(); - clipRegion.rect(xChLeft, yTop, this._config.scaledCellWidth, yBot - yTop); + clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop); this._tmpCtx.clip(clipRegion); // Start 1/2 cell before and end 1/2 cells after to ensure a smooth curve with other cells - this._tmpCtx.moveTo(xChLeft - this._config.scaledCellWidth / 2, yMid); + this._tmpCtx.moveTo(xChLeft - this._config.deviceCellWidth / 2, yMid); this._tmpCtx.bezierCurveTo( - xChLeft - this._config.scaledCellWidth / 2, yCurlyTop, + xChLeft - this._config.deviceCellWidth / 2, yCurlyTop, xChLeft, yCurlyTop, xChLeft, yMid ); @@ -498,8 +498,8 @@ export class TextureAtlas implements ITextureAtlas { ); this._tmpCtx.bezierCurveTo( xChRight, yCurlyBot, - xChRight + this._config.scaledCellWidth / 2, yCurlyBot, - xChRight + this._config.scaledCellWidth / 2, yMid + xChRight + this._config.deviceCellWidth / 2, yCurlyBot, + xChRight + this._config.deviceCellWidth / 2, yMid ); break; case UnderlineStyle.DOTTED: @@ -543,11 +543,11 @@ export class TextureAtlas implements ITextureAtlas { // outline around the whole glyph, as well as additional pixels in the glyph at the top // which would increase GPU memory demands const clipRegion = new Path2D(); - clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.scaledCellWidth, yBot - yTop + Math.ceil(lineWidth / 2)); + clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.deviceCellWidth, yBot - yTop + Math.ceil(lineWidth / 2)); this._tmpCtx.clip(clipRegion); this._tmpCtx.lineWidth = this._config.devicePixelRatio * 3; this._tmpCtx.strokeStyle = backgroundColor.css; - this._tmpCtx.strokeText(chars, padding, padding + this._config.scaledCharHeight); + this._tmpCtx.strokeText(chars, padding, padding + this._config.deviceCharHeight); this._tmpCtx.restore(); } } @@ -556,21 +556,21 @@ export class TextureAtlas implements ITextureAtlas { // Draw the character if (!customGlyph) { - this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight); + this._tmpCtx.fillText(chars, padding, padding + this._config.deviceCharHeight); } // If this charcater is underscore and beyond the cell bounds, shift it up until it is visible // even on the bottom row, try for a maximum of 5 pixels. if (chars === '_' && !this._config.allowTransparency) { - let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck); + let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck); if (isBeyondCellBounds) { for (let offset = 1; offset <= 5; offset++) { this._tmpCtx.save(); this._tmpCtx.fillStyle = backgroundColor.css; this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height); this._tmpCtx.restore(); - this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight - offset); - isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck); + this._tmpCtx.fillText(chars, padding, padding + this._config.deviceCharHeight - offset); + isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck); if (!isBeyondCellBounds) { break; } @@ -585,8 +585,8 @@ export class TextureAtlas implements ITextureAtlas { this._tmpCtx.lineWidth = lineWidth; this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle; this._tmpCtx.beginPath(); - this._tmpCtx.moveTo(padding, padding + Math.floor(this._config.scaledCharHeight / 2) - yOffset); - this._tmpCtx.lineTo(padding + this._config.scaledCharWidth * chWidth, padding + Math.floor(this._config.scaledCharHeight / 2) - yOffset); + this._tmpCtx.moveTo(padding, padding + Math.floor(this._config.deviceCharHeight / 2) - yOffset); + this._tmpCtx.lineTo(padding + this._config.deviceCharWidth * chWidth, padding + Math.floor(this._config.deviceCharHeight / 2) - yOffset); this._tmpCtx.stroke(); } @@ -697,8 +697,8 @@ export class TextureAtlas implements ITextureAtlas { */ private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number): IRasterizedGlyph { boundingBox.top = 0; - const height = restrictedGlyph ? this._config.scaledCellHeight : this._tmpCanvas.height; - const width = restrictedGlyph ? this._config.scaledCellWidth : allowedWidth; + const height = restrictedGlyph ? this._config.deviceCellHeight : this._tmpCanvas.height; + const width = restrictedGlyph ? this._config.deviceCellWidth : allowedWidth; let found = false; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { @@ -770,8 +770,8 @@ export class TextureAtlas implements ITextureAtlas { y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT }, offset: { - x: -boundingBox.left + padding + ((restrictedGlyph || customGlyph) ? Math.floor((this._config.scaledCellWidth - this._config.scaledCharWidth) / 2) : 0), - y: -boundingBox.top + padding + ((restrictedGlyph || customGlyph) ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.scaledCellHeight - this._config.scaledCharHeight) / 2) : 0) + x: -boundingBox.left + padding + ((restrictedGlyph || customGlyph) ? Math.floor((this._config.deviceCellWidth - this._config.deviceCharWidth) / 2) : 0), + y: -boundingBox.top + padding + ((restrictedGlyph || customGlyph) ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.deviceCellHeight - this._config.deviceCharHeight) / 2) : 0) } }; } diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 4da1d8d2..1185e4aa 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, ReadonlyColorSet } from 'browser/Types'; +import { IColorSet } from 'browser/Types'; import { IDisposable } from 'common/Types'; import { IEvent } from 'common/EventEmitter'; @@ -17,10 +17,10 @@ export interface ICharAtlasConfig { fontFamily: string; fontWeight: FontWeight; fontWeightBold: FontWeight; - scaledCellWidth: number; - scaledCellHeight: number; - scaledCharWidth: number; - scaledCharHeight: number; + deviceCellWidth: number; + deviceCellHeight: number; + deviceCharWidth: number; + deviceCharHeight: number; allowTransparency: boolean; drawBoldTextInBrightColors: boolean; minimumContrastRatio: number; diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 27fb51a7..8564cbfa 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -7,6 +7,7 @@ import { assert } from 'chai'; import { pollFor, timeout, writeSync, openTerminal, launchBrowser } from './TestUtils'; import { Browser, Page } from 'playwright'; import { fail } from 'assert'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; const APP = 'http://127.0.0.1:3001/test'; @@ -1032,21 +1033,6 @@ interface IDimensions { renderDimensions: IRenderDimensions; } -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; -} - async function getDimensions(): Promise { return await page.evaluate(` (function() { @@ -1062,8 +1048,8 @@ async function getDimensions(): Promise { async function getCellCoordinates(dimensions: IDimensions, col: number, row: number): Promise<{ x: number, y: number }> { return { - x: dimensions.left + dimensions.renderDimensions.scaledCellWidth * (col - 0.5), - y: dimensions.top + dimensions.renderDimensions.scaledCellHeight * (row - 0.5) + x: dimensions.left + dimensions.renderDimensions.device.cell.width * (col - 0.5), + y: dimensions.top + dimensions.renderDimensions.device.cell.height * (row - 0.5) }; } From 22e8a2f8e7a8f6886effe22a6fa8705c1b29f6e4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 18:07:05 -0700 Subject: [PATCH 30/95] Remove old dimension properties --- addons/xterm-addon-canvas/src/CanvasRenderer.ts | 16 ++-------------- addons/xterm-addon-fit/src/FitAddon.ts | 8 +++++--- addons/xterm-addon-fit/src/tsconfig.json | 12 +++++++++++- addons/xterm-addon-webgl/src/WebglRenderer.ts | 12 ------------ demo/client.ts | 4 ++-- src/browser/AccessibilityManager.ts | 4 ++-- src/browser/Terminal.ts | 16 ++++++++-------- src/browser/Viewport.ts | 4 ++-- .../decorations/BufferDecorationRenderer.ts | 14 +++++++------- src/browser/input/CompositionHelper.ts | 6 +++--- src/browser/input/Mouse.ts | 10 +++++----- src/browser/renderer/dom/DomRenderer.ts | 12 ------------ src/browser/renderer/shared/RendererUtils.ts | 14 +------------- src/browser/renderer/shared/Types.d.ts | 13 ------------- src/browser/services/MouseService.ts | 12 ++++++------ src/browser/services/RenderService.ts | 2 +- src/browser/services/SelectionService.test.ts | 4 ++-- src/browser/services/SelectionService.ts | 2 +- test/api/InputHandler.api.ts | 8 ++++---- test/api/MouseTracking.api.ts | 2 +- test/api/Terminal.api.ts | 2 +- 21 files changed, 64 insertions(+), 113 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index f8ab3eef..d4563c5c 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -87,8 +87,8 @@ export class CanvasRenderer extends Disposable implements IRenderer { } // Resize the screen - this._screenElement.style.width = `${this.dimensions.canvasWidth}px`; - this._screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`; + this._screenElement.style.height = `${this.dimensions.css.canvas.height}px`; } public handleCharSizeChanged(): void { @@ -151,29 +151,17 @@ export class CanvasRenderer extends Disposable implements IRenderer { // See the WebGL renderer for an explanation of this section. const dpr = this._coreBrowserService.dpr; - this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * dpr); this.dimensions.device.char.width = Math.floor(this._charSizeService.width * dpr); - this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * dpr); this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr); - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight); - this.dimensions.scaledCharTop = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2); - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing); - this.dimensions.scaledCharLeft = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); this.dimensions.device.char.left = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); - this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight; this.dimensions.device.canvas.height = this._bufferService.rows * this.dimensions.device.cell.height; - this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth; this.dimensions.device.canvas.width = this._bufferService.cols * this.dimensions.device.cell.width; - this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / dpr); this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr); - this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / dpr); this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr); - this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows; - this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols; } diff --git a/addons/xterm-addon-fit/src/FitAddon.ts b/addons/xterm-addon-fit/src/FitAddon.ts index 7b9c228f..6b3df6f0 100644 --- a/addons/xterm-addon-fit/src/FitAddon.ts +++ b/addons/xterm-addon-fit/src/FitAddon.ts @@ -4,6 +4,7 @@ */ import { Terminal, ITerminalAddon } from 'xterm'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; interface ITerminalDimensions { /** @@ -58,8 +59,9 @@ export class FitAddon implements ITerminalAddon { // TODO: Remove reliance on private API const core = (this._terminal as any)._core; + const dims: IRenderDimensions = core._renderService.dimensions; - if (core._renderService.dimensions.actualCellWidth === 0 || core._renderService.dimensions.actualCellHeight === 0) { + if (dims.css.cell.width === 0 || dims.css.cell.height === 0) { return undefined; } @@ -81,8 +83,8 @@ export class FitAddon implements ITerminalAddon { const availableHeight = parentElementHeight - elementPaddingVer; const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth; const geometry = { - cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth)), - rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight)) + cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)), + rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height)) }; return geometry; } diff --git a/addons/xterm-addon-fit/src/tsconfig.json b/addons/xterm-addon-fit/src/tsconfig.json index f3e409d1..3bfbea67 100644 --- a/addons/xterm-addon-fit/src/tsconfig.json +++ b/addons/xterm-addon-fit/src/tsconfig.json @@ -13,10 +13,20 @@ "strict": true, "types": [ "../../../node_modules/@types/mocha" - ] + ], + "paths": { + "browser/*": [ + "../../../src/browser/*" + ] + } }, "include": [ "./**/*", "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/browser" + } ] } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 70795dda..7fa3c0aa 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -449,39 +449,31 @@ export class WebglRenderer extends Disposable implements IRenderer { // Calculate the device character width. Width is floored as it must be drawn to an integer grid // in order for the char atlas glyphs to not be blurry. - this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); this.dimensions.device.char.width = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); // Calculate the device character height. Height is ceiled in case devicePixelRatio is a // floating point number in order to ensure there is enough space to draw the character to the // cell. - this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); this.dimensions.device.char.height = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); // Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be // floored since lineHeight can never be lower then 1, this guarentees the device cell height // will always be larger than device char height. - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._terminal.options.lineHeight); // Calculate the y offset within a cell that glyph should draw at in order for it to be centered // correctly within the cell. - this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); this.dimensions.device.char.top = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2); // Calculate the device cell width, taking the letterSpacing into account. - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._terminal.options.letterSpacing); // Calculate the x offset with a cell that text should draw from in order for it to be centered // correctly within the cell. - this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); this.dimensions.device.char.left = Math.floor(this._terminal.options.letterSpacing / 2); // Recalculate the canvas dimensions, the device dimensions define the actual number of pixel in // the canvas - this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; - this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height; this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width; @@ -490,8 +482,6 @@ export class WebglRenderer extends Disposable implements IRenderer { // `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when // it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too // large for the canvas element size. - this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); - this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio); this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio); @@ -499,8 +489,6 @@ export class WebglRenderer extends Disposable implements IRenderer { // device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the // page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char // size on the canvas can differ. - this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; - this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; this.dimensions.css.cell.height = this.dimensions.device.cell.height / this._devicePixelRatio; this.dimensions.css.cell.width = this.dimensions.device.cell.width / this._devicePixelRatio; } diff --git a/demo/client.ts b/demo/client.ts index 44a96eb3..921ee155 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -618,8 +618,8 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a function updateTerminalSize(): void { 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'; + const width = (cols * term._core._renderService.dimensions.css.cell.width + term._core.viewport.scrollBarWidth).toString() + 'px'; + const height = (rows * term._core._renderService.dimensions.css.cell.height).toString() + 'px'; terminalContainer.style.width = width; terminalContainer.style.height = height; addons.fit.instance.fit(); diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index d0c9f601..3998e779 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -274,7 +274,7 @@ export class AccessibilityManager extends Disposable { } private _refreshRowsDimensions(): void { - if (!this._renderService.dimensions.actualCellHeight) { + if (!this._renderService.dimensions.css.cell.height) { return; } if (this._rowElements.length !== this._terminal.rows) { @@ -286,7 +286,7 @@ export class AccessibilityManager extends Disposable { } private _refreshRowDimensions(element: HTMLElement): void { - element.style.height = `${this._renderService.dimensions.actualCellHeight}px`; + element.style.height = `${this._renderService.dimensions.css.cell.height}px`; } private _announceCharacters(): void { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index b5262b1e..3c88b948 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -321,11 +321,11 @@ export class Terminal extends CoreTerminal implements ITerminal { return; } const cursorX = Math.min(this.buffer.x, this.cols - 1); - const cellHeight = this._renderService.dimensions.actualCellHeight; + const cellHeight = this._renderService.dimensions.css.cell.height; const width = bufferLine.getWidth(cursorX); - const cellWidth = this._renderService.dimensions.actualCellWidth * width; - const cursorTop = this.buffer.y * this._renderService.dimensions.actualCellHeight; - const cursorLeft = cursorX * this._renderService.dimensions.actualCellWidth; + const cellWidth = this._renderService.dimensions.css.cell.width * width; + const cursorTop = this.buffer.y * this._renderService.dimensions.css.cell.height; + const cursorLeft = cursorX * this._renderService.dimensions.css.cell.width; // Sync the textarea to the exact position of the composition view so the IME knows where the // text is. @@ -1273,13 +1273,13 @@ export class Terminal extends CoreTerminal implements ITerminal { switch (type) { case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS: - const canvasWidth = this._renderService.dimensions.canvasWidth.toFixed(0); - const canvasHeight = this._renderService.dimensions.canvasHeight.toFixed(0); + const canvasWidth = this._renderService.dimensions.css.canvas.width.toFixed(0); + const canvasHeight = this._renderService.dimensions.css.canvas.height.toFixed(0); this.coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`); break; case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS: - const cellWidth = this._renderService.dimensions.actualCellWidth.toFixed(0); - const cellHeight = this._renderService.dimensions.actualCellHeight.toFixed(0); + const cellWidth = this._renderService.dimensions.css.cell.width.toFixed(0); + const cellHeight = this._renderService.dimensions.css.cell.height.toFixed(0); this.coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`); break; } diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 0ba7c459..8e01f23c 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -107,7 +107,7 @@ export class Viewport extends Disposable implements IViewport { this._currentRowHeight = this._renderService.dimensions.device.cell.height / this._coreBrowserService.dpr; this._currentDeviceCellHeight = this._renderService.dimensions.device.cell.height; this._lastRecordedViewportHeight = this._viewportElement.offsetHeight; - const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.canvasHeight); + const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.css.canvas.height); if (this._lastRecordedBufferHeight !== newBufferHeight) { this._lastRecordedBufferHeight = newBufferHeight; this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px'; @@ -138,7 +138,7 @@ export class Viewport extends Disposable implements IViewport { } // If viewport height changed - if (this._lastRecordedViewportHeight !== this._renderService.dimensions.canvasHeight) { + if (this._lastRecordedViewportHeight !== this._renderService.dimensions.css.canvas.height) { this._refresh(immediate); return; } diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 5836e266..72ed6428 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -72,10 +72,10 @@ export class BufferDecorationRenderer extends Disposable { private _createElement(decoration: IInternalDecoration): HTMLElement { const element = document.createElement('div'); element.classList.add('xterm-decoration'); - element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth)}px`; - element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.actualCellHeight}px`; - element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.actualCellHeight}px`; - element.style.lineHeight = `${this._renderService.dimensions.actualCellHeight}px`; + element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.css.cell.width)}px`; + element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.css.cell.height}px`; + element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.css.cell.height}px`; + element.style.lineHeight = `${this._renderService.dimensions.css.cell.height}px`; const x = decoration.options.x ?? 0; if (x && x > this._bufferService.cols) { @@ -104,7 +104,7 @@ export class BufferDecorationRenderer extends Disposable { this._decorationElements.set(decoration, element); this._container.appendChild(element); } - element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`; + element.style.top = `${line * this._renderService.dimensions.css.cell.height}px`; element.style.display = this._altBufferIsActive ? 'none' : 'block'; decoration.onRenderEmitter.fire(element); } @@ -116,9 +116,9 @@ export class BufferDecorationRenderer extends Disposable { } const x = decoration.options.x ?? 0; if ((decoration.options.anchor || 'left') === 'right') { - element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; + element.style.right = x ? `${x * this._renderService.dimensions.css.cell.width}px` : ''; } else { - element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; + element.style.left = x ? `${x * this._renderService.dimensions.css.cell.width}px` : ''; } } diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts index ba7d4b6a..7542969a 100644 --- a/src/browser/input/CompositionHelper.ts +++ b/src/browser/input/CompositionHelper.ts @@ -218,9 +218,9 @@ export class CompositionHelper { if (this._bufferService.buffer.isCursorInViewport) { const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1); - const cellHeight = this._renderService.dimensions.actualCellHeight; - const cursorTop = this._bufferService.buffer.y * this._renderService.dimensions.actualCellHeight; - const cursorLeft = cursorX * this._renderService.dimensions.actualCellWidth; + const cellHeight = this._renderService.dimensions.css.cell.height; + const cursorTop = this._bufferService.buffer.y * this._renderService.dimensions.css.cell.height; + const cursorLeft = cursorX * this._renderService.dimensions.css.cell.width; this._compositionView.style.left = cursorLeft + 'px'; this._compositionView.style.top = cursorTop + 'px'; diff --git a/src/browser/input/Mouse.ts b/src/browser/input/Mouse.ts index 309d9265..c40a7cc7 100644 --- a/src/browser/input/Mouse.ts +++ b/src/browser/input/Mouse.ts @@ -24,13 +24,13 @@ export function getCoordsRelativeToElement(window: Pick, event: Pick, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined { +export function getCoords(window: Pick, event: Pick, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, cssCellWidth: number, cssCellHeight: number, isSelection?: boolean): [number, number] | undefined { // Coordinates cannot be measured if there are no valid if (!hasValidCharSize) { return undefined; @@ -41,8 +41,8 @@ export function getCoords(window: Pick, event: Pick< return undefined; } - coords[0] = Math.ceil((coords[0] + (isSelection ? actualCellWidth / 2 : 0)) / actualCellWidth); - coords[1] = Math.ceil(coords[1] / actualCellHeight); + coords[0] = Math.ceil((coords[0] + (isSelection ? cssCellWidth / 2 : 0)) / cssCellWidth); + coords[1] = Math.ceil(coords[1] / cssCellHeight); // Ensure coordinates are within the terminal viewport. Note that selections // need an addition point of precision to cover the end point (as characters diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 51e9dc3e..399682ac 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -92,29 +92,17 @@ export class DomRenderer extends Disposable implements IRenderer { private _updateDimensions(): void { const dpr = this._coreBrowserService.dpr; - this.dimensions.scaledCharWidth = this._charSizeService.width * dpr; this.dimensions.device.char.width = this._charSizeService.width * dpr; - this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * dpr); this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr); - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing); - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight); - this.dimensions.scaledCharLeft = 0; this.dimensions.device.char.left = 0; - this.dimensions.scaledCharTop = 0; this.dimensions.device.char.top = 0; - this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols; this.dimensions.device.canvas.width = this.dimensions.device.cell.width * this._bufferService.cols; - this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._bufferService.rows; this.dimensions.device.canvas.height = this.dimensions.device.cell.height * this._bufferService.rows; - this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / dpr); this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr); - this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / dpr); this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr); - this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols; - this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows; for (const element of this._rowElements) { diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 37743277..052f2894 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -46,19 +46,7 @@ export function createRenderDimensions(): IRenderDimensions { left: 0, top: 0 } - }, - scaledCharWidth: 0, - scaledCharHeight: 0, - scaledCellWidth: 0, - scaledCellHeight: 0, - scaledCharLeft: 0, - scaledCharTop: 0, - scaledCanvasWidth: 0, - scaledCanvasHeight: 0, - canvasWidth: 0, - canvasHeight: 0, - actualCellWidth: 0, - actualCellHeight: 0 + } }; } diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 1185e4aa..433d48a2 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -53,19 +53,6 @@ export interface IRenderDimensions { cell: IDimensions; char: IDimensions & IOffset; }; - - /** @deprecated */ scaledCharWidth: number; - /** @deprecated */ scaledCharHeight: number; - /** @deprecated */ scaledCellWidth: number; - /** @deprecated */ scaledCellHeight: number; - /** @deprecated */ scaledCharLeft: number; - /** @deprecated */ scaledCharTop: number; - /** @deprecated */ scaledCanvasWidth: number; - /** @deprecated */ scaledCanvasHeight: number; - /** @deprecated */ canvasWidth: number; - /** @deprecated */ canvasHeight: number; - /** @deprecated */ actualCellWidth: number; - /** @deprecated */ actualCellHeight: number; } export interface IRequestRedrawEvent { diff --git a/src/browser/services/MouseService.ts b/src/browser/services/MouseService.ts index 2f5550c9..38561dfd 100644 --- a/src/browser/services/MouseService.ts +++ b/src/browser/services/MouseService.ts @@ -23,8 +23,8 @@ export class MouseService implements IMouseService { colCount, rowCount, this._charSizeService.hasValidSize, - this._renderService.dimensions.actualCellWidth, - this._renderService.dimensions.actualCellHeight, + this._renderService.dimensions.css.cell.width, + this._renderService.dimensions.css.cell.height, isSelection ); } @@ -37,14 +37,14 @@ export class MouseService implements IMouseService { if (!this._charSizeService.hasValidSize || coords[0] < 0 || coords[1] < 0 - || coords[0] >= this._renderService.dimensions.canvasWidth - || coords[1] >= this._renderService.dimensions.canvasHeight) { + || coords[0] >= this._renderService.dimensions.css.canvas.width + || coords[1] >= this._renderService.dimensions.css.canvas.height) { return undefined; } return { - col: Math.floor(coords[0] / this._renderService.dimensions.actualCellWidth), - row: Math.floor(coords[1] / this._renderService.dimensions.actualCellHeight), + col: Math.floor(coords[0] / this._renderService.dimensions.css.cell.width), + row: Math.floor(coords[1] / this._renderService.dimensions.css.cell.height), x: Math.floor(coords[0]), y: Math.floor(coords[1]) }; diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 190967db..75ca2867 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -186,7 +186,7 @@ export class RenderService extends Disposable implements IRenderService { 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) { + if (this._renderer.dimensions.css.canvas.width === this._canvasWidth && this._renderer.dimensions.css.canvas.height === this._canvasHeight) { return; } this._onDimensionsChange.fire(this._renderer.dimensions); diff --git a/src/browser/services/SelectionService.test.ts b/src/browser/services/SelectionService.test.ts index f8a15e51..67158def 100644 --- a/src/browser/services/SelectionService.test.ts +++ b/src/browser/services/SelectionService.test.ts @@ -49,8 +49,8 @@ describe('SelectionService', () => { bufferService = new MockBufferService(20, 20, optionsService); buffer = bufferService.buffer; const renderService = new MockRenderService(); - renderService.dimensions.canvasHeight = 10 * 20; - renderService.dimensions.canvasWidth = 10 * 20; + renderService.dimensions.css.canvas.height = 10 * 20; + renderService.dimensions.css.canvas.width = 10 * 20; selectionService = new TestSelectionService(bufferService, optionsService, renderService); }); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 763f938b..486c1941 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -408,7 +408,7 @@ export class SelectionService extends Disposable implements ISelectionService { */ private _getMouseEventScrollAmount(event: MouseEvent): number { let offset = getCoordsRelativeToElement(this._coreBrowserService.window, event, this._screenElement)[1]; - const terminalHeight = this._renderService.dimensions.canvasHeight; + const terminalHeight = this._renderService.dimensions.css.canvas.height; if (offset >= 0 && offset <= terminalHeight) { return 0; } diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 8192bfed..d83fcb1f 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -601,9 +601,9 @@ async function getCursor(): Promise<{ col: number, row: number }> { async function getDimensions(): Promise { const dim: IRenderDimensions = await page.evaluate(`term._core._renderService.dimensions`); return { - cellWidth: dim.actualCellWidth.toFixed(0), - cellHeight: dim.actualCellHeight.toFixed(0), - width: dim.canvasWidth.toFixed(0), - height: dim.canvasHeight.toFixed(0) + cellWidth: dim.css.cell.width.toFixed(0), + cellHeight: dim.css.cell.height.toFixed(0), + width: dim.css.canvas.width.toFixed(0), + height: dim.css.canvas.height.toFixed(0) }; } diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index 1b00fee5..8a64a59a 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -48,7 +48,7 @@ async function cellPos(col: number, row: number): Promise { (function() { const rect = window.term.element.getBoundingClientRect(); const dim = term._core._renderService.dimensions; - return {left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right, width: dim.actualCellWidth, height: dim.actualCellHeight}; + return {left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right, width: dim.css.cell.width, height: dim.css.cell.height}; })(); `); return [col * coords.width + coords.left + 2, row * coords.height + coords.top + 2]; diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 8564cbfa..f1edf65c 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -721,7 +721,7 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term = new Terminal()`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.evaluate(`document.querySelector('#terminal-container').style.display=''`); - await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); + await pollFor(page, `window.term._core._renderService.dimensions.css.cell.width > 0`, true); }); describe('registerDecoration', () => { From ca68a5c59af97bcd9b9e80955223c127e0cb0577 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 15 Oct 2022 20:13:24 -0700 Subject: [PATCH 31/95] Fix typo in test --- addons/xterm-addon-webgl/test/WebglRenderer.api.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index b8c4f028..5e34aede 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -992,8 +992,8 @@ async function getCellColor(col: number, row: number): Promise { window.result = new Uint8Array(4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( - Math.floor((${col - 0.5}) * window.d.canvas.cell.width), - Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.canvas.cell.height), + Math.floor((${col - 0.5}) * window.d.device.cell.width), + Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.device.cell.height), 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result ); `); @@ -1003,12 +1003,12 @@ async function getCellColor(col: number, row: number): Promise { async function getCellPixels(col: number, row: number): Promise { await page.evaluate(` window.gl = window.term._core._renderService._renderer._gl; - window.result = new Uint8Array(window.d.canvas.cell.width * window.d.canvas.cell.height * 4); + window.result = new Uint8Array(window.d.device.cell.width * window.d.device.cell.height * 4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( - Math.floor(${col - 1} * window.d.canvas.cell.width), - Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.canvas.cell.height), - window.d.canvas.cell.width, window.d.canvas.cell.height, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result + Math.floor(${col - 1} * window.d.device.cell.width), + Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.device.cell.height), + window.d.device.cell.width, window.d.device.cell.height, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result ); `); return await page.evaluate(`Array.from(window.result)`); From dc19628014b47a015bc99033eea27ae9b780d77c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 16 Oct 2022 06:34:12 -0700 Subject: [PATCH 32/95] Fix width -> height --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 7fa3c0aa..f4eceb00 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -254,7 +254,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._terminal, this._themeService.colors, this.dimensions.device.cell.width, - this.dimensions.device.cell.width, + this.dimensions.device.cell.height, this.dimensions.device.char.width, this.dimensions.device.char.height, this._coreBrowserService.dpr From e15c6b9f3ea69f7df1a4fdc818fd33b9260e97b4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 16 Oct 2022 06:49:27 -0700 Subject: [PATCH 33/95] Warn/throw on unexpected attach addon socket state --- addons/xterm-addon-attach/src/AttachAddon.ts | 22 ++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-attach/src/AttachAddon.ts b/addons/xterm-addon-attach/src/AttachAddon.ts index 9fbd796b..7fd8df29 100644 --- a/addons/xterm-addon-attach/src/AttachAddon.ts +++ b/addons/xterm-addon-attach/src/AttachAddon.ts @@ -47,16 +47,14 @@ export class AttachAddon implements ITerminalAddon { } private _sendData(data: string): void { - // TODO: do something better than just swallowing - // the data if the socket is not in a working condition - if (this._socket.readyState !== 1) { + if (!this._checkOpenSocket()) { return; } this._socket.send(data); } private _sendBinary(data: string): void { - if (this._socket.readyState !== 1) { + if (!this._checkOpenSocket()) { return; } const buffer = new Uint8Array(data.length); @@ -65,6 +63,22 @@ export class AttachAddon implements ITerminalAddon { } this._socket.send(buffer); } + + private _checkOpenSocket(): boolean { + switch (this._socket.readyState) { + case WebSocket.OPEN: + return true; + case WebSocket.CONNECTING: + throw new Error('Attach addon was loaded before socket was open'); + case WebSocket.CLOSING: + console.warn('Attach addon socket is closing'); + return false; + case WebSocket.CLOSED: + throw new Error('Attach addon socket is closed'); + default: + throw new Error('Unexpected socket state'); + } + } } function addSocketListener(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable { From a98248439276abaa89337f59ab7fd11fb77749f0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 16 Oct 2022 06:55:04 -0700 Subject: [PATCH 34/95] Re-enable onData event test --- src/browser/Terminal.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index a9d0b772..4e0ebeab 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -9,6 +9,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { MockUnicodeService } from 'common/TestUtils.test'; import { IMarker } from 'common/Types'; +import { ICoreService } from 'common/services/Services'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -47,11 +48,10 @@ describe('Terminal', () => { }); describe('events', () => { - // TODO: Add an onData test back - // it('should fire the onData evnet', (done) => { - // term.onData(() => done()); - // term.handler('fake'); - // }); + it('should fire the onData evnet', (done) => { + term.onData(() => done()); + term.coreService.triggerDataEvent('fake'); + }); it('should fire the onCursorMove event', () => { return new Promise(async r => { term.onCursorMove(() => r()); From 7796b4101a7942f048a013de0a2a03ddb89282fe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 16 Oct 2022 06:58:20 -0700 Subject: [PATCH 35/95] Type Buffer.tabs --- src/browser/decorations/OverviewRulerRenderer.ts | 2 -- src/common/buffer/Buffer.ts | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index 90166960..d5765385 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -190,8 +190,6 @@ export class OverviewRulerRenderer extends Disposable { } private _renderColorZone(zone: IColorZone): void { - // TODO: Is _decorationElements needed? - this._ctx.fillStyle = zone.color; this._ctx.fillRect( /* x */ drawX[zone.position || 'full'], diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 7bee6dfd..c929ba2d 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -32,8 +32,7 @@ export class Buffer implements IBuffer { public x: number = 0; public scrollBottom: number; public scrollTop: number; - // TODO: Type me - public tabs: any; + public tabs: { [column: number]: boolean | undefined } = {}; public savedY: number = 0; public savedX: number = 0; public savedCurAttrData = DEFAULT_ATTR_DATA.clone(); From b560d85553d71f269f5fbe6e822b2af2b1f08f50 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 16 Oct 2022 07:04:24 -0700 Subject: [PATCH 36/95] Remove unneeded params as link provider is stable --- demo/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 921ee155..fdb46f6c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -248,8 +248,7 @@ function createTerminal(): void { addons.fit.instance = new FitAddon(); addons.unicode11.instance = new Unicode11Addon(); addons.webgl.instance = new WebglAddon(); - // TODO: Remove arguments when link provider API is the default - addons['web-links'].instance = new WebLinksAddon(undefined, undefined, true); + addons['web-links'].instance = new WebLinksAddon(); typedTerm.loadAddon(addons.fit.instance); typedTerm.loadAddon(addons.search.instance); typedTerm.loadAddon(addons.serialize.instance); From 96486c414bea233920809086c91915a0b5f62be6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 16 Oct 2022 07:04:34 -0700 Subject: [PATCH 37/95] Acquire char size service better in webgl renderer --- addons/xterm-addon-webgl/src/WebglAddon.ts | 15 +++++++++++++-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 17 ++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 1f8e2616..4f4c9dcc 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICharacterJoinerService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; +import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { ITerminal } from 'browser/Types'; import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; @@ -46,11 +46,22 @@ export class WebglAddon extends Disposable implements ITerminalAddon { const unsafeCore = core as any; 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 = this.register(new WebglRenderer(terminal, themeService, characterJoinerService, coreBrowserService, optionsService, coreService, decorationService, this._preserveDrawingBuffer)); + this._renderer = this.register(new WebglRenderer( + terminal, + characterJoinerService, + charSizeService, + coreBrowserService, + coreService, + decorationService, + optionsService, + themeService, + 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 f4eceb00..f366c379 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 { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; -import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; +import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ITerminal } from 'browser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { CellData } from 'common/buffer/CellData'; @@ -56,12 +56,13 @@ export class WebglRenderer extends Disposable implements IRenderer { constructor( private _terminal: Terminal, - private readonly _themeService: IThemeService, private readonly _characterJoinerService: ICharacterJoinerService, + private readonly _charSizeService: ICharSizeService, private readonly _coreBrowserService: ICoreBrowserService, - optionsService: IOptionsService, coreService: ICoreService, private readonly _decorationService: IDecorationService, + optionsService: IOptionsService, + private readonly _themeService: IThemeService, preserveDrawingBuffer?: boolean ) { super(); @@ -302,7 +303,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { if (!this._isAttached) { - if (this._coreBrowserService.window.document.body.contains(this._core.screenElement!) && (this._core as any)._charSizeService.width && (this._core as any)._charSizeService.height) { + if (this._coreBrowserService.window.document.body.contains(this._core.screenElement!) && this._charSizeService.width && this._charSizeService.height) { this._updateDimensions(); this._refreshCharAtlas(); this._isAttached = true; @@ -440,21 +441,19 @@ export class WebglRenderer extends Disposable implements IRenderer { * Recalculates the character and canvas dimensions. */ private _updateDimensions(): void { - // TODO: Acquire CharSizeService properly - // Perform a new measure if the CharMeasure dimensions are not yet available - if (!(this._core as any)._charSizeService.width || !(this._core as any)._charSizeService.height) { + if (!this._charSizeService.width || !this._charSizeService.height) { return; } // Calculate the device character width. Width is floored as it must be drawn to an integer grid // in order for the char atlas glyphs to not be blurry. - this.dimensions.device.char.width = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); + this.dimensions.device.char.width = Math.floor(this._charSizeService.width * this._devicePixelRatio); // Calculate the device character height. Height is ceiled in case devicePixelRatio is a // floating point number in order to ensure there is enough space to draw the character to the // cell. - this.dimensions.device.char.height = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); + this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * this._devicePixelRatio); // Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be // floored since lineHeight can never be lower then 1, this guarentees the device cell height From 478535d23671f49a5e9a30dad7d750ea7441e37f Mon Sep 17 00:00:00 2001 From: hackermon Date: Mon, 17 Oct 2022 09:49:54 -0400 Subject: [PATCH 38/95] replit branding change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c78ae76e..f672942d 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**DockerStacks**](https://docker-stacks.com/): Local LAMP/LEMP development studio - [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash. - [**Laravel Ssh Web Client**](https://github.com/roke22/Laravel-ssh-client): Laravel server inventory with ssh web client to connect at server using xterm.js -- [**Repl.it**](https://repl.it): Collaborative browser based IDE with support for 50+ different languages. +- [**Replit**](https://replit.com): Collaborative browser based IDE with support for 50+ different languages. - [**TeleType**](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot. - [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages are supported, with results displayed by xterm.js. - [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP, and Database services. From 0b84d17b18e836a37ed544df337630709bc7aede Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 18 Oct 2022 17:32:22 -0400 Subject: [PATCH 39/95] revert decoration dispose changes (#4215) --- .../decorations/BufferDecorationRenderer.ts | 2 +- src/common/Lifecycle.ts | 9 +++----- src/common/services/DecorationService.ts | 21 ++++++++++++------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 72ed6428..d33115f7 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -98,7 +98,6 @@ export class BufferDecorationRenderer extends Disposable { } else { let element = this._decorationElements.get(decoration); if (!element) { - decoration.onDispose(() => this._removeDecoration(decoration)); element = this._createElement(decoration); decoration.element = element; this._decorationElements.set(decoration, element); @@ -125,5 +124,6 @@ export class BufferDecorationRenderer extends Disposable { private _removeDecoration(decoration: IInternalDecoration): void { this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); + decoration.dispose(); } } diff --git a/src/common/Lifecycle.ts b/src/common/Lifecycle.ts index 7ccc8aa7..b3a7cc21 100644 --- a/src/common/Lifecycle.ts +++ b/src/common/Lifecycle.ts @@ -17,18 +17,15 @@ export abstract class Disposable implements IDisposable { } /** - * 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}. + * Disposes the object, triggering the `dispose` method on all registered IDisposables. */ - public readonly dispose = (): void => { + public 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/services/DecorationService.ts b/src/common/services/DecorationService.ts index c27e7b2f..881b3d07 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -42,6 +42,7 @@ export class DecorationService extends Disposable implements IDecorationService this.reset(); })); } + public registerDecoration(options: IDecorationOptions): IDecoration | undefined { if (options.marker.isDisposed) { return undefined; @@ -91,19 +92,25 @@ 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) { @@ -136,12 +143,10 @@ class Decoration extends Disposable implements IInternalDecoration { if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) { this.options.overviewRulerOptions.position = 'full'; } + } - this.register(toDisposable(() => { - if (this._isDisposed) { - return; - } - this._onDispose.fire(); - })); + public override dispose(): void { + this._onDispose.fire(); + super.dispose(); } } From 11e90591f9a46e4d49c8b5e07e7df217e71aa80f Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 19 Oct 2022 10:09:12 -0400 Subject: [PATCH 40/95] Change clearing innerText to using replaceChildren to fix testing using jsdom Signed-off-by: Sebastian Malton --- src/browser/renderer/dom/DomRenderer.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 399682ac..72f7e254 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -338,7 +338,14 @@ export class DomRenderer extends Disposable implements IRenderer { public clear(): void { for (const e of this._rowElements) { - e.innerText = ''; + /** + * NOTE: This used to be `e.innerText = '';` but that doesn't work when using `jsdom` and `@testing-library/react` + * + * references: + * - https://github.com/testing-library/react-testing-library/issues/1146 + * - https://github.com/jsdom/jsdom/issues/1245 + */ + e.replaceChildren(); } } @@ -349,11 +356,10 @@ export class DomRenderer extends Disposable implements IRenderer { for (let y = start; y <= end; y++) { const rowElement = this._rowElements[y]; - rowElement.innerText = ''; const row = y + this._bufferService.buffer.ydisp; const lineData = this._bufferService.buffer.lines.get(row); const cursorStyle = this._optionsService.rawOptions.cursorStyle; - rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.css.cell.width, this._bufferService.cols)); + rowElement.replaceChildren(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.css.cell.width, this._bufferService.cols)); } } From 8bd8195e300257e890be6edd4a8318cd74f9e339 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:03:01 -0700 Subject: [PATCH 41/95] Disable canvas ImageBitmap optimization on Safari Fixes #4218 --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ccd1f56f..67d459f7 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -20,6 +20,7 @@ import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; import { Disposable, toDisposable } from 'common/Lifecycle'; +import { isSafari } from 'common/Platform'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -434,6 +435,10 @@ class BitmapGenerator { public refresh(): void { // Clear the bitmap immediately as it's stale this._bitmap = undefined; + // Disable ImageBitmaps on Safari because of https://bugs.webkit.org/show_bug.cgi?id=149990 + if (isSafari) { + return; + } if (this._commitTimeout === undefined) { this._commitTimeout = window.setTimeout(() => this._generate(), GLYPH_BITMAP_COMMIT_DELAY); } From 8d4d5bd54ba76a12d25995cc47ab6f9808bfb390 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 21 Oct 2022 13:02:24 -0700 Subject: [PATCH 42/95] Fix dropped frame issue This ensures decorations are refreshed in the same frame when called from an animation frame callback Fixes #4225 --- src/browser/decorations/BufferDecorationRenderer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index d33115f7..e195c5ae 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -28,7 +28,7 @@ export class BufferDecorationRenderer extends Disposable { this._container.classList.add('xterm-decoration-container'); this._screenElement.appendChild(this._container); - this.register(this._renderService.onRenderedViewportChange(() => this._queueRefresh())); + this.register(this._renderService.onRenderedViewportChange(() => this._doRefreshDecorations())); this.register(this._renderService.onDimensionsChange(() => { this._dimensionsChanged = true; this._queueRefresh(); @@ -50,12 +50,12 @@ export class BufferDecorationRenderer extends Disposable { return; } this._animationFrame = this._renderService.addRefreshCallback(() => { - this.refreshDecorations(); + this._doRefreshDecorations(); this._animationFrame = undefined; }); } - public refreshDecorations(): void { + private _doRefreshDecorations(): void { for (const decoration of this._decorationService.decorations) { this._renderDecoration(decoration); } From 19c1ee56d3b0deb521413182d1ce53294710e525 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 27 Oct 2022 13:55:24 -0700 Subject: [PATCH 43/95] Fix resize in demo A double resize could happen due to the mismatch in canvas vs device pixel widths Fixes #4113 --- demo/client.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index fdb46f6c..18ce1808 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -615,10 +615,8 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a } function updateTerminalSize(): void { - 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.css.cell.width + term._core.viewport.scrollBarWidth).toString() + 'px'; - const height = (rows * term._core._renderService.dimensions.css.cell.height).toString() + 'px'; + const width = (term._core._renderService.dimensions.css.canvas.width + term._core.viewport.scrollBarWidth).toString() + 'px'; + const height = (term._core._renderService.dimensions.css.canvas.height).toString() + 'px'; terminalContainer.style.width = width; terminalContainer.style.height = height; addons.fit.instance.fit(); From b5cd4f14923ac482b2fd4f8a8a63456b128303c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 27 Oct 2022 16:50:45 -0700 Subject: [PATCH 44/95] Cache ICoreBrowserService.isFocused per task This is called for every selected cell when rendering which ends up consuming a bunch of CPU due to the DOM calls. --- src/browser/services/CoreBrowserService.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index 6504e5e4..e992f255 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -8,10 +8,15 @@ import { ICoreBrowserService } from './Services'; export class CoreBrowserService implements ICoreBrowserService { public serviceBrand: undefined; + private _isFocused = false; + private _cachedIsFocused: boolean | undefined = undefined; + constructor( private _textarea: HTMLTextAreaElement, public readonly window: Window & typeof globalThis ) { + this._textarea.addEventListener('focus', () => this._isFocused = true); + this._textarea.addEventListener('blur', () => this._isFocused = false); } public get dpr(): number { @@ -19,7 +24,10 @@ export class CoreBrowserService implements ICoreBrowserService { } public get isFocused(): boolean { - const docOrShadowRoot = this._textarea.getRootNode ? this._textarea.getRootNode() as Document | ShadowRoot : this._textarea.ownerDocument; - return docOrShadowRoot.activeElement === this._textarea && this._textarea.ownerDocument.hasFocus(); + if (this._cachedIsFocused === undefined) { + this._cachedIsFocused = this._isFocused && this._textarea.ownerDocument.hasFocus(); + queueMicrotask(() => this._cachedIsFocused = undefined); + } + return this._cachedIsFocused; } } From 7908b7cf6217a0c2e54a18e454ec4d9dd70935f8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 27 Oct 2022 22:30:46 -0700 Subject: [PATCH 45/95] Avoid GC pressure from server data buffer --- demo/server.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/demo/server.js b/demo/server.js index 201c8706..92b82e98 100644 --- a/demo/server.js +++ b/demo/server.js @@ -111,27 +111,30 @@ function startServer() { } // binary message buffering function bufferUtf8(socket, timeout, maxSize) { - let buffer = []; + const dataBuffer = new Uint8Array(maxSize); let sender = null; let length = 0; return (data) => { - buffer.push(data); - length += data.length; - if (length > maxSize || userInput) { - userInput = false; - socket.send(Buffer.concat(buffer, length)); - buffer = []; + function flush() { + socket.send(Buffer.from(dataBuffer.buffer, 0, length)); length = 0; if (sender) { clearTimeout(sender); sender = null; } + } + if (length + data.length > maxSize) { + flush(); + } + dataBuffer.set(data, length); + length += data.length; + if (length > maxSize || userInput) { + userInput = false; + flush(); } else if (!sender) { sender = setTimeout(() => { - socket.send(Buffer.concat(buffer, length)); - buffer = []; sender = null; - length = 0; + flush(); }, timeout); } }; From 4f4ce0146dfc390456542dceb3de9617fd2a5062 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 08:54:57 -0700 Subject: [PATCH 46/95] Add console.image helper This is useful for printing canvases that aren't attached to the DOM --- demo/client.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/demo/client.ts b/demo/client.ts index 18ce1808..5b561a8c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -992,3 +992,33 @@ function addOverviewRuler(): void { term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' } }); } +(console as any).image = (source: ImageData | HTMLCanvasElement, scale: number = 1) => { + function getBox(width: number, height: number): any { + return { + string: '+', + style: 'font-size: 1px; padding: ' + Math.floor(height/2) + 'px ' + Math.floor(width/2) + 'px; line-height: ' + height + 'px;' + }; + } + if (source instanceof HTMLCanvasElement) { + source = source.getContext('2d')?.getImageData(0, 0, source.width, source.height)!; + } + const canvas = document.createElement('canvas'); + canvas.width = source.width; + canvas.height = source.height; + const ctx = canvas.getContext('2d')!; + ctx.putImageData(source, 0, 0); + + const sw = source.width * scale; + const sh = source.height * scale; + const dim = getBox(sw, sh); + console.log( + `Image: ${source.width} x ${source.height}\n%c${dim.string}`, + `${dim.style}background: url(${canvas.toDataURL()}); background-size: ${sw}px ${sh}px; background-repeat: no-repeat; color: transparent;` + ); + console.groupCollapsed('Zoomed'); + console.log( + `%c${dim.string}`, + `${getBox(sw * 10, sh * 10).style}background: url(${canvas.toDataURL()}); background-size: ${sw * 10}px ${sh * 10}px; background-repeat: no-repeat; color: transparent; image-rendering: pixelated;-ms-interpolation-mode: nearest-neighbor;` + ); + console.groupEnd(); +}; From 8b545a3fe4b58fbfc262f9777d18bd1b318edbbe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 08:57:41 -0700 Subject: [PATCH 47/95] Fix bbox right scan start offset This causes particularly wide glyphs like italic emoji to get clipped even if they're within the allowed padding. Fixes #4229 --- src/browser/renderer/shared/TextureAtlas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 23deb8a3..d59ee64e 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -730,7 +730,7 @@ export class TextureAtlas implements ITextureAtlas { } boundingBox.right = width; found = false; - for (let x = width - 1; x >= 0; x--) { + for (let x = padding + width - 1; x >= 0; x--) { for (let y = 0; y < height; y++) { const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; if (imageData.data[alphaOffset] !== 0) { From 67b3faef0116589a321e9da05f9cbdcd8e51e729 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 09:01:22 -0700 Subject: [PATCH 48/95] Use bbox padding and width consistently --- src/browser/renderer/shared/TextureAtlas.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index d59ee64e..4edebf5c 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -715,7 +715,7 @@ export class TextureAtlas implements ITextureAtlas { } boundingBox.left = 0; found = false; - for (let x = 0; x < width; x++) { + for (let x = 0; x < padding + width; x++) { for (let y = 0; y < height; y++) { const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; if (imageData.data[alphaOffset] !== 0) { @@ -730,7 +730,7 @@ export class TextureAtlas implements ITextureAtlas { } boundingBox.right = width; found = false; - for (let x = padding + width - 1; x >= 0; x--) { + for (let x = padding + width - 1; x >= padding; x--) { for (let y = 0; y < height; y++) { const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; if (imageData.data[alphaOffset] !== 0) { From b0672aac099a501db94603f955102cbce0a96539 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 14:02:58 -0700 Subject: [PATCH 49/95] Correctly offset mcr check on canvas renderer Fixes #4224 --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 67d459f7..b037d61e 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -357,7 +357,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer */ protected _drawChars(cell: ICellData, x: number, y: number): void { const chars = cell.getChars(); - this._cellColorResolver.resolve(cell, x, y); + this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y); let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext); From 5a4a46f13025776362e285e8e0ae7dfc46aaf5b5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 14:09:39 -0700 Subject: [PATCH 50/95] Remove promise and fetch shims and use await for fetch Fixes #4222 --- demo/client.ts | 20 +++++++++----------- demo/index.html | 2 -- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 5b561a8c..1f8ac417 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -302,7 +302,7 @@ function createTerminal(): void { }); // fit is called within a setTimeout, cols and rows need this. - setTimeout(() => { + setTimeout(async () => { initOptions(term); // TODO: Clean this up, opt-cols/rows doesn't exist anymore (document.getElementById(`opt-cols`) as HTMLInputElement).value = term.cols; @@ -312,16 +312,14 @@ function createTerminal(): void { // Set terminal size again to set the specific dimensions on the demo updateTerminalSize(); - fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, { method: 'POST' }).then((res) => { - res.text().then((processId) => { - pid = processId; - socketURL += processId; - socket = new WebSocket(socketURL); - socket.onopen = runRealTerminal; - socket.onclose = runFakeTerminal; - socket.onerror = runFakeTerminal; - }); - }); + const res = await fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, { method: 'POST' }); + const processId = await res.text(); + pid = processId; + socketURL += processId; + socket = new WebSocket(socketURL); + socket.onopen = runRealTerminal; + socket.onclose = runFakeTerminal; + socket.onerror = runFakeTerminal; }, 0); } diff --git a/demo/index.html b/demo/index.html index a65b8bbc..83877d22 100644 --- a/demo/index.html +++ b/demo/index.html @@ -10,8 +10,6 @@ - -

xterm.js: A terminal for the web

From 54335ef54ae052063cb384148e1655a8a0749e60 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 18:16:55 -0700 Subject: [PATCH 51/95] Create texture page attribute --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 69 ++++++++++++++----- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 4bce546a..a6c2f74c 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -8,7 +8,6 @@ import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel } from ' import { fill } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; -import { IColorSet } from 'browser/Types'; import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; @@ -30,8 +29,9 @@ const enum VertexAttribLocations { CELL_POSITION = 1, OFFSET = 2, SIZE = 3, - TEXCOORD = 4, - TEXSIZE = 5 + TEXPAGE = 4, + TEXCOORD = 5, + TEXSIZE = 6 } const vertexShaderSource = `#version 300 es @@ -39,6 +39,7 @@ layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad; layout (location = ${VertexAttribLocations.CELL_POSITION}) in vec2 a_cellpos; layout (location = ${VertexAttribLocations.OFFSET}) in vec2 a_offset; layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size; +layout (location = ${VertexAttribLocations.TEXPAGE}) in float a_texpage; layout (location = ${VertexAttribLocations.TEXCOORD}) in vec2 a_texcoord; layout (location = ${VertexAttribLocations.TEXSIZE}) in vec2 a_texsize; @@ -46,10 +47,12 @@ uniform mat4 u_projection; uniform vec2 u_resolution; out vec2 v_texcoord; +flat out int v_texpage; void main() { vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size); gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0); + v_texpage = int(a_texpage); v_texcoord = a_texcoord + a_unitquad * a_texsize; }`; @@ -57,16 +60,21 @@ const fragmentShaderSource = `#version 300 es precision lowp float; in vec2 v_texcoord; +flat in int v_texpage; -uniform sampler2D u_texture; +uniform sampler2D u_texture[2]; out vec4 outColor; void main() { - outColor = texture(u_texture, v_texcoord); + if (v_texpage == 0) { + outColor = texture(u_texture[0], v_texcoord); + } else if (v_texpage == 1) { + outColor = texture(u_texture[1], v_texcoord); + } }`; -const INDICES_PER_CELL = 10; +const INDICES_PER_CELL = 11; const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; const CELL_POSITION_INDICES = 2; @@ -84,6 +92,7 @@ export class GlyphRenderer extends Disposable { private _projectionLocation: WebGLUniformLocation; private _resolutionLocation: WebGLUniformLocation; private _textureLocation: WebGLUniformLocation; + private readonly _nullTexture: WebGLTexture; private _atlasTexture: WebGLTexture; private _attributesBuffer: WebGLBuffer; private _activeBuffer: number = 0; @@ -145,24 +154,35 @@ export class GlyphRenderer extends Disposable { gl.enableVertexAttribArray(VertexAttribLocations.SIZE); gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1); + gl.enableVertexAttribArray(VertexAttribLocations.TEXPAGE); + gl.vertexAttribPointer(VertexAttribLocations.TEXPAGE, 1, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.TEXPAGE, 1); gl.enableVertexAttribArray(VertexAttribLocations.TEXCOORD); - gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 5 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.TEXCOORD, 1); gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE); - gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 6 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 7 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1); gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION); - gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 8 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); // Setup empty texture atlas this._atlasTexture = throwIfFalsy(gl.createTexture()); this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture))); + gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + this._nullTexture = throwIfFalsy(gl.createTexture()); + gl.activeTexture(gl.TEXTURE0 + 1); + gl.bindTexture(gl.TEXTURE_2D, this._nullTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255])); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + // Allow drawing of transparent texture gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); @@ -213,12 +233,14 @@ export class GlyphRenderer extends Disposable { // a_size array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.device.canvas.width; array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height; + // a_texpage + array[$i + 4] = 0; // a_texcoord - array[$i + 4] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width; - array[$i + 5] = $glyph.texturePositionClipSpace.y; + array[$i + 5] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 6] = $glyph.texturePositionClipSpace.y; // a_texsize - array[$i + 6] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.cacheCanvas.width; - array[$i + 7] = $glyph.sizeClipSpace.y; + array[$i + 7] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 8] = $glyph.sizeClipSpace.y; } else { // a_origin array[$i ] = -$glyph.offset.x + this._dimensions.device.char.left; @@ -226,12 +248,14 @@ export class GlyphRenderer extends Disposable { // a_size array[$i + 2] = $glyph.size.x / this._dimensions.device.canvas.width; array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height; + // a_texpage + array[$i + 4] = 0; // a_texcoord - array[$i + 4] = $glyph.texturePositionClipSpace.x; - array[$i + 5] = $glyph.texturePositionClipSpace.y; + array[$i + 5] = $glyph.texturePositionClipSpace.x; + array[$i + 6] = $glyph.texturePositionClipSpace.y; // a_texsize - array[$i + 6] = $glyph.sizeClipSpace.x; - array[$i + 7] = $glyph.sizeClipSpace.y; + array[$i + 7] = $glyph.sizeClipSpace.x; + array[$i + 8] = $glyph.sizeClipSpace.y; } // a_cellpos only changes on resize } @@ -257,8 +281,8 @@ export class GlyphRenderer extends Disposable { let i = 0; for (let y = 0; y < terminal.rows; y++) { for (let x = 0; x < terminal.cols; x++) { - this._vertices.attributes[i + 8] = x / terminal.cols; - this._vertices.attributes[i + 9] = y / terminal.rows; + this._vertices.attributes[i + 9] = x / terminal.cols; + this._vertices.attributes[i + 10] = y / terminal.rows; i += INDICES_PER_CELL; } } @@ -306,13 +330,20 @@ export class GlyphRenderer extends Disposable { // Bind the texture atlas if it's changed if (this._atlas.hasCanvasChanged) { this._atlas.hasCanvasChanged = false; + // TODO: Make nicer + const layerTextureUnits = new Int32Array([0, 1]); + gl.uniform1iv(this._textureLocation, layerTextureUnits); gl.uniform1i(this._textureLocation, 0); gl.activeTexture(gl.TEXTURE0 + 0); gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas); + // TODO: Why is mipmap here? gl.generateMipmap(gl.TEXTURE_2D); } + gl.activeTexture(gl.TEXTURE0 + 1); + gl.bindTexture(gl.TEXTURE_2D, this._nullTexture); + // Set uniforms gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX); gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); From 84ab6a49c4cf717027251a4c118552a7ed0472ea Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 18:56:49 -0700 Subject: [PATCH 52/95] Bring texturePage partially to atlas --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 54 +++++++++++-------- src/browser/renderer/shared/TextureAtlas.ts | 29 +++++++--- src/browser/renderer/shared/Types.d.ts | 6 +++ 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index a6c2f74c..58df22dd 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -85,18 +85,17 @@ let $leftCellPadding = 0; let $clippedPixels = 0; export class GlyphRenderer extends Disposable { + private readonly _program: WebGLProgram; + private readonly _vertexArrayObject: IWebGLVertexArrayObject; + private readonly _projectionLocation: WebGLUniformLocation; + private readonly _resolutionLocation: WebGLUniformLocation; + private readonly _textureLocation: WebGLUniformLocation; + private readonly _atlasTexture: WebGLTexture; + private readonly _atlasTexture1: WebGLTexture; + private readonly _attributesBuffer: WebGLBuffer; + private _atlas: ITextureAtlas | undefined; - - private _program: WebGLProgram; - private _vertexArrayObject: IWebGLVertexArrayObject; - private _projectionLocation: WebGLUniformLocation; - private _resolutionLocation: WebGLUniformLocation; - private _textureLocation: WebGLUniformLocation; - private readonly _nullTexture: WebGLTexture; - private _atlasTexture: WebGLTexture; - private _attributesBuffer: WebGLBuffer; private _activeBuffer: number = 0; - private _vertices: IVertices = { count: 0, attributes: new Float32Array(0), @@ -172,16 +171,17 @@ export class GlyphRenderer extends Disposable { this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture))); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - this._nullTexture = throwIfFalsy(gl.createTexture()); + this._atlasTexture1 = throwIfFalsy(gl.createTexture()); + this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture1))); gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._nullTexture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255])); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255])); // Allow drawing of transparent texture gl.enable(gl.BLEND); @@ -234,7 +234,7 @@ export class GlyphRenderer extends Disposable { array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.device.canvas.width; array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height; // a_texpage - array[$i + 4] = 0; + array[$i + 4] = $glyph.texturePage; // a_texcoord array[$i + 5] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width; array[$i + 6] = $glyph.texturePositionClipSpace.y; @@ -249,7 +249,7 @@ export class GlyphRenderer extends Disposable { array[$i + 2] = $glyph.size.x / this._dimensions.device.canvas.width; array[$i + 3] = $glyph.size.y / this._dimensions.device.canvas.height; // a_texpage - array[$i + 4] = 0; + array[$i + 4] = $glyph.texturePage; // a_texcoord array[$i + 5] = $glyph.texturePositionClipSpace.x; array[$i + 6] = $glyph.texturePositionClipSpace.y; @@ -333,16 +333,18 @@ export class GlyphRenderer extends Disposable { // TODO: Make nicer const layerTextureUnits = new Int32Array([0, 1]); gl.uniform1iv(this._textureLocation, layerTextureUnits); - gl.uniform1i(this._textureLocation, 0); gl.activeTexture(gl.TEXTURE0 + 0); gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas); - // TODO: Why is mipmap here? + gl.generateMipmap(gl.TEXTURE_2D); + + // TODO: Check if the particular texture page changed + gl.activeTexture(gl.TEXTURE0 + 1); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas1!); gl.generateMipmap(gl.TEXTURE_2D); } - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._nullTexture); // Set uniforms gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX); @@ -356,9 +358,19 @@ export class GlyphRenderer extends Disposable { const gl = this._gl; this._atlas = atlas; + gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas); gl.generateMipmap(gl.TEXTURE_2D); + + gl.activeTexture(gl.TEXTURE0 + 1); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas1!); + gl.generateMipmap(gl.TEXTURE_2D); } public setDimensions(dimensions: IRenderDimensions): void { diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 4edebf5c..25ecf3cb 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -30,9 +30,10 @@ const TEXTURE_CAPACITY = Math.floor(TEXTURE_HEIGHT * 0.8); * A shared object which is used to draw nothing for a particular cell. */ const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = { - offset: { x: 0, y: 0 }, + texturePage: 0, texturePosition: { x: 0, y: 0 }, texturePositionClipSpace: { x: 0, y: 0 }, + offset: { x: 0, y: 0 }, size: { x: 0, y: 0 }, sizeClipSpace: { x: 0, y: 0 } }; @@ -56,7 +57,9 @@ export class TextureAtlas implements ITextureAtlas { // The texture that the atlas is drawn to public cacheCanvas: HTMLCanvasElement; + public cacheCanvas1: HTMLCanvasElement | undefined; private _cacheCtx: CanvasRenderingContext2D; + private _cacheCtx1: CanvasRenderingContext2D | undefined; private _tmpCanvas: HTMLCanvasElement; // A temporary context that glyphs are drawn to before being transfered to the atlas. @@ -89,23 +92,34 @@ export class TextureAtlas implements ITextureAtlas { private readonly _config: ICharAtlasConfig, private readonly _unicodeService: IUnicodeService ) { - this.cacheCanvas = document.createElement('canvas'); - this.cacheCanvas.width = TEXTURE_WIDTH; - this.cacheCanvas.height = TEXTURE_HEIGHT; + this.cacheCanvas = this._createCanvas(TEXTURE_WIDTH, 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 })); - this._tmpCanvas = document.createElement('canvas'); - this._tmpCanvas.width = this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2; - this._tmpCanvas.height = this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 2; + this.cacheCanvas1 = this._createCanvas(TEXTURE_WIDTH, TEXTURE_HEIGHT); + this._cacheCtx1 = throwIfFalsy(this.cacheCanvas1.getContext('2d', { alpha: true })); + this._cacheCtx1.fillStyle = 'rgb(255, 255, 0)'; + this._cacheCtx1.fillRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + + this._tmpCanvas = this._createCanvas( + this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2, + this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 2 + ); this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency, willReadFrequently: true })); } + private _createCanvas(width: number, height: number): HTMLCanvasElement { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; + } + public dispose(): void { if (this.cacheCanvas.parentElement) { this.cacheCanvas.parentElement.removeChild(this.cacheCanvas); @@ -759,6 +773,7 @@ export class TextureAtlas implements ITextureAtlas { } } return { + texturePage: 1, texturePosition: { x: 0, y: 0 }, texturePositionClipSpace: { x: 0, y: 0 }, size: { diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 433d48a2..b5220e2c 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -88,6 +88,8 @@ export interface IRenderer extends IDisposable { export interface ITextureAtlas extends IDisposable { readonly cacheCanvas: HTMLCanvasElement; + readonly cacheCanvas1: HTMLCanvasElement | undefined; + hasCanvasChanged: boolean; /** @@ -120,6 +122,10 @@ export interface IRasterizedGlyph { * in pixels. */ offset: IVector; + /** + * The index of the texture page that the glyph is on. + */ + texturePage: number; /** * the x and y position of the glyph in the texture in pixels. */ From 6b37187b4d3c42e8a258439096fb85ee8971f46f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:08:33 -0700 Subject: [PATCH 53/95] Create AtlasPage --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 62 +++++++++---------- src/browser/renderer/shared/TextureAtlas.ts | 60 ++++++++++-------- src/browser/renderer/shared/Types.d.ts | 4 +- 3 files changed, 69 insertions(+), 57 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 58df22dd..2096c35a 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -77,6 +77,7 @@ void main() { const INDICES_PER_CELL = 11; const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; const CELL_POSITION_INDICES = 2; +const MAX_ATLAS_PAGES = 8; // Work variables to avoid garbage collection let $i = 0; @@ -90,8 +91,7 @@ export class GlyphRenderer extends Disposable { private readonly _projectionLocation: WebGLUniformLocation; private readonly _resolutionLocation: WebGLUniformLocation; private readonly _textureLocation: WebGLUniformLocation; - private readonly _atlasTexture: WebGLTexture; - private readonly _atlasTexture1: WebGLTexture; + private readonly _atlasTextures: WebGLTexture[]; private readonly _attributesBuffer: WebGLBuffer; private _atlas: ITextureAtlas | undefined; @@ -166,22 +166,18 @@ export class GlyphRenderer extends Disposable { gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); - // Setup empty texture atlas - this._atlasTexture = throwIfFalsy(gl.createTexture()); - this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture))); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); - - this._atlasTexture1 = throwIfFalsy(gl.createTexture()); - this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture1))); - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255])); + // Setup empty textures for all potential atlas pages + this._atlasTextures = []; + for (let i = 0; i < MAX_ATLAS_PAGES; i++) { + const texture = throwIfFalsy(gl.createTexture()); + this.register(toDisposable(() => gl.deleteTexture(texture))); + gl.activeTexture(gl.TEXTURE0 + i); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255])); + this._atlasTextures[i] = texture; + } // Allow drawing of transparent texture gl.enable(gl.BLEND); @@ -334,15 +330,17 @@ export class GlyphRenderer extends Disposable { const layerTextureUnits = new Int32Array([0, 1]); gl.uniform1iv(this._textureLocation, layerTextureUnits); gl.activeTexture(gl.TEXTURE0 + 0); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas); gl.generateMipmap(gl.TEXTURE_2D); - // TODO: Check if the particular texture page changed - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas1!); - gl.generateMipmap(gl.TEXTURE_2D); + if (this._atlas.pages.length > 1) { + // TODO: Check if the particular texture page changed + gl.activeTexture(gl.TEXTURE0 + 1); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[1]); + gl.generateMipmap(gl.TEXTURE_2D); + } } @@ -359,18 +357,20 @@ export class GlyphRenderer extends Disposable { this._atlas = atlas; gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas); gl.generateMipmap(gl.TEXTURE_2D); - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas1!); - gl.generateMipmap(gl.TEXTURE_2D); + if (atlas.pages.length > 1) { + gl.activeTexture(gl.TEXTURE0 + 1); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[1]); + gl.generateMipmap(gl.TEXTURE_2D); + } } public setDimensions(dimensions: IRenderDimensions): void { diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 25ecf3cb..0d291ff6 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -56,10 +56,10 @@ export class TextureAtlas implements ITextureAtlas { private _cacheMapCombined: FourKeyMap = new FourKeyMap(); // The texture that the atlas is drawn to - public cacheCanvas: HTMLCanvasElement; - public cacheCanvas1: HTMLCanvasElement | undefined; - private _cacheCtx: CanvasRenderingContext2D; - private _cacheCtx1: CanvasRenderingContext2D | undefined; + private _pages: AtlasPage[] = []; + public get pages(): HTMLCanvasElement[] { return this._pages.map(e => e.canvas); } + + public get cacheCanvas(): HTMLCanvasElement { return this._pages[0].canvas; } private _tmpCanvas: HTMLCanvasElement; // A temporary context that glyphs are drawn to before being transfered to the atlas. @@ -92,18 +92,9 @@ export class TextureAtlas implements ITextureAtlas { private readonly _config: ICharAtlasConfig, private readonly _unicodeService: IUnicodeService ) { - this.cacheCanvas = this._createCanvas(TEXTURE_WIDTH, 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 })); - - this.cacheCanvas1 = this._createCanvas(TEXTURE_WIDTH, TEXTURE_HEIGHT); - this._cacheCtx1 = throwIfFalsy(this.cacheCanvas1.getContext('2d', { alpha: true })); - this._cacheCtx1.fillStyle = 'rgb(255, 255, 0)'; - this._cacheCtx1.fillRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); - - this._tmpCanvas = this._createCanvas( + this._pages.push(new AtlasPage(document)); + this._tmpCanvas = createCanvas( + document, this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2, this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 2 ); @@ -113,13 +104,6 @@ export class TextureAtlas implements ITextureAtlas { })); } - private _createCanvas(width: number, height: number): HTMLCanvasElement { - const canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; - } - public dispose(): void { if (this.cacheCanvas.parentElement) { this.cacheCanvas.parentElement.removeChild(this.cacheCanvas); @@ -159,7 +143,9 @@ export class TextureAtlas implements ITextureAtlas { if (this._currentRow.x === 0 && this._currentRow.y === 0) { return; } - this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + for (const page of this._pages) { + page.clear(); + } this._cacheMap.clear(); this._cacheMapCombined.clear(); this._currentRow.x = 0; @@ -689,7 +675,7 @@ export class TextureAtlas implements ITextureAtlas { activeRow.x += rasterizedGlyph.size.x; // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us - this._cacheCtx.putImageData( + this._pages[0].ctx.putImageData( imageData, rasterizedGlyph.texturePosition.x - this._workBoundingBox.left, rasterizedGlyph.texturePosition.y - this._workBoundingBox.top, @@ -792,6 +778,23 @@ export class TextureAtlas implements ITextureAtlas { } } +class AtlasPage { + public readonly canvas: HTMLCanvasElement; + public readonly ctx: CanvasRenderingContext2D; + + constructor(document: Document) { + this.canvas = createCanvas(document, TEXTURE_WIDTH, 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.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true })); + } + + public clear(): void { + this.ctx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + } +} + /** * Makes a particular rgb color and colors that are nearly the same in an ImageData completely * transparent. @@ -846,3 +849,10 @@ function checkCompletelyTransparent(imageData: ImageData): boolean { } return true; } + +function createCanvas(document: Document, width: number, height: number): HTMLCanvasElement { + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; +} diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index b5220e2c..f7ee968d 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -87,8 +87,10 @@ export interface IRenderer extends IDisposable { } export interface ITextureAtlas extends IDisposable { + /** @deprecated */ readonly cacheCanvas: HTMLCanvasElement; - readonly cacheCanvas1: HTMLCanvasElement | undefined; + + readonly pages: HTMLCanvasElement[]; hasCanvasChanged: boolean; From 975d52fab9b79fd3351cc18b13602e70b0f3d703 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:12:01 -0700 Subject: [PATCH 54/95] Remove deprecated prop --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 7 ++++--- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 12 ++++++------ addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 ++-- src/browser/renderer/shared/TextureAtlas.ts | 8 +++----- src/browser/renderer/shared/Types.d.ts | 5 +---- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index b037d61e..1db5c81d 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -39,7 +39,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer protected _charAtlas!: ITextureAtlas; public get canvas(): HTMLCanvasElement { return this._canvas; } - public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.cacheCanvas!; } + // TODO: Support multiple pages + public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].canvas!; } constructor( private readonly _terminal: Terminal, @@ -118,7 +119,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer } this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); - this._bitmapGenerator = new BitmapGenerator(this._charAtlas.cacheCanvas); + this._bitmapGenerator = new BitmapGenerator(this._charAtlas.pages[0].canvas); } public resize(dim: IRenderDimensions): void { @@ -372,7 +373,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._charAtlas.hasCanvasChanged = false; } this._ctx.drawImage( - this._bitmapGenerator?.bitmap || this._charAtlas!.cacheCanvas, + this._bitmapGenerator?.bitmap || this._charAtlas!.pages[0].canvas, glyph.texturePosition.x, glyph.texturePosition.y, glyph.size.x, diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 2096c35a..73fa1af2 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -232,10 +232,10 @@ export class GlyphRenderer extends Disposable { // a_texpage array[$i + 4] = $glyph.texturePage; // a_texcoord - array[$i + 5] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 5] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.pages[0].canvas.width; array[$i + 6] = $glyph.texturePositionClipSpace.y; // a_texsize - array[$i + 7] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 7] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.pages[0].canvas.width; array[$i + 8] = $glyph.sizeClipSpace.y; } else { // a_origin @@ -331,14 +331,14 @@ export class GlyphRenderer extends Disposable { gl.uniform1iv(this._textureLocation, layerTextureUnits); gl.activeTexture(gl.TEXTURE0 + 0); gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[0].canvas); gl.generateMipmap(gl.TEXTURE_2D); if (this._atlas.pages.length > 1) { // TODO: Check if the particular texture page changed gl.activeTexture(gl.TEXTURE0 + 1); gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[1]); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[1].canvas); gl.generateMipmap(gl.TEXTURE_2D); } } @@ -360,7 +360,7 @@ export class GlyphRenderer extends Disposable { gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[0].canvas); gl.generateMipmap(gl.TEXTURE_2D); if (atlas.pages.length > 1) { @@ -368,7 +368,7 @@ export class GlyphRenderer extends Disposable { gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[1]); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[1].canvas); gl.generateMipmap(gl.TEXTURE_2D); } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index f366c379..35542bcf 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -135,7 +135,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } public get textureAtlas(): HTMLCanvasElement | undefined { - return this._charAtlas?.cacheCanvas; + return this._charAtlas?.pages[0].canvas; } private _handleColorChange(): void { @@ -261,7 +261,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._coreBrowserService.dpr ); if (this._charAtlas !== atlas) { - this._onChangeTextureAtlas.fire(atlas.cacheCanvas); + this._onChangeTextureAtlas.fire(atlas.pages[0].canvas); } this._charAtlas = atlas; this._charAtlas.warmUp(); diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 0d291ff6..7e533226 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -57,9 +57,7 @@ export class TextureAtlas implements ITextureAtlas { // The texture that the atlas is drawn to private _pages: AtlasPage[] = []; - public get pages(): HTMLCanvasElement[] { return this._pages.map(e => e.canvas); } - - public get cacheCanvas(): HTMLCanvasElement { return this._pages[0].canvas; } + public get pages(): { canvas: HTMLCanvasElement }[] { return this._pages; } private _tmpCanvas: HTMLCanvasElement; // A temporary context that glyphs are drawn to before being transfered to the atlas. @@ -105,8 +103,8 @@ export class TextureAtlas implements ITextureAtlas { } public dispose(): void { - if (this.cacheCanvas.parentElement) { - this.cacheCanvas.parentElement.removeChild(this.cacheCanvas); + for (const page of this.pages) { + page.canvas.remove(); } } diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index f7ee968d..2eaac493 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -87,10 +87,7 @@ export interface IRenderer extends IDisposable { } export interface ITextureAtlas extends IDisposable { - /** @deprecated */ - readonly cacheCanvas: HTMLCanvasElement; - - readonly pages: HTMLCanvasElement[]; + readonly pages: { canvas: HTMLCanvasElement }[]; hasCanvasChanged: boolean; From 67ef4555154c0938f0df5fa3c03213f4159f92a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:20:26 -0700 Subject: [PATCH 55/95] Get a second page kind of working --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 4 +- src/browser/renderer/shared/TextureAtlas.ts | 90 ++++++++++--------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 73fa1af2..823a0db1 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -232,10 +232,10 @@ export class GlyphRenderer extends Disposable { // a_texpage array[$i + 4] = $glyph.texturePage; // a_texcoord - array[$i + 5] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.pages[0].canvas.width; + array[$i + 5] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.pages[$glyph.texturePage].canvas.width; array[$i + 6] = $glyph.texturePositionClipSpace.y; // a_texsize - array[$i + 7] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.pages[0].canvas.width; + array[$i + 7] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.pages[$glyph.texturePage].canvas.width; array[$i + 8] = $glyph.sizeClipSpace.y; } else { // a_origin diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 7e533226..e2d5d617 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -15,10 +15,9 @@ import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; 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. -const TEXTURE_WIDTH = 1024; -const TEXTURE_HEIGHT = 1024; +// For debugging purposes, it can be useful to set this to a really tiny value. +const TEXTURE_WIDTH = 512; +const TEXTURE_HEIGHT = 512; /** * The amount of the texture to be filled before throwing it away and starting @@ -63,36 +62,19 @@ export class TextureAtlas implements ITextureAtlas { // A temporary context that glyphs are drawn to before being transfered to the atlas. private _tmpCtx: CanvasRenderingContext2D; - // Texture atlas current positioning data. The texture packing strategy used is to fill from - // left-to-right and top-to-bottom. When the glyph being written is less than half of the current - // row's height, the following happens: - // - // - The current row becomes the fixed height row A - // - A new fixed height row B the exact size of the glyph is created below the current row - // - A new dynamic height current row is created below B - // - // This strategy does a good job preventing space being wasted for very short glyphs such as - // underscores, hyphens etc. or those with underlines rendered. - private _currentRow: ICharAtlasActiveRow = { - x: 0, - y: 0, - height: 0 - }; - private readonly _fixedRows: ICharAtlasActiveRow[] = []; - public hasCanvasChanged = false; private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; private _workAttributeData: AttributeData = new AttributeData(); constructor( - document: Document, + private readonly _document: Document, private readonly _config: ICharAtlasConfig, private readonly _unicodeService: IUnicodeService ) { - this._pages.push(new AtlasPage(document)); + this._pages.push(new AtlasPage(_document)); this._tmpCanvas = createCanvas( - document, + _document, this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2, this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 2 ); @@ -129,16 +111,17 @@ export class TextureAtlas implements ITextureAtlas { } public beginFrame(): boolean { - if (this._currentRow.y > TEXTURE_CAPACITY) { - this.clearTexture(); - this.warmUp(); + if (this._pages[this._pages.length - 1].currentRow.y > TEXTURE_CAPACITY) { + // TODO: Support drawing to multiple pages at once + console.log(`Add page #${this._pages.length + 1}`); + this._pages.push(new AtlasPage(this._document)); return true; } return false; } public clearTexture(): void { - if (this._currentRow.x === 0 && this._currentRow.y === 0) { + if (this._pages[0].currentRow.x === 0 && this._pages[0].currentRow.y === 0) { return; } for (const page of this._pages) { @@ -146,10 +129,6 @@ export class TextureAtlas implements ITextureAtlas { } this._cacheMap.clear(); this._cacheMapCombined.clear(); - this._currentRow.x = 0; - this._currentRow.y = 0; - this._currentRow.height = 0; - this._fixedRows.length = 0; this._didWarmUp = false; this.hasCanvasChanged = true; } @@ -613,11 +592,12 @@ export class TextureAtlas implements ITextureAtlas { // Find the best atlas row to use let activeRow: ICharAtlasActiveRow; + const page = this._pages[this._pages.length - 1]; while (true) { // Select the ideal existing row, preferring fixed rows over the current row - activeRow = this._currentRow; - for (const row of this._fixedRows) { - if ((activeRow === this._currentRow || row.height < activeRow.height) && rasterizedGlyph.size.y <= row.height) { + activeRow = page.currentRow; + for (const row of page.fixedRows) { + if ((activeRow === page.currentRow || row.height < activeRow.height) && rasterizedGlyph.size.y <= row.height) { activeRow = row; } } @@ -626,20 +606,20 @@ export class TextureAtlas implements ITextureAtlas { // process as it now has a fixed height if (activeRow.height > rasterizedGlyph.size.y * 2) { // Fix the current row as the new row is being added below - if (this._currentRow.height > 0) { - this._fixedRows.push(this._currentRow); + if (page.currentRow.height > 0) { + page.fixedRows.push(page.currentRow); } // Create the new fixed height row activeRow = { x: 0, - y: this._currentRow.y + this._currentRow.height, + y: page.currentRow.y + page.currentRow.height, height: rasterizedGlyph.size.y }; - this._fixedRows.push(activeRow); + page.fixedRows.push(activeRow); // Create the new current row below the new fixed height row - this._currentRow = { + page.currentRow = { x: 0, y: activeRow.y + activeRow.height, height: 0 @@ -652,16 +632,17 @@ export class TextureAtlas implements ITextureAtlas { } // If there is enough room in the current row, finish it and try again - if (activeRow === this._currentRow) { + if (activeRow === page.currentRow) { activeRow.x = 0; activeRow.y += activeRow.height; activeRow.height = 0; } else { - this._fixedRows.splice(this._fixedRows.indexOf(activeRow), 1); + page.fixedRows.splice(page.fixedRows.indexOf(activeRow), 1); } } // Record texture position + rasterizedGlyph.texturePage = this._pages.length - 1; rasterizedGlyph.texturePosition.x = activeRow.x; rasterizedGlyph.texturePosition.y = activeRow.y; rasterizedGlyph.texturePositionClipSpace.x = activeRow.x / TEXTURE_WIDTH; @@ -673,7 +654,7 @@ export class TextureAtlas implements ITextureAtlas { activeRow.x += rasterizedGlyph.size.x; // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us - this._pages[0].ctx.putImageData( + page.ctx.putImageData( imageData, rasterizedGlyph.texturePosition.x - this._workBoundingBox.left, rasterizedGlyph.texturePosition.y - this._workBoundingBox.top, @@ -757,7 +738,7 @@ export class TextureAtlas implements ITextureAtlas { } } return { - texturePage: 1, + texturePage: 0, texturePosition: { x: 0, y: 0 }, texturePositionClipSpace: { x: 0, y: 0 }, size: { @@ -780,6 +761,23 @@ class AtlasPage { public readonly canvas: HTMLCanvasElement; public readonly ctx: CanvasRenderingContext2D; + // Texture atlas current positioning data. The texture packing strategy used is to fill from + // left-to-right and top-to-bottom. When the glyph being written is less than half of the current + // row's height, the following happens: + // + // - The current row becomes the fixed height row A + // - A new fixed height row B the exact size of the glyph is created below the current row + // - A new dynamic height current row is created below B + // + // This strategy does a good job preventing space being wasted for very short glyphs such as + // underscores, hyphens etc. or those with underlines rendered. + public currentRow: ICharAtlasActiveRow = { + x: 0, + y: 0, + height: 0 + }; + public readonly fixedRows: ICharAtlasActiveRow[] = []; + constructor(document: Document) { this.canvas = createCanvas(document, TEXTURE_WIDTH, TEXTURE_HEIGHT); // The canvas needs alpha because we use clearColor to convert the background color to alpha. @@ -790,6 +788,10 @@ class AtlasPage { public clear(): void { this.ctx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + this.currentRow.x = 0; + this.currentRow.y = 0; + this.currentRow.height = 0; + this.fixedRows.length = 0; } } From efba2c9040c1ce0490f65c7bc3d474ea46fdba42 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 28 Oct 2022 19:30:42 -0700 Subject: [PATCH 56/95] Get pages working, add to demo --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 48 +++++++++---------- addons/xterm-addon-webgl/src/WebglAddon.ts | 5 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 6 ++- .../typings/xterm-addon-webgl.d.ts | 3 ++ demo/client.ts | 18 ++++--- src/browser/renderer/shared/TextureAtlas.ts | 9 +++- src/browser/renderer/shared/Types.d.ts | 2 + 7 files changed, 57 insertions(+), 34 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 823a0db1..54afb7a7 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -62,7 +62,7 @@ precision lowp float; in vec2 v_texcoord; flat in int v_texpage; -uniform sampler2D u_texture[2]; +uniform sampler2D u_texture[8]; out vec4 outColor; @@ -71,6 +71,18 @@ void main() { outColor = texture(u_texture[0], v_texcoord); } else if (v_texpage == 1) { outColor = texture(u_texture[1], v_texcoord); + } else if (v_texpage == 2) { + outColor = texture(u_texture[2], v_texcoord); + } else if (v_texpage == 3) { + outColor = texture(u_texture[3], v_texcoord); + } else if (v_texpage == 4) { + outColor = texture(u_texture[4], v_texcoord); + } else if (v_texpage == 5) { + outColor = texture(u_texture[5], v_texcoord); + } else if (v_texpage == 6) { + outColor = texture(u_texture[6], v_texcoord); + } else if (v_texpage == 7) { + outColor = texture(u_texture[7], v_texcoord); } }`; @@ -327,23 +339,17 @@ export class GlyphRenderer extends Disposable { if (this._atlas.hasCanvasChanged) { this._atlas.hasCanvasChanged = false; // TODO: Make nicer - const layerTextureUnits = new Int32Array([0, 1]); + const layerTextureUnits = new Int32Array([0, 1, 2, 3, 4, 5, 6, 7]); gl.uniform1iv(this._textureLocation, layerTextureUnits); - gl.activeTexture(gl.TEXTURE0 + 0); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[0].canvas); - gl.generateMipmap(gl.TEXTURE_2D); - - if (this._atlas.pages.length > 1) { - // TODO: Check if the particular texture page changed - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[1].canvas); + // TODO: Only upload the texture(s) that changed + for (let i = 0; i < this._atlas.pages.length; i++) { + gl.activeTexture(gl.TEXTURE0 + i); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i]); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[i].canvas); gl.generateMipmap(gl.TEXTURE_2D); } } - // Set uniforms gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX); gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); @@ -356,19 +362,13 @@ export class GlyphRenderer extends Disposable { const gl = this._gl; this._atlas = atlas; - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[0].canvas); - gl.generateMipmap(gl.TEXTURE_2D); - - if (atlas.pages.length > 1) { - gl.activeTexture(gl.TEXTURE0 + 1); - gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]); + // TODO: Share code + for (let i = 0; i < this._atlas.pages.length; i++) { + gl.activeTexture(gl.TEXTURE0 + i); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[i]); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[1].canvas); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[i].canvas); gl.generateMipmap(gl.TEXTURE_2D); } } diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 4f4c9dcc..5262da8e 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -17,8 +17,10 @@ export class WebglAddon extends Disposable implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private readonly _onChangeTextureAtlas = this.register(new EventEmitter()); + private readonly _onChangeTextureAtlas = this.register(new EventEmitter()); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; + private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter()); + public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; private readonly _onContextLoss = this.register(new EventEmitter()); public readonly onContextLoss = this._onContextLoss.event; @@ -64,6 +66,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon { )); this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss)); this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); + this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); renderService.setRenderer(this._renderer); this.register(toDisposable(() => { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 35542bcf..636be672 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,7 +14,7 @@ import { 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 { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { CharData, IBufferLine, ICellData } from 'common/Types'; @@ -49,6 +49,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private readonly _onChangeTextureAtlas = this.register(new EventEmitter()); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; + private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter()); + public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; private readonly _onRequestRedraw = this.register(new EventEmitter()); public readonly onRequestRedraw = this._onRequestRedraw.event; private readonly _onContextLoss = this.register(new EventEmitter()); @@ -262,6 +264,8 @@ export class WebglRenderer extends Disposable implements IRenderer { ); if (this._charAtlas !== atlas) { this._onChangeTextureAtlas.fire(atlas.pages[0].canvas); + // TODO: Dispose this when there's a new atlas + forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); } this._charAtlas = atlas; this._charAtlas.warmUp(); 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 6865b6db..a6afd2ac 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -22,6 +22,9 @@ declare module 'xterm-addon-webgl' { */ public readonly onChangeTextureAtlas: IEvent; + // TODO: Doc + public readonly onAddTextureAtlasCanvas: IEvent; + constructor(preserveDrawingBuffer?: boolean); /** diff --git a/demo/client.ts b/demo/client.ts index 5b561a8c..3829b6dd 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -273,8 +273,9 @@ function createTerminal(): void { typedTerm.loadAddon(addons.webgl.instance); setTimeout(() => { if (addons.webgl.instance !== undefined) { - addTextureAtlas(addons.webgl.instance.textureAtlas); - addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + setTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); + addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e)); } }, 0); @@ -551,13 +552,13 @@ function initAddons(term: TerminalType): void { term.loadAddon(addon.instance); if (name === 'webgl') { setTimeout(() => { - addTextureAtlas(addons.webgl.instance.textureAtlas); - addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + setTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); }, 0); } else if (name === 'canvas') { setTimeout(() => { - addTextureAtlas(addons.canvas.instance.textureAtlas); - addons.canvas.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + setTextureAtlas(addons.canvas.instance.textureAtlas); + addons.canvas.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); }, 0); } else if (name === 'unicode11') { term.unicode.activeVersion = '11'; @@ -648,9 +649,12 @@ function htmlSerializeButtonHandler(): void { document.getElementById('htmlserialize-output-result').innerText = 'Copied to clipboard'; } -function addTextureAtlas(e: HTMLCanvasElement): void { +function setTextureAtlas(e: HTMLCanvasElement): void { document.querySelector('#texture-atlas').replaceChildren(e); } +function appendTextureAtlas(e: HTMLCanvasElement): void { + document.querySelector('#texture-atlas').appendChild(e); +} function writeCustomGlyphHandler(): void { term.write('\n\r'); diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index e2d5d617..5b7f7eb2 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -14,6 +14,7 @@ import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { EventEmitter } from 'common/EventEmitter'; // For debugging purposes, it can be useful to set this to a really tiny value. const TEXTURE_WIDTH = 512; @@ -67,6 +68,10 @@ export class TextureAtlas implements ITextureAtlas { private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; private _workAttributeData: AttributeData = new AttributeData(); + // TODO: Register + private readonly _onAddTextureAtlasCanvas = new EventEmitter(); + public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; + constructor( private readonly _document: Document, private readonly _config: ICharAtlasConfig, @@ -114,7 +119,9 @@ export class TextureAtlas implements ITextureAtlas { if (this._pages[this._pages.length - 1].currentRow.y > TEXTURE_CAPACITY) { // TODO: Support drawing to multiple pages at once console.log(`Add page #${this._pages.length + 1}`); - this._pages.push(new AtlasPage(this._document)); + const newPage = new AtlasPage(this._document); + this._pages.push(newPage); + this._onAddTextureAtlasCanvas.fire(newPage.canvas); return true; } return false; diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 2eaac493..a622438b 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -91,6 +91,8 @@ export interface ITextureAtlas extends IDisposable { hasCanvasChanged: boolean; + onAddTextureAtlasCanvas: IEvent; + /** * Warm up the texture atlas, adding common glyphs to avoid slowing early frame. */ From 713baeb64db3a0a098f4cbe0202cb46783a0d165 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 29 Oct 2022 12:45:20 -0700 Subject: [PATCH 57/95] Base atlas page size on device pixel ratio --- src/browser/renderer/shared/TextureAtlas.ts | 44 ++++++++++----------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 5b7f7eb2..1bb72c3e 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -16,16 +16,6 @@ import { IdleTaskQueue } from 'common/TaskQueue'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; import { EventEmitter } from 'common/EventEmitter'; -// For debugging purposes, it can be useful to set this to a really tiny value. -const TEXTURE_WIDTH = 512; -const TEXTURE_HEIGHT = 512; - -/** - * The amount of the texture to be filled before throwing it away and starting - * again. Since the throw away and individual glyph draws don't cost too much, - * this prevent juggling multiple textures in the GL context. - */ -const TEXTURE_CAPACITY = Math.floor(TEXTURE_HEIGHT * 0.8); /** * A shared object which is used to draw nothing for a particular cell. */ @@ -77,7 +67,7 @@ export class TextureAtlas implements ITextureAtlas { private readonly _config: ICharAtlasConfig, private readonly _unicodeService: IUnicodeService ) { - this._pages.push(new AtlasPage(_document)); + this._pages.push(new AtlasPage(_document, _config.devicePixelRatio)); this._tmpCanvas = createCanvas( _document, this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2, @@ -116,10 +106,12 @@ export class TextureAtlas implements ITextureAtlas { } public beginFrame(): boolean { - if (this._pages[this._pages.length - 1].currentRow.y > TEXTURE_CAPACITY) { + const page = this._pages[this._pages.length - 1]; + // TODO: Fill the page completely + if (page.currentRow.y > Math.floor(page.canvas.height * 0.8)) { // TODO: Support drawing to multiple pages at once console.log(`Add page #${this._pages.length + 1}`); - const newPage = new AtlasPage(this._document); + const newPage = new AtlasPage(this._document, this._config.devicePixelRatio); this._pages.push(newPage); this._onAddTextureAtlasCanvas.fire(newPage.canvas); return true; @@ -595,11 +587,11 @@ export class TextureAtlas implements ITextureAtlas { return NULL_RASTERIZED_GLYPH; } - const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, restrictedPowerlineGlyph, customGlyph, padding); + const page = this._pages[this._pages.length - 1]; + const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, restrictedPowerlineGlyph, customGlyph, padding, page.canvas.width, page.canvas.height); // Find the best atlas row to use let activeRow: ICharAtlasActiveRow; - const page = this._pages[this._pages.length - 1]; while (true) { // Select the ideal existing row, preferring fixed rows over the current row activeRow = page.currentRow; @@ -634,7 +626,7 @@ export class TextureAtlas implements ITextureAtlas { } // Exit the loop if there is enough room in the row - if (activeRow.x + rasterizedGlyph.size.x <= TEXTURE_WIDTH) { + if (activeRow.x + rasterizedGlyph.size.x <= page.canvas.width) { break; } @@ -652,8 +644,8 @@ export class TextureAtlas implements ITextureAtlas { rasterizedGlyph.texturePage = this._pages.length - 1; rasterizedGlyph.texturePosition.x = activeRow.x; rasterizedGlyph.texturePosition.y = activeRow.y; - rasterizedGlyph.texturePositionClipSpace.x = activeRow.x / TEXTURE_WIDTH; - rasterizedGlyph.texturePositionClipSpace.y = activeRow.y / TEXTURE_HEIGHT; + rasterizedGlyph.texturePositionClipSpace.x = activeRow.x / page.canvas.width; + rasterizedGlyph.texturePositionClipSpace.y = activeRow.y / page.canvas.height; // Update atlas current row, for fixed rows the glyph height will never be larger than the row // height @@ -681,7 +673,7 @@ export class TextureAtlas implements ITextureAtlas { * @param imageData The image data to read. * @param boundingBox An IBoundingBox to put the clipped bounding box values. */ - private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number): IRasterizedGlyph { + private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean, padding: number, pageWidth: number, pageHeight: number): IRasterizedGlyph { boundingBox.top = 0; const height = restrictedGlyph ? this._config.deviceCellHeight : this._tmpCanvas.height; const width = restrictedGlyph ? this._config.deviceCellWidth : allowedWidth; @@ -753,8 +745,8 @@ export class TextureAtlas implements ITextureAtlas { y: boundingBox.bottom - boundingBox.top + 1 }, sizeClipSpace: { - x: (boundingBox.right - boundingBox.left + 1) / TEXTURE_WIDTH, - y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT + x: (boundingBox.right - boundingBox.left + 1) / pageWidth, + y: (boundingBox.bottom - boundingBox.top + 1) / pageHeight }, offset: { x: -boundingBox.left + padding + ((restrictedGlyph || customGlyph) ? Math.floor((this._config.deviceCellWidth - this._config.deviceCharWidth) / 2) : 0), @@ -785,8 +777,12 @@ class AtlasPage { }; public readonly fixedRows: ICharAtlasActiveRow[] = []; - constructor(document: Document) { - this.canvas = createCanvas(document, TEXTURE_WIDTH, TEXTURE_HEIGHT); + constructor( + document: Document, + dpr: number + ) { + const size = Math.pow(2, 8 + Math.max(1, dpr)); + this.canvas = createCanvas(document, size, size); // 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. @@ -794,7 +790,7 @@ class AtlasPage { } public clear(): void { - this.ctx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.currentRow.x = 0; this.currentRow.y = 0; this.currentRow.height = 0; From c6e3cc007cd024506321def1523d65ef265025eb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 29 Oct 2022 12:49:11 -0700 Subject: [PATCH 58/95] Stop atlas flickering on demo --- demo/client.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/demo/client.ts b/demo/client.ts index 3829b6dd..3c8a2919 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-syntax */ /** * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT @@ -650,11 +651,17 @@ function htmlSerializeButtonHandler(): void { } function setTextureAtlas(e: HTMLCanvasElement): void { + styleAtlasPage(e); document.querySelector('#texture-atlas').replaceChildren(e); } function appendTextureAtlas(e: HTMLCanvasElement): void { + styleAtlasPage(e); document.querySelector('#texture-atlas').appendChild(e); } +function styleAtlasPage(e: HTMLCanvasElement): void { + e.style.width = `${e.width / window.devicePixelRatio}px`; + e.style.height = `${e.height / window.devicePixelRatio}px`; +} function writeCustomGlyphHandler(): void { term.write('\n\r'); From 0a49a5dde1afd433ce354de06bb6e7f54a55089f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 29 Oct 2022 13:01:41 -0700 Subject: [PATCH 59/95] Make texture atlas presentation nicer for multiple pages --- demo/index.html | 2 ++ demo/style.css | 12 +++++------- src/browser/renderer/shared/TextureAtlas.ts | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/demo/index.html b/demo/index.html index a65b8bbc..446d1d5b 100644 --- a/demo/index.html +++ b/demo/index.html @@ -91,6 +91,8 @@ + +