From 9fb84cb89df6754d06c470748825aa0cdd71077a Mon Sep 17 00:00:00 2001 From: TatLead Date: Mon, 19 Sep 2022 05:53:50 +0800 Subject: [PATCH 01/41] Update xterm-headless.d.ts --- typings/xterm-headless.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 1c0a986ad..7453b7e26 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -19,8 +19,7 @@ declare module 'xterm-headless' { export interface ITerminalOptions { /** * Whether to allow the use of proposed API. When false, any usage of APIs - * marked as experimental/proposed will throw an error. This defaults to - * true currently, but will change to false in v5.0. + * marked as experimental/proposed will throw an error. The default is false. */ allowProposedApi?: boolean; From 11c806a4d034a8247de9f40d1e74b24fe7fcef09 Mon Sep 17 00:00:00 2001 From: TatLead Date: Mon, 19 Sep 2022 05:54:11 +0800 Subject: [PATCH 02/41] Update xterm.d.ts --- typings/xterm.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d66e3b03c..0e2b0357c 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -26,8 +26,7 @@ declare module 'xterm' { export interface ITerminalOptions { /** * Whether to allow the use of proposed API. When false, any usage of APIs - * marked as experimental/proposed will throw an error. This defaults to - * true currently, but will change to false in v5.0. + * marked as experimental/proposed will throw an error. The default is false. */ allowProposedApi?: boolean; From 78128762f921e7ea1dcb286a75586fae2e1d21d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 22 Sep 2022 13:06:08 -0700 Subject: [PATCH 03/41] Perform texture atlas warm up in an idle callback Part of #4103 --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b4593d909..9ba83de6d 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -121,7 +121,9 @@ export class WebglCharAtlas implements IDisposable { public warmUp(): void { if (!this._didWarmUp) { - this._doWarmUp(); + (typeof requestIdleCallback !== 'function' ? requestIdleCallback : setTimeout)(() => { + this._doWarmUp(); + }); this._didWarmUp = true; } } @@ -129,8 +131,10 @@ export class WebglCharAtlas implements IDisposable { private _doWarmUp(): void { // Pre-fill with ASCII 33-126 for (let i = 33; i < 126; i++) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); - this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); + if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) { + const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); + this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); + } } } From 09a5da5d33b14f93d888736faa975a2fa14e34d1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 23 Sep 2022 10:11:24 -0700 Subject: [PATCH 04/41] Fix width of upper 1/8 block char See microsoft/vscode#161323 --- src/browser/renderer/CustomGlyphs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/CustomGlyphs.ts b/src/browser/renderer/CustomGlyphs.ts index 4c3874c7a..32256cf4b 100644 --- a/src/browser/renderer/CustomGlyphs.ts +++ b/src/browser/renderer/CustomGlyphs.ts @@ -33,7 +33,7 @@ export const blockElementDefinitions: { [index: string]: IBlockVector[] | undefi '▐': [{ x: 4, y: 0, w: 4, h: 8 }], // RIGHT HALF BLOCK // Block elements (0x2594-0x2595) - '▔': [{ x: 0, y: 0, w: 9, h: 1 }], // UPPER ONE EIGHTH BLOCK + '▔': [{ x: 0, y: 0, w: 8, h: 1 }], // UPPER ONE EIGHTH BLOCK '▕': [{ x: 7, y: 0, w: 1, h: 8 }], // RIGHT ONE EIGHTH BLOCK // Terminal graphic characters (0x2596-0x259F) From d62b4531c3a6e72b964c4a6c4e8706a2b55be9fa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 06:58:01 -0700 Subject: [PATCH 05/41] Optimize contexts for reading frequently This was a recommendation from Edge. Docs here: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 5 ++++- src/browser/ColorManager.ts | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9ba83de6d..092cf2e5b 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -110,7 +110,10 @@ export class WebglCharAtlas implements IDisposable { 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._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency })); + this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { + alpha: this._config.allowTransparency, + willReadFrequently: true + })); } public dispose(): void { diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index d8dcde6fc..4ec0053c6 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -89,7 +89,9 @@ export class ColorManager implements IColorManager { const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d', { + willReadFrequently: true + }); if (!ctx) { throw new Error('Could not get rendering context'); } From 81d68b115c8f86269c6a91e6641ad7cf458382b2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 10:00:22 -0700 Subject: [PATCH 06/41] Share rgba vars throughout Color.ts, fast setTheme parseColor --- src/browser/ColorManager.ts | 20 ++++++++---- src/common/Color.ts | 63 ++++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index d8dcde6fc..5bd6319cc 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -220,21 +220,29 @@ export class ColorManager implements IColorManager { } private _parseColor( - css: string | undefined, + cssString: string | undefined, fallback: IColor, allowTransparency: boolean = this.allowTransparency ): IColor { - if (css === undefined) { + if (cssString === undefined) { return fallback; } + // Fast path: avoid parsing via canvas if it looks like #RGB[A] or #RRGGBB[AA] + if (cssString.startsWith('#')) { + const c = css.toColor(cssString); + if (c) { + return c; + } + } + // If parsing the value results in failure, then it must be ignored, and the attribute must // retain its previous value. // -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles this._ctx.fillStyle = this._litmusColor; - this._ctx.fillStyle = css; + this._ctx.fillStyle = cssString; if (typeof this._ctx.fillStyle !== 'string') { - console.warn(`Color: ${css} is invalid using fallback ${fallback.css}`); + console.warn(`Color: ${cssString} is invalid using fallback ${fallback.css}`); return fallback; } @@ -261,7 +269,7 @@ export class ColorManager implements IColorManager { // // So let's just use the fallback color in this case instead. console.warn( - `Color: ${css} is using transparency, but allowTransparency is false. ` + + `Color: ${cssString} is using transparency, but allowTransparency is false. ` + `Using fallback ${fallback.css}.` ); return fallback; @@ -274,7 +282,7 @@ export class ColorManager implements IColorManager { const rgba: number = channels.toRgba(r, g, b, alpha); return { rgba, - css + css: cssString }; } diff --git a/src/common/Color.ts b/src/common/Color.ts index a66e39c1b..a9f8cc029 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -5,6 +5,11 @@ import { IColor, IColorRGB } from 'common/Types'; +let $r = 0; +let $g = 0; +let $b = 0; +let $a = 0; + /** * Helper functions where the source type is "channels" (individual color channels as numbers). */ @@ -29,8 +34,8 @@ export namespace channels { */ export namespace color { export function blend(bg: IColor, fg: IColor): IColor { - const a = (fg.rgba & 0xFF) / 255; - if (a === 1) { + $a = (fg.rgba & 0xFF) / 255; + if ($a === 1) { return { css: fg.css, rgba: fg.rgba @@ -42,11 +47,11 @@ export namespace color { const bgR = (bg.rgba >> 24) & 0xFF; const bgG = (bg.rgba >> 16) & 0xFF; const bgB = (bg.rgba >> 8) & 0xFF; - const r = bgR + Math.round((fgR - bgR) * a); - const g = bgG + Math.round((fgG - bgG) * a); - const b = bgB + Math.round((fgB - bgB) * a); - const css = channels.toCss(r, g, b); - const rgba = channels.toRgba(r, g, b); + $r = bgR + Math.round((fgR - bgR) * $a); + $g = bgG + Math.round((fgG - bgG) * $a); + $b = bgB + Math.round((fgB - bgB) * $a); + const css = channels.toCss($r, $g, $b); + const rgba = channels.toRgba($r, $g, $b); return { css, rgba }; } @@ -68,25 +73,25 @@ export namespace color { export function opaque(color: IColor): IColor { const rgbaColor = (color.rgba | 0xFF) >>> 0; - const [r, g, b] = rgba.toChannels(rgbaColor); + [$r, $g, $b] = rgba.toChannels(rgbaColor); return { - css: channels.toCss(r, g, b), + css: channels.toCss($r, $g, $b), rgba: rgbaColor }; } export function opacity(color: IColor, opacity: number): IColor { - const a = Math.round(opacity * 0xFF); - const [r, g, b] = rgba.toChannels(color.rgba); + $a = Math.round(opacity * 0xFF); + [$r, $g, $b] = rgba.toChannels(color.rgba); return { - css: channels.toCss(r, g, b, a), - rgba: channels.toRgba(r, g, b, a) + css: channels.toCss($r, $g, $b, $a), + rgba: channels.toRgba($r, $g, $b, $a) }; } export function multiplyOpacity(color: IColor, factor: number): IColor { - const a = color.rgba & 0xFF; - return opacity(color, (a * factor) / 0xFF); + $a = color.rgba & 0xFF; + return opacity(color, ($a * factor) / 0xFF); } export function toColorRGB(color: IColor): IColorRGB { @@ -102,17 +107,17 @@ export namespace css { if (css.match(/#[0-9a-f]{3,8}/i)) { switch (css.length) { case 4: { // #rgb - const r = parseInt(css.slice(1, 2).repeat(2), 16); - const g = parseInt(css.slice(2, 3).repeat(2), 16); - const b = parseInt(css.slice(3, 4).repeat(2), 16); - return rgba.toColor(r, g, b); + $r = parseInt(css.slice(1, 2).repeat(2), 16); + $g = parseInt(css.slice(2, 3).repeat(2), 16); + $b = parseInt(css.slice(3, 4).repeat(2), 16); + return rgba.toColor($r, $g, $b); } case 5: { // #rgba - const r = parseInt(css.slice(1, 2).repeat(2), 16); - const g = parseInt(css.slice(2, 3).repeat(2), 16); - const b = parseInt(css.slice(3, 4).repeat(2), 16); - const a = parseInt(css.slice(4, 5).repeat(2), 16); - return rgba.toColor(r, g, b, a); + $r = parseInt(css.slice(1, 2).repeat(2), 16); + $g = parseInt(css.slice(2, 3).repeat(2), 16); + $b = parseInt(css.slice(3, 4).repeat(2), 16); + $a = parseInt(css.slice(4, 5).repeat(2), 16); + return rgba.toColor($r, $g, $b, $a); } case 7: // #rrggbb return { @@ -128,11 +133,11 @@ export namespace css { } const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/); if (rgbaMatch) { // rgb() or rgba() - const r = parseInt(rgbaMatch[1]); - const g = parseInt(rgbaMatch[2]); - const b = parseInt(rgbaMatch[3]); - const a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF); - return rgba.toColor(r, g, b, a); + $r = parseInt(rgbaMatch[1]); + $g = parseInt(rgbaMatch[2]); + $b = parseInt(rgbaMatch[3]); + $a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF); + return rgba.toColor($r, $g, $b, $a); } throw new Error('css.toColor: Unsupported css format'); } From 6368cb4cff68d41a28db24e9d1af72973b7d9a64 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 10:37:40 -0700 Subject: [PATCH 07/41] Move canvas-based parsing to Color.ts lib This removed the allowTransparency code, it didn't actually do much before, just whether the 'advanced parsing' would allow transparency. Since we already do the common formats, I'm not even sure what other format it could be. --- .../src/SerializeAddon.test.ts | 2 +- src/browser/ColorManager.test.ts | 2 +- src/browser/ColorManager.ts | 101 +++--------------- src/browser/Terminal.ts | 2 +- src/common/Color.ts | 57 +++++++++- src/common/Platform.ts | 2 +- 6 files changed, 72 insertions(+), 94 deletions(-) diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts index 05f2c61c7..df4a72da6 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts @@ -78,7 +78,7 @@ describe('xterm-addon-serialize', () => { terminal.loadAddon(serializeAddon); selectionService = new TestSelectionService((terminal as any)._core._bufferService); - cm = new ColorManager(document, false); + cm = new ColorManager(); (terminal as any)._core._colorManager = cm; (terminal as any)._core._selectionService = selectionService; }); diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index 019bf42a3..cf60a1f59 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -28,7 +28,7 @@ describe('ColorManager', () => { return {data: [0, 0, 0, 0xFF]}; } }); - cm = new ColorManager(document, false); + cm = new ColorManager(); }); describe('constructor', () => { diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 5bd6319cc..a22a423fd 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -80,22 +80,11 @@ export const DEFAULT_ANSI_COLORS = Object.freeze((() => { */ export class ColorManager implements IColorManager { public colors: IColorSet; - private _ctx: CanvasRenderingContext2D; - private _litmusColor: CanvasGradient; + private _contrastCache: IColorContrastCache; private _restoreColors!: IRestoreColorSet; - constructor(document: Document, public allowTransparency: boolean) { - const canvas = document.createElement('canvas'); - canvas.width = 1; - canvas.height = 1; - const ctx = canvas.getContext('2d'); - if (!ctx) { - throw new Error('Could not get rendering context'); - } - this._ctx = ctx; - this._ctx.globalCompositeOperation = 'copy'; - this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1); + constructor() { this._contrastCache = new ColorContrastCache(); this.colors = { foreground: DEFAULT_FOREGROUND, @@ -118,9 +107,6 @@ export class ColorManager implements IColorManager { case 'minimumContrastRatio': this._contrastCache.clear(); break; - case 'allowTransparency': - this.allowTransparency = value; - break; } } @@ -132,11 +118,11 @@ export class ColorManager implements IColorManager { public setTheme(theme: ITheme = {}): void { this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND); this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); - this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); - this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); - this.colors.selectionBackgroundTransparent = this._parseColor(theme.selectionBackground, DEFAULT_SELECTION, true); + this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR); + this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); + this.colors.selectionBackgroundTransparent = this._parseColor(theme.selectionBackground, DEFAULT_SELECTION); this.colors.selectionBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionBackgroundTransparent); - this.colors.selectionInactiveBackgroundTransparent = this._parseColor(theme.selectionInactiveBackground, this.colors.selectionBackgroundTransparent, true); + this.colors.selectionInactiveBackgroundTransparent = this._parseColor(theme.selectionInactiveBackground, this.colors.selectionBackgroundTransparent); this.colors.selectionInactiveBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionInactiveBackgroundTransparent); const nullColor: IColor = { css: '', @@ -221,76 +207,15 @@ export class ColorManager implements IColorManager { private _parseColor( cssString: string | undefined, - fallback: IColor, - allowTransparency: boolean = this.allowTransparency + fallback: IColor ): IColor { - if (cssString === undefined) { - return fallback; - } - - // Fast path: avoid parsing via canvas if it looks like #RGB[A] or #RRGGBB[AA] - if (cssString.startsWith('#')) { - const c = css.toColor(cssString); - if (c) { - return c; + if (cssString !== undefined) { + try { + return css.toColor(cssString); + } catch { + // no-op } } - - // If parsing the value results in failure, then it must be ignored, and the attribute must - // retain its previous value. - // -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles - this._ctx.fillStyle = this._litmusColor; - this._ctx.fillStyle = cssString; - if (typeof this._ctx.fillStyle !== 'string') { - console.warn(`Color: ${cssString} is invalid using fallback ${fallback.css}`); - return fallback; - } - - this._ctx.fillRect(0, 0, 1, 1); - const data = this._ctx.getImageData(0, 0, 1, 1).data; - - // Check if the printed color was transparent - if (data[3] !== 0xFF) { - if (!allowTransparency) { - // Ideally we'd just ignore the alpha channel, but... - // - // Browsers may not give back exactly the same RGB values we put in, because most/all - // convert the color to a pre-multiplied representation. getImageData converts that back to - // a un-premultipled representation, but the precision loss may make the RGB channels unuable - // on their own. - // - // E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns - // into 0x00000000. - // - // "Note: Due to the lossy nature of converting to and from premultiplied alpha color values, - // pixels that have just been set using putImageData() might be returned to an equivalent - // getImageData() as different values." - // -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation - // - // So let's just use the fallback color in this case instead. - console.warn( - `Color: ${cssString} is using transparency, but allowTransparency is false. ` + - `Using fallback ${fallback.css}.` - ); - return fallback; - } - - // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color - // the color value has alpha less than 1.0, and the string is the color value in the CSS rgba() - const [r, g, b, a] = this._ctx.fillStyle.substring(5, this._ctx.fillStyle.length - 1).split(',').map(component => Number(component)); - const alpha = Math.round(a * 255); - const rgba: number = channels.toRgba(r, g, b, alpha); - return { - rgba, - css: cssString - }; - } - - return { - // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color - // if it has alpha equal to 1.0, then the string is a lowercase six-digit hex value, prefixed with a "#" character - css: this._ctx.fillStyle, - rgba: channels.toRgba(data[0], data[1], data[2], data[3]) - }; + return fallback; } } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9aba38186..afdac748c 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -502,7 +502,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._instantiationService.setService(ICharSizeService, this._charSizeService); this._theme = this.options.theme || this._theme; - this._colorManager = new ColorManager(document, this.options.allowTransparency); + this._colorManager = new ColorManager(); this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e, this.optionsService.rawOptions[e]))); this._colorManager.setTheme(this._theme); diff --git a/src/common/Color.ts b/src/common/Color.ts index a9f8cc029..35bcdb880 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { isNode } from 'common/Platform'; import { IColor, IColorRGB } from 'common/Types'; let $r = 0; @@ -103,7 +104,29 @@ export namespace color { * Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', '#rrggbbaa'). */ export namespace css { + let $ctx: CanvasRenderingContext2D | undefined; + let $litmusColor: CanvasGradient | undefined; + if (!isNode) { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + if (ctx) { + $ctx = ctx; + $ctx.globalCompositeOperation = 'copy'; + $litmusColor = $ctx.createLinearGradient(0, 0, 1, 1); + } + } + + /** + * Converts a css string to an IColor, this should handle all valid CSS color strings and will + * throw if it's invalid. The ideal format to use is `#rrggbb[aa]` as it's the fastest to parse. + * + * Only `#rgb[a]`, `#rrggbb[aa]`, `rgb()` and `rgba()` formats are supported when run in a Node + * environment. + */ export function toColor(css: string): IColor { + // Formats: #rgb[a] and #rrggbb[aa] if (css.match(/#[0-9a-f]{3,8}/i)) { switch (css.length) { case 4: { // #rgb @@ -131,15 +154,45 @@ export namespace css { }; } } + + // Formats: rgb() or rgba() const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/); - if (rgbaMatch) { // rgb() or rgba() + if (rgbaMatch) { $r = parseInt(rgbaMatch[1]); $g = parseInt(rgbaMatch[2]); $b = parseInt(rgbaMatch[3]); $a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF); return rgba.toColor($r, $g, $b, $a); } - throw new Error('css.toColor: Unsupported css format'); + + // Validate the context is available for canvas-based color parsing + if (!$ctx || !$litmusColor) { + throw new Error('css.toColor: Unsupported css format'); + } + + // Validate the color using canvas fillStyle + // See https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles + $ctx.fillStyle = $litmusColor; + $ctx.fillStyle = css; + if (typeof $ctx.fillStyle !== 'string') { + throw new Error('css.toColor: Unsupported css format'); + } + + $ctx.fillRect(0, 0, 1, 1); + [$r, $g, $b, $a] = $ctx.getImageData(0, 0, 1, 1).data; + + // Validate the color is non-transparent as color hue gets lost when drawn to the canvas + if ($a !== 0xFF) { + throw new Error('css.toColor: Unsupported css format'); + } + + // Extract the color from the canvas' fillStyle property which exposes the color value in rgba() + // format + // See https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color + return { + rgba: channels.toRgba($r, $g, $b, $a), + css + }; } } diff --git a/src/common/Platform.ts b/src/common/Platform.ts index 7b823b122..6be0584f9 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -13,7 +13,7 @@ interface INavigator { // we want this module to live in common. declare const navigator: INavigator; -const isNode = (typeof navigator === 'undefined') ? true : false; +export const isNode = (typeof navigator === 'undefined') ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; From c42f290eb8cadea56eff5b0f6dbfb995b4048c9e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:10:26 -0700 Subject: [PATCH 08/41] Do char atlas warmup via new IdleTaskQueue --- .../src/atlas/WebglCharAtlas.ts | 18 ++++---- src/common/IdleTaskQueue.ts | 41 +++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/common/IdleTaskQueue.ts diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 092cf2e5b..d23caac06 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -16,6 +16,7 @@ import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; +import { IdleTaskQueue } from 'common/IdleTaskQueue'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -124,20 +125,21 @@ export class WebglCharAtlas implements IDisposable { public warmUp(): void { if (!this._didWarmUp) { - (typeof requestIdleCallback !== 'function' ? requestIdleCallback : setTimeout)(() => { - this._doWarmUp(); - }); + this._doWarmUp(); this._didWarmUp = true; } } private _doWarmUp(): void { - // Pre-fill with ASCII 33-126 + // Pre-fill with ASCII 33-126, this is not urgent and done in idle callbacks + const queue = new IdleTaskQueue(); for (let i = 33; i < 126; i++) { - if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); - this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); - } + queue.enqueue(() => { + if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) { + const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT); + this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph); + } + }); } } diff --git a/src/common/IdleTaskQueue.ts b/src/common/IdleTaskQueue.ts new file mode 100644 index 000000000..6d3b341b1 --- /dev/null +++ b/src/common/IdleTaskQueue.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export class IdleTaskQueue { + private _tasks: Function[] = []; + private _idleCallback?: number; + private _maxTaskDuration: number; + private _i = 0; + + constructor(targetFps: number = 240) { + this._maxTaskDuration = 1000 / targetFps; + } + + public enqueue(task: Function): void { + this._tasks.push(task); + this._start(); + } + + private _start(): void { + if (!this._idleCallback) { + this._idleCallback = requestIdleCallback(() => this._process()); + } + } + + private _process(): void { + const start = performance.now(); + this._idleCallback = undefined; + while (this._i < this._tasks.length) { + this._tasks[this._i++](); + if (performance.now() - start > this._maxTaskDuration) { + this._start(); + return; + } + } + // Clear the queue + this._i = 0; + this._tasks.length = 0; + } +} From 270ac1c7d9b6b3d23e99652f68e24d6c867aa1cc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:17:39 -0700 Subject: [PATCH 09/41] Docs --- src/common/IdleTaskQueue.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/common/IdleTaskQueue.ts b/src/common/IdleTaskQueue.ts index 6d3b341b1..240a26ff6 100644 --- a/src/common/IdleTaskQueue.ts +++ b/src/common/IdleTaskQueue.ts @@ -3,16 +3,27 @@ * @license MIT */ +/** + * A queue of that runs tasks over several idle callbacks, trying to maintain the specified + * frame rate. The tasks will run in the order they are enqueued, but they will run some time later, + * and care should be taken to ensure they're non-urgent and will not introduce race conditions. + */ export class IdleTaskQueue { private _tasks: Function[] = []; private _idleCallback?: number; private _maxTaskDuration: number; private _i = 0; + /** + * @param targetFps The target frame rate. + */ constructor(targetFps: number = 240) { this._maxTaskDuration = 1000 / targetFps; } + /** + * Adds a task to the queue which will run in a future idle callback. + */ public enqueue(task: Function): void { this._tasks.push(task); this._start(); From 4e8e6b978889c32fa4d8711c87dac2a1d5552637 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:45:48 -0700 Subject: [PATCH 10/41] Defer paused renderer resize to idle callback --- src/browser/services/RenderService.ts | 10 +++++++++- src/common/IdleTaskQueue.ts | 26 +++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 7db0e5cd1..880a06671 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,6 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; +import { IdleTaskQueue } from 'common/IdleTaskQueue'; interface ISelectionState { start: [number, number] | undefined; @@ -105,6 +106,7 @@ export class RenderService extends Disposable implements IRenderService { } if (!this._isPaused && this._needsFullRefresh) { + this._pausedResizeQueue.flush(); this.refreshRows(0, this._rowCount - 1); this._needsFullRefresh = false; } @@ -203,8 +205,14 @@ export class RenderService extends Disposable implements IRenderService { this.refreshRows(0, this._rowCount - 1); } + private _pausedResizeQueue = new IdleTaskQueue(); public onResize(cols: number, rows: number): void { - this._renderer.onResize(cols, rows); + if (this._isPaused) { + this._pausedResizeQueue.clear(); + this._pausedResizeQueue.enqueue(() => this._renderer.onResize(cols, rows)); + } else { + this._renderer.onResize(cols, rows); + } this._fullRefresh(); } diff --git a/src/common/IdleTaskQueue.ts b/src/common/IdleTaskQueue.ts index 240a26ff6..167e79e9d 100644 --- a/src/common/IdleTaskQueue.ts +++ b/src/common/IdleTaskQueue.ts @@ -29,6 +29,28 @@ export class IdleTaskQueue { this._start(); } + /** + * Flushes the queue, running all remaining tasks synchronously. + */ + public flush(): void { + while (this._i < this._tasks.length) { + this._tasks[this._i++](); + } + this.clear(); + } + + /** + * Clears any remaining tasks from the queue, these will not be run. + */ + public clear(): void { + if (this._idleCallback) { + cancelIdleCallback(this._idleCallback); + this._idleCallback = undefined; + } + this._i = 0; + this._tasks.length = 0; + } + private _start(): void { if (!this._idleCallback) { this._idleCallback = requestIdleCallback(() => this._process()); @@ -45,8 +67,6 @@ export class IdleTaskQueue { return; } } - // Clear the queue - this._i = 0; - this._tasks.length = 0; + this.clear(); } } From 4ac5a91c95aa877c457d476c80a389551a4a8b61 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:56:46 -0700 Subject: [PATCH 11/41] Make DebouncedIdleTask helper --- src/browser/services/RenderService.ts | 9 ++++----- src/common/{IdleTaskQueue.ts => Idle.ts} | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 7 deletions(-) rename src/common/{IdleTaskQueue.ts => Idle.ts} (79%) diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 880a06671..ca1321d62 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { IdleTaskQueue } from 'common/IdleTaskQueue'; +import { DebouncedIdleTask } from 'common/Idle'; interface ISelectionState { start: [number, number] | undefined; @@ -106,7 +106,7 @@ export class RenderService extends Disposable implements IRenderService { } if (!this._isPaused && this._needsFullRefresh) { - this._pausedResizeQueue.flush(); + this._pausedResizeTask.flush(); this.refreshRows(0, this._rowCount - 1); this._needsFullRefresh = false; } @@ -205,11 +205,10 @@ export class RenderService extends Disposable implements IRenderService { this.refreshRows(0, this._rowCount - 1); } - private _pausedResizeQueue = new IdleTaskQueue(); + private _pausedResizeTask = new DebouncedIdleTask(); public onResize(cols: number, rows: number): void { if (this._isPaused) { - this._pausedResizeQueue.clear(); - this._pausedResizeQueue.enqueue(() => this._renderer.onResize(cols, rows)); + this._pausedResizeTask.set(() => this._renderer.onResize(cols, rows)); } else { this._renderer.onResize(cols, rows); } diff --git a/src/common/IdleTaskQueue.ts b/src/common/Idle.ts similarity index 79% rename from src/common/IdleTaskQueue.ts rename to src/common/Idle.ts index 167e79e9d..529d38a4e 100644 --- a/src/common/IdleTaskQueue.ts +++ b/src/common/Idle.ts @@ -9,7 +9,7 @@ * and care should be taken to ensure they're non-urgent and will not introduce race conditions. */ export class IdleTaskQueue { - private _tasks: Function[] = []; + private _tasks: (() => void)[] = []; private _idleCallback?: number; private _maxTaskDuration: number; private _i = 0; @@ -24,7 +24,7 @@ export class IdleTaskQueue { /** * Adds a task to the queue which will run in a future idle callback. */ - public enqueue(task: Function): void { + public enqueue(task: () => void): void { this._tasks.push(task); this._start(); } @@ -70,3 +70,23 @@ export class IdleTaskQueue { this.clear(); } } + +export class DebouncedIdleTask { + private _queue: IdleTaskQueue; + + /** + * @param targetFps The target frame rate. + */ + constructor(targetFps: number = 240) { + this._queue = new IdleTaskQueue(targetFps); + } + + public set(task: () => void): void { + this._queue.clear(); + this._queue.enqueue(task); + } + + public flush(): void { + this._queue.flush(); + } +} From 66efb0388abdf8c8dea9c3b6ed3bc51a6916b6bf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:57:12 -0700 Subject: [PATCH 12/41] Move prop to top --- src/browser/services/RenderService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index ca1321d62..3849bdd33 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -25,6 +25,7 @@ export class RenderService extends Disposable implements IRenderService { private _renderDebouncer: IRenderDebouncerWithCallback; private _screenDprMonitor: ScreenDprMonitor; + private _pausedResizeTask = new DebouncedIdleTask(); private _isPaused: boolean = false; private _needsFullRefresh: boolean = false; @@ -205,7 +206,6 @@ export class RenderService extends Disposable implements IRenderService { this.refreshRows(0, this._rowCount - 1); } - private _pausedResizeTask = new DebouncedIdleTask(); public onResize(cols: number, rows: number): void { if (this._isPaused) { this._pausedResizeTask.set(() => this._renderer.onResize(cols, rows)); From aec1f2b2be7687d333d1eedf4724a88e2bf642f1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 11:58:17 -0700 Subject: [PATCH 13/41] Fix import --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index d23caac06..dfcede38a 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -16,7 +16,7 @@ import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; -import { IdleTaskQueue } from 'common/IdleTaskQueue'; +import { IdleTaskQueue } from 'common/Idle'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. From 1f45d5344c7049d8997a011b5378b73dce0f8d7a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:10:29 -0700 Subject: [PATCH 14/41] Use the browser's IdleDeadline to determine max task time --- src/common/Idle.ts | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/common/Idle.ts b/src/common/Idle.ts index 529d38a4e..2aebc599d 100644 --- a/src/common/Idle.ts +++ b/src/common/Idle.ts @@ -11,16 +11,8 @@ export class IdleTaskQueue { private _tasks: (() => void)[] = []; private _idleCallback?: number; - private _maxTaskDuration: number; private _i = 0; - /** - * @param targetFps The target frame rate. - */ - constructor(targetFps: number = 240) { - this._maxTaskDuration = 1000 / targetFps; - } - /** * Adds a task to the queue which will run in a future idle callback. */ @@ -53,16 +45,22 @@ export class IdleTaskQueue { private _start(): void { if (!this._idleCallback) { - this._idleCallback = requestIdleCallback(() => this._process()); + this._idleCallback = requestIdleCallback(this._process.bind(this)); } } - private _process(): void { - const start = performance.now(); + private _process(deadline: IdleDeadline): void { this._idleCallback = undefined; + let taskDuration = 0; + let longestTask = 0; while (this._i < this._tasks.length) { + taskDuration = performance.now(); this._tasks[this._i++](); - if (performance.now() - start > this._maxTaskDuration) { + taskDuration = performance.now() - taskDuration; + longestTask = Math.max(taskDuration, longestTask); + // Guess the following task will take a similar time to task that just finished, allow + // additional room to try avoid exceeding the deadline + if (longestTask * 1.5 > deadline.timeRemaining()) { this._start(); return; } @@ -71,14 +69,15 @@ export class IdleTaskQueue { } } +/** + * An object that tracks a single debounced task that will run on the next idle frame. When called + * multiple times, only the last set task will run. + */ export class DebouncedIdleTask { private _queue: IdleTaskQueue; - /** - * @param targetFps The target frame rate. - */ - constructor(targetFps: number = 240) { - this._queue = new IdleTaskQueue(targetFps); + constructor() { + this._queue = new IdleTaskQueue(); } public set(task: () => void): void { From ab90dcd0dc2562a6d0952eeca5c5d531bdb9851b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 14:13:04 -0700 Subject: [PATCH 15/41] Clarify comment --- src/common/Idle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Idle.ts b/src/common/Idle.ts index 2aebc599d..e05ad0687 100644 --- a/src/common/Idle.ts +++ b/src/common/Idle.ts @@ -58,7 +58,7 @@ export class IdleTaskQueue { this._tasks[this._i++](); taskDuration = performance.now() - taskDuration; longestTask = Math.max(taskDuration, longestTask); - // Guess the following task will take a similar time to task that just finished, allow + // Guess the following task will take a similar time to the longest task in this batch, allow // additional room to try avoid exceeding the deadline if (longestTask * 1.5 > deadline.timeRemaining()) { this._start(); From 8f8813a19a52e20d242b5017e109da6805d4d4b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 15:16:02 -0700 Subject: [PATCH 16/41] Add PriorityTaskQueue --- src/common/{Idle.ts => TaskQueue.ts} | 65 +++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 7 deletions(-) rename src/common/{Idle.ts => TaskQueue.ts} (54%) diff --git a/src/common/Idle.ts b/src/common/TaskQueue.ts similarity index 54% rename from src/common/Idle.ts rename to src/common/TaskQueue.ts index e05ad0687..49e3f5ec3 100644 --- a/src/common/Idle.ts +++ b/src/common/TaskQueue.ts @@ -3,16 +3,24 @@ * @license MIT */ -/** - * A queue of that runs tasks over several idle callbacks, trying to maintain the specified - * frame rate. The tasks will run in the order they are enqueued, but they will run some time later, - * and care should be taken to ensure they're non-urgent and will not introduce race conditions. - */ -export class IdleTaskQueue { +import { isNode } from 'common/Platform'; + +interface ITaskQueue { +} + +interface ITaskDeadline { + timeRemaining(): number; +} +type CallbackWithDeadline = (deadline: ITaskDeadline) => void; + +abstract class TaskQueue implements ITaskQueue { private _tasks: (() => void)[] = []; private _idleCallback?: number; private _i = 0; + protected abstract _requestCallback(callback: CallbackWithDeadline): number; + protected abstract _cancelCallback(identifier: number): void; + /** * Adds a task to the queue which will run in a future idle callback. */ @@ -69,12 +77,55 @@ export class IdleTaskQueue { } } +/** + * A queue of that runs tasks over several tasks via setTimeout, trying to maintain above 60 frames + * per second. The tasks will run in the order they are enqueued, but they will run some time later, + * and care should be taken to ensure they're non-urgent and will not introduce race conditions. + */ +export class PriorityTaskQueue extends TaskQueue { + protected _requestCallback(callback: CallbackWithDeadline): number { + return setTimeout(() => callback(this._createDeadline(16))); + } + + protected _cancelCallback(identifier: number): void { + clearTimeout(identifier); + } + + private _createDeadline(duration: number): ITaskDeadline { + const end = performance.now() + duration; + return { + timeRemaining: () => Math.max(0, end - performance.now()) + }; + } +} + +class IdleTaskQueueInternal extends TaskQueue { + protected _requestCallback(callback: IdleRequestCallback): number { + return requestIdleCallback(callback); + } + + protected _cancelCallback(identifier: number): void { + cancelIdleCallback(identifier); + } +} + +/** + * A queue of that runs tasks over several idle callbacks, trying to respect the idle callback's + * deadline given by the environment. The tasks will run in the order they are enqueued, but they + * will run some time later, and care should be taken to ensure they're non-urgent and will not + * introduce race conditions. + * + * This reverts to a {@link PriorityTaskQueue} if the environment does not support idle callbacks. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export const IdleTaskQueue = (!isNode && 'requestIdleCallback' in window) ? IdleTaskQueueInternal : PriorityTaskQueue; + /** * An object that tracks a single debounced task that will run on the next idle frame. When called * multiple times, only the last set task will run. */ export class DebouncedIdleTask { - private _queue: IdleTaskQueue; + private _queue: IdleTaskQueueInternal | PriorityTaskQueue; constructor() { this._queue = new IdleTaskQueue(); From 494fe3a95d8bca2afaaf7af8c10485f2ec5f8167 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:13:53 -0700 Subject: [PATCH 17/41] Fill in ITaskQueue --- src/common/TaskQueue.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/common/TaskQueue.ts b/src/common/TaskQueue.ts index 49e3f5ec3..27e3a492d 100644 --- a/src/common/TaskQueue.ts +++ b/src/common/TaskQueue.ts @@ -6,6 +6,20 @@ import { isNode } from 'common/Platform'; interface ITaskQueue { + /** + * Adds a task to the queue which will run in a future idle callback. + */ + enqueue(task: () => void): void; + + /** + * Flushes the queue, running all remaining tasks synchronously. + */ + flush(): void; + + /** + * Clears any remaining tasks from the queue, these will not be run. + */ + clear(): void; } interface ITaskDeadline { @@ -21,17 +35,11 @@ abstract class TaskQueue implements ITaskQueue { protected abstract _requestCallback(callback: CallbackWithDeadline): number; protected abstract _cancelCallback(identifier: number): void; - /** - * Adds a task to the queue which will run in a future idle callback. - */ public enqueue(task: () => void): void { this._tasks.push(task); this._start(); } - /** - * Flushes the queue, running all remaining tasks synchronously. - */ public flush(): void { while (this._i < this._tasks.length) { this._tasks[this._i++](); @@ -39,9 +47,6 @@ abstract class TaskQueue implements ITaskQueue { this.clear(); } - /** - * Clears any remaining tasks from the queue, these will not be run. - */ public clear(): void { if (this._idleCallback) { cancelIdleCallback(this._idleCallback); From 04d196a32dd36657ad367ed115693b5d5f987981 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:14:38 -0700 Subject: [PATCH 18/41] Use task queue interface --- src/common/TaskQueue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/TaskQueue.ts b/src/common/TaskQueue.ts index 27e3a492d..3b1acfa60 100644 --- a/src/common/TaskQueue.ts +++ b/src/common/TaskQueue.ts @@ -130,7 +130,7 @@ export const IdleTaskQueue = (!isNode && 'requestIdleCallback' in window) ? Idle * multiple times, only the last set task will run. */ export class DebouncedIdleTask { - private _queue: IdleTaskQueueInternal | PriorityTaskQueue; + private _queue: ITaskQueue; constructor() { this._queue = new IdleTaskQueue(); From c548977a3eb5b96f70d440900d66c0d5abf38d80 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:16:14 -0700 Subject: [PATCH 19/41] Fix imports --- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 2 +- src/browser/services/RenderService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index dfcede38a..4764ede4e 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -16,7 +16,7 @@ import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; -import { IdleTaskQueue } from 'common/Idle'; +import { IdleTaskQueue } from 'common/TaskQueue'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 3849bdd33..972586098 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -12,7 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; -import { DebouncedIdleTask } from 'common/Idle'; +import { DebouncedIdleTask } from 'common/TaskQueue'; interface ISelectionState { start: [number, number] | undefined; From c6e4788344bf5f83596efed24a9634496d3df6ad Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:23:14 -0700 Subject: [PATCH 20/41] Use microtasks on server This reduces demo input latency on my mac approx 10ms -> 4ms --- demo/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/server.js b/demo/server.js index 8d295942f..8bb684a2d 100644 --- a/demo/server.js +++ b/demo/server.js @@ -86,7 +86,7 @@ function startServer() { return (data) => { s += data; if (!sender) { - sender = setTimeout(() => { + sender = queueMicrotask(() => { socket.send(s); s = ''; sender = null; @@ -103,7 +103,7 @@ function startServer() { buffer.push(data); length += data.length; if (!sender) { - sender = setTimeout(() => { + sender = queueMicrotask(() => { socket.send(Buffer.concat(buffer, length)); buffer = []; sender = null; From 0b8120808c539feee99965f8fbebc58d4a83ffde Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:27:56 -0700 Subject: [PATCH 21/41] Prefer microtask to timeout in write queue This seems to make little difference but it's more correct as we want to write as fast as possible to minimize input latency --- src/common/input/WriteBuffer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 67fd751e9..c9dfd039c 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -105,7 +105,7 @@ export class WriteBuffer { // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; - setTimeout(() => this._innerWrite()); + queueMicrotask(() => this._innerWrite()); } this._pendingData += data.length; @@ -217,7 +217,7 @@ export class WriteBuffer { this._callbacks = this._callbacks.slice(this._bufferOffset); this._bufferOffset = 0; } - setTimeout(() => this._innerWrite()); + queueMicrotask(() => this._innerWrite()); } else { this._writeBuffer.length = 0; this._callbacks.length = 0; From 2b597a82a119982dbffa1eea6c280c8054e30a61 Mon Sep 17 00:00:00 2001 From: Daniel Steinberg Date: Mon, 26 Sep 2022 11:19:44 -0400 Subject: [PATCH 22/41] Move real-world uses section down --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 2c450e424..5c4d2084e 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,26 @@ The full API for xterm.js is contained within the [TypeScript declaration file]( Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs. +## Releases + +Xterm.js follows a monthly release cycle roughly. + +All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones). + +### Beta builds + +Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with: + +```bash +npm install -S xterm@beta +``` + +These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes. + +## Contributing + +You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development. + ## Real-world uses Xterm.js is used in several world-class applications to provide great terminal experiences. @@ -197,26 +217,6 @@ Xterm.js is used in several world-class applications to provide great terminal e Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only. -## Releases - -Xterm.js follows a monthly release cycle roughly. - -All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones). - -### Beta builds - -Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with: - -```bash -npm install -S xterm@beta -``` - -These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes. - -## Contributing - -You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development. - ## License Agreement If you contribute code to this project, you implicitly allow your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. From 25b69e5ed0939a6c7723421f8c3501c3ee4d0c6c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 27 Sep 2022 06:43:07 -0700 Subject: [PATCH 23/41] Fix repetitive microtask problem See #4145 --- src/common/input/WriteBuffer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index c9dfd039c..3c7727bb2 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -217,7 +217,7 @@ export class WriteBuffer { this._callbacks = this._callbacks.slice(this._bufferOffset); this._bufferOffset = 0; } - queueMicrotask(() => this._innerWrite()); + setTimeout(() => this._innerWrite()); } else { this._writeBuffer.length = 0; this._callbacks.length = 0; From d74e43a42a238d613a34f050f4301fc4a164b096 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 27 Sep 2022 06:44:18 -0700 Subject: [PATCH 24/41] Remove queuemicrotask polyfill Support seems good --- src/common/input/WriteBuffer.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index c9dfd039c..c324d4349 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -33,12 +33,6 @@ const WRITE_TIMEOUT_MS = 12; */ const WRITE_BUFFER_LENGTH_THRESHOLD = 50; -// queueMicrotask polyfill for nodejs < v11 -const qmt: (cb: () => void) => void = (typeof queueMicrotask === 'undefined') - ? (cb: () => void) => { Promise.resolve().then(cb); } - : queueMicrotask; - - export class WriteBuffer { private _writeBuffer: (string | Uint8Array)[] = []; private _callbacks: ((() => void) | undefined)[] = []; @@ -194,7 +188,7 @@ export class WriteBuffer { // 2. spawn a promise immediately resolving to `true` // (executed on the same queue, thus properly aligned before continuation happens) result.catch(err => { - qmt(() => {throw err;}); + queueMicrotask(() => {throw err;}); return Promise.resolve(false); }).then(continuation); return; From 8201925fe886a61626b9201466e87db6e1a9bf83 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 27 Sep 2022 06:46:14 -0700 Subject: [PATCH 25/41] Fix priority task queue and idle callback fallback Mistake of not hooking up the abstract methods, this should throw on Safari for example. --- src/common/TaskQueue.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/TaskQueue.ts b/src/common/TaskQueue.ts index 3b1acfa60..94c5c53b3 100644 --- a/src/common/TaskQueue.ts +++ b/src/common/TaskQueue.ts @@ -49,7 +49,7 @@ abstract class TaskQueue implements ITaskQueue { public clear(): void { if (this._idleCallback) { - cancelIdleCallback(this._idleCallback); + this._cancelCallback(this._idleCallback); this._idleCallback = undefined; } this._i = 0; @@ -58,11 +58,11 @@ abstract class TaskQueue implements ITaskQueue { private _start(): void { if (!this._idleCallback) { - this._idleCallback = requestIdleCallback(this._process.bind(this)); + this._idleCallback = this._requestCallback(this._process.bind(this)); } } - private _process(deadline: IdleDeadline): void { + private _process(deadline: ITaskDeadline): void { this._idleCallback = undefined; let taskDuration = 0; let longestTask = 0; From 23328a394bbb10790014d78f888d96174e1aae98 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:01:36 -0700 Subject: [PATCH 26/41] Fix trusted types in dom renderer Fixes #4156 --- src/browser/renderer/dom/DomRendererRowFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index d3eb9e8e6..b6ee7bf17 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -166,7 +166,7 @@ export class DomRendererRowFactory { if (cell.isUnderline()) { charElement.classList.add(`${UNDERLINE_CLASS}-${cell.extended.underlineStyle}`); if (charElement.textContent === ' ') { - charElement.innerHTML = ' '; + charElement.textContent = '\xa0'; // =   } if (!cell.isUnderlineColorDefault()) { if (cell.isUnderlineColorRGB()) { From 44388e5e6c206c272c9d2370b85235dee6a729bf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Sep 2022 08:55:29 -0700 Subject: [PATCH 27/41] Use microtask to flush the write buffer after input Fixes #4158 --- src/common/CoreTerminal.ts | 1 + src/common/input/WriteBuffer.ts | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index d7cb0f7eb..02a943927 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -132,6 +132,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(forwardEvent(this._bufferService.onResize, this._onResize)); this.register(forwardEvent(this.coreService.onData, this._onData)); this.register(forwardEvent(this.coreService.onBinary, this._onBinary)); + this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput())); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 2cdf4e3c5..1ac98b51e 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -40,11 +40,16 @@ export class WriteBuffer { private _bufferOffset = 0; private _isSyncWriting = false; private _syncCalls = 0; + private _didUserInput = false; public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } private _onWriteParsed = new EventEmitter(); constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } + public handleUserInput(): void { + this._didUserInput = true; + } + /** * @deprecated Unreliable, to be removed soon. */ @@ -99,7 +104,15 @@ export class WriteBuffer { // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; - queueMicrotask(() => this._innerWrite()); + // If this is the first write call after the user has done some input, + // parse it immediately in an upcoming microtask to minimize reduce input, + // otherwise schedule for the next event + if (this._didUserInput) { + this._didUserInput = false; + queueMicrotask(() => this._innerWrite()); + } else { + setTimeout(() => this._innerWrite()); + } } this._pendingData += data.length; From 34df17e36dc309171880bd996b8b37f412bbb4de Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:51:28 -0700 Subject: [PATCH 28/41] Call innerWrite immediately over a microtask --- src/common/input/WriteBuffer.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 1ac98b51e..4f316f24b 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -104,15 +104,20 @@ export class WriteBuffer { // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; + // If this is the first write call after the user has done some input, // parse it immediately in an upcoming microtask to minimize reduce input, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; - queueMicrotask(() => this._innerWrite()); - } else { - setTimeout(() => this._innerWrite()); + this._pendingData += data.length; + this._writeBuffer.push(data); + this._callbacks.push(callback); + this._innerWrite(); + return; } + + setTimeout(() => this._innerWrite()); } this._pendingData += data.length; From cd6f370c2855ff0d4ed99030a5a4494323858cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 28 Sep 2022 20:03:55 +0200 Subject: [PATCH 29/41] unbuffered delivery for userInput response only --- demo/server.js | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/demo/server.js b/demo/server.js index 8bb684a2d..c594da3e6 100644 --- a/demo/server.js +++ b/demo/server.js @@ -79,14 +79,25 @@ function startServer() { console.log('Connected to terminal ' + term.pid); ws.send(logs[term.pid]); + // unbuffered delivery after user input + let userInput = false; + // string message buffering - function buffer(socket, timeout) { + function buffer(socket, timeout, maxSize) { let s = ''; let sender = null; return (data) => { s += data; - if (!sender) { - sender = queueMicrotask(() => { + if (s.length > maxSize || userInput) { + userInput = false; + socket.send(s); + s = ''; + if (sender) { + clearTimeout(sender); + sender = null; + } + } else if (!sender) { + sender = setTimeout(() => { socket.send(s); s = ''; sender = null; @@ -95,15 +106,24 @@ function startServer() { }; } // binary message buffering - function bufferUtf8(socket, timeout) { + function bufferUtf8(socket, timeout, maxSize) { let buffer = []; let sender = null; let length = 0; return (data) => { buffer.push(data); length += data.length; - if (!sender) { - sender = queueMicrotask(() => { + if (length > maxSize || userInput) { + userInput = false; + socket.send(Buffer.concat(buffer, length)); + buffer = []; + length = 0; + if (sender) { + clearTimeout(sender); + sender = null; + } + } else if (!sender) { + sender = setTimeout(() => { socket.send(Buffer.concat(buffer, length)); buffer = []; sender = null; @@ -112,7 +132,7 @@ function startServer() { } }; } - const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5); + const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 2, 262144); // WARNING: This is a naive implementation that will not throttle the flow of data. This means // it could flood the communication channel and make the terminal unresponsive. Learn more about @@ -126,6 +146,7 @@ function startServer() { }); ws.on('message', function(msg) { term.write(msg); + userInput = true; }); ws.on('close', function () { term.kill(); From 193d305dcd37537aa47758e580059ffd220b8723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 29 Sep 2022 10:36:08 +0200 Subject: [PATCH 30/41] revert timeout to 5ms, remove log in server.js --- demo/server.js | 17 +++-------------- src/common/input/WriteBuffer.ts | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/demo/server.js b/demo/server.js index c594da3e6..0e82f9e96 100644 --- a/demo/server.js +++ b/demo/server.js @@ -16,8 +16,7 @@ function startServer() { var app = express(); expressWs(app); - var terminals = {}, - logs = {}; + var terminals = {}; app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.get('/logo.png', (req, res) => { @@ -55,10 +54,6 @@ function startServer() { console.log('Created terminal with PID: ' + term.pid); terminals[term.pid] = term; - logs[term.pid] = ''; - term.on('data', function(data) { - logs[term.pid] += data; - }); res.send(term.pid.toString()); res.end(); }); @@ -77,7 +72,6 @@ function startServer() { app.ws('/terminals/:pid', function (ws, req) { var term = terminals[parseInt(req.params.pid)]; console.log('Connected to terminal ' + term.pid); - ws.send(logs[term.pid]); // unbuffered delivery after user input let userInput = false; @@ -132,17 +126,13 @@ function startServer() { } }; } - const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 2, 262144); + const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 5, 262144); // WARNING: This is a naive implementation that will not throttle the flow of data. This means // it could flood the communication channel and make the terminal unresponsive. Learn more about // the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/ term.on('data', function(data) { - try { - send(data); - } catch (ex) { - // The WebSocket is not open, ignore - } + send(data); }); ws.on('message', function(msg) { term.write(msg); @@ -153,7 +143,6 @@ function startServer() { console.log('Closed terminal ' + term.pid); // Clean things up delete terminals[term.pid]; - delete logs[term.pid]; }); }); diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 4f316f24b..8cb1edf4b 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -106,7 +106,7 @@ export class WriteBuffer { this._bufferOffset = 0; // If this is the first write call after the user has done some input, - // parse it immediately in an upcoming microtask to minimize reduce input, + // parse it immediately to minimize reduce input, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; From 150499b7e5ee56fa4bc0985f0099ce739ff6f802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 29 Sep 2022 10:46:25 +0200 Subject: [PATCH 31/41] comment fix --- src/common/input/WriteBuffer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 8cb1edf4b..5e19105d8 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -106,7 +106,7 @@ export class WriteBuffer { this._bufferOffset = 0; // If this is the first write call after the user has done some input, - // parse it immediately to minimize reduce input, + // parse it immediately to minimize input latency, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; From 52ed65d7bda0c041d9ce29e2b53558d2a3fbf237 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:26:07 -0700 Subject: [PATCH 32/41] Move w objects to $ prefix variables Fixes #4138 --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 58 +++++++------ .../src/RectangleRenderer.ts | 50 ++++++----- addons/xterm-addon-webgl/src/WebglRenderer.ts | 84 +++++++++---------- .../src/atlas/WebglCharAtlas.ts | 16 ++-- src/common/buffer/BufferLine.ts | 14 ++-- src/common/services/DecorationService.ts | 14 ++-- 6 files changed, 112 insertions(+), 124 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index a7f4a700f..689b847a2 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -70,13 +70,11 @@ const INDICES_PER_CELL = 10; const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; const CELL_POSITION_INDICES = 2; -/** Work variables to avoid garbage collection. */ -const w: { i: number, glyph: IRasterizedGlyph | undefined, leftCellPadding: number, clippedPixels: number } = { - i: 0, - glyph: undefined, - leftCellPadding: 0, - clippedPixels: 0 -}; +// Work variables to avoid garbage collection +let $i = 0; +let $glyph: IRasterizedGlyph | undefined = undefined; +let $leftCellPadding = 0; +let $clippedPixels = 0; export class GlyphRenderer extends Disposable { private _atlas: WebglCharAtlas | undefined; @@ -186,12 +184,12 @@ export class GlyphRenderer extends Disposable { } private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { - w.i = (y * this._terminal.cols + x) * INDICES_PER_CELL; + $i = (y * this._terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null character, allow space character to continue as it may have // underline/strikethrough styles if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { - fill(array, 0, w.i, w.i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); + fill(array, 0, $i, $i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -201,39 +199,39 @@ export class GlyphRenderer extends Disposable { // Get the glyph if (chars && chars.length > 1) { - w.glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext); + $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext); } else { - w.glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext); + $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext); } - w.leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2); - if (bg !== lastBg && w.glyph.offset.x > w.leftCellPadding) { - w.clippedPixels = w.glyph.offset.x - w.leftCellPadding; + $leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2); + if (bg !== lastBg && $glyph.offset.x > $leftCellPadding) { + $clippedPixels = $glyph.offset.x - $leftCellPadding; // a_origin - array[w.i ] = -(w.glyph.offset.x - w.clippedPixels) + this._dimensions.scaledCharLeft; - array[w.i + 1] = -w.glyph.offset.y + this._dimensions.scaledCharTop; + array[$i ] = -($glyph.offset.x - $clippedPixels) + this._dimensions.scaledCharLeft; + array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop; // a_size - array[w.i + 2] = (w.glyph.size.x - w.clippedPixels) / this._dimensions.scaledCanvasWidth; - array[w.i + 3] = w.glyph.size.y / this._dimensions.scaledCanvasHeight; + array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.scaledCanvasWidth; + array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight; // a_texcoord - array[w.i + 4] = w.glyph.texturePositionClipSpace.x + w.clippedPixels / this._atlas.cacheCanvas.width; - array[w.i + 5] = w.glyph.texturePositionClipSpace.y; + array[$i + 4] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 5] = $glyph.texturePositionClipSpace.y; // a_texsize - array[w.i + 6] = w.glyph.sizeClipSpace.x - w.clippedPixels / this._atlas.cacheCanvas.width; - array[w.i + 7] = w.glyph.sizeClipSpace.y; + array[$i + 6] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 7] = $glyph.sizeClipSpace.y; } else { // a_origin - array[w.i ] = -w.glyph.offset.x + this._dimensions.scaledCharLeft; - array[w.i + 1] = -w.glyph.offset.y + this._dimensions.scaledCharTop; + array[$i ] = -$glyph.offset.x + this._dimensions.scaledCharLeft; + array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop; // a_size - array[w.i + 2] = w.glyph.size.x / this._dimensions.scaledCanvasWidth; - array[w.i + 3] = w.glyph.size.y / this._dimensions.scaledCanvasHeight; + array[$i + 2] = $glyph.size.x / this._dimensions.scaledCanvasWidth; + array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight; // a_texcoord - array[w.i + 4] = w.glyph.texturePositionClipSpace.x; - array[w.i + 5] = w.glyph.texturePositionClipSpace.y; + array[$i + 4] = $glyph.texturePositionClipSpace.x; + array[$i + 5] = $glyph.texturePositionClipSpace.y; // a_texsize - array[w.i + 6] = w.glyph.sizeClipSpace.x; - array[w.i + 7] = w.glyph.sizeClipSpace.y; + array[$i + 6] = $glyph.sizeClipSpace.x; + array[$i + 7] = $glyph.sizeClipSpace.y; } // a_cellpos only changes on resize } diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ef08fb7c2..dccc7f6a9 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -58,17 +58,15 @@ const BYTES_PER_RECTANGLE = INDICES_PER_RECTANGLE * Float32Array.BYTES_PER_ELEME const INITIAL_BUFFER_RECTANGLE_CAPACITY = 20 * INDICES_PER_RECTANGLE; -/** Work variables to avoid garbage collection. */ -const w: { rgba: number, isDefault: boolean, x1: number, y1: number, r: number, g: number, b: number, a: number } = { - rgba: 0, - isDefault: false, - x1: 0, - y1: 0, - r: 0, - g: 0, - b: 0, - a: 0 -}; +// Work variables to avoid garbage collection +let $rgba = 0; +let $isDefault = false; +let $x1 = 0; +let $y1 = 0; +let $r = 0; +let $g = 0; +let $b = 0; +let $a = 0; export class RectangleRenderer extends Disposable { @@ -232,47 +230,47 @@ export class RectangleRenderer extends Disposable { } private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { - w.isDefault = false; + $isDefault = false; if (fg & FgFlags.INVERSE) { switch (fg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: - w.rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; + $rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - w.rgba = (fg & Attributes.RGB_MASK) << 8; + $rgba = (fg & Attributes.RGB_MASK) << 8; break; case Attributes.CM_DEFAULT: default: - w.rgba = this._colors.foreground.rgba; + $rgba = this._colors.foreground.rgba; } } else { switch (bg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: - w.rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; + $rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - w.rgba = (bg & Attributes.RGB_MASK) << 8; + $rgba = (bg & Attributes.RGB_MASK) << 8; break; case Attributes.CM_DEFAULT: default: - w.rgba = this._colors.background.rgba; - w.isDefault = true; + $rgba = this._colors.background.rgba; + $isDefault = true; } } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); } - w.x1 = startX * this._dimensions.scaledCellWidth; - w.y1 = y * this._dimensions.scaledCellHeight; - w.r = ((w.rgba >> 24) & 0xFF) / 255; - w.g = ((w.rgba >> 16) & 0xFF) / 255; - w.b = ((w.rgba >> 8 ) & 0xFF) / 255; - w.a = (!w.isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1; + $x1 = startX * this._dimensions.scaledCellWidth; + $y1 = y * this._dimensions.scaledCellHeight; + $r = (($rgba >> 24) & 0xFF) / 255; + $g = (($rgba >> 16) & 0xFF) / 255; + $b = (($rgba >> 8 ) & 0xFF) / 255; + $a = (!$isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1; - this._addRectangle(vertices.attributes, offset, w.x1, w.y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, w.r, w.g, w.b, w.a); + this._addRectangle(vertices.attributes, offset, $x1, $y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, $r, $g, $b, $a); } private _addRectangle(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, r: number, g: number, b: number, a: number): void { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d8daa0d18..b276cd053 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -26,14 +26,12 @@ import { CharData, IBufferLine, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { ICoreService, IDecorationService } from 'common/services/Services'; -/** Work variables to avoid garbage collection. */ -const w: { fg: number, bg: number, hasFg: boolean, hasBg: boolean, isSelected: boolean } = { - fg: 0, - bg: 0, - hasFg: false, - hasBg: false, - isSelected: false -}; +// Work variables to avoid garbage collection +let $fg = 0; +let $bg = 0; +let $hasFg = false; +let $hasBg = false; +let $isSelected = false; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -475,89 +473,89 @@ export class WebglRenderer extends Disposable implements IRenderer { // override logic throughout the different sub-renderers // Reset overrides work variables - w.bg = 0; - w.fg = 0; - w.hasBg = false; - w.hasFg = false; - w.isSelected = false; + $bg = 0; + $fg = 0; + $hasBg = false; + $hasFg = false; + $isSelected = false; // Apply decorations on the bottom layer this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { if (d.backgroundColorRGB) { - w.bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; } if (d.foregroundColorRGB) { - w.fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } }); // Apply the selection color if needed - w.isSelected = this._isCellSelected(x, y); - if (w.isSelected) { - w.bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $isSelected = this._isCellSelected(x, y); + if ($isSelected) { + $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + $hasBg = true; if (this._colors.selectionForeground) { - w.fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } } // Apply decorations on the top layer this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { if (d.backgroundColorRGB) { - w.bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; } if (d.foregroundColorRGB) { - w.fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } }); // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag // ahead of time in order to use the correct cache key - if (w.hasBg) { - if (w.isSelected) { + if ($hasBg) { + if ($isSelected) { // Non-RGB attributes from model + force non-dim + override + force RGB color mode - w.bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | w.bg | Attributes.CM_RGB; + $bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; } else { // Non-RGB attributes from model + override + force RGB color mode - w.bg = (this._workCell.bg & ~Attributes.RGB_MASK) | w.bg | Attributes.CM_RGB; + $bg = (this._workCell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; } } - if (w.hasFg) { + if ($hasFg) { // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - w.fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | w.fg | Attributes.CM_RGB; + $fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; } // Handle case where inverse was specified by only one of bg override or fg override was set, // resolving the other inverse color and setting the inverse flag if needed. if (this._workColors.fg & FgFlags.INVERSE) { - if (w.hasBg && !w.hasFg) { + if ($hasBg && !$hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - w.fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { - w.fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); + $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); } - w.hasFg = true; + $hasFg = true; } - if (!w.hasBg && w.hasFg) { + if (!$hasBg && $hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - w.bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { - w.bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); + $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); } - w.hasBg = true; + $hasBg = true; } } // Use the override if it exists - this._workColors.bg = w.hasBg ? w.bg : this._workColors.bg; - this._workColors.fg = w.hasFg ? w.fg : this._workColors.fg; + this._workColors.bg = $hasBg ? $bg : this._workColors.bg; + this._workColors.fg = $hasFg ? $fg : this._workColors.fg; } private _isCellSelected(x: number, y: number): boolean { diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 4764ede4e..dee1e02c5 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -54,10 +54,8 @@ interface ICharAtlasActiveRow { height: number; } -/** Work variables to avoid garbage collection. */ -const w: { glyph: IRasterizedGlyph | undefined } = { - glyph: undefined -}; +// Work variables to avoid garbage collection +let $glyph = undefined; export class WebglCharAtlas implements IDisposable { private _didWarmUp: boolean = false; @@ -184,12 +182,12 @@ export class WebglCharAtlas implements IDisposable { fg: number, ext: number ): IRasterizedGlyph { - w.glyph = cacheMap.get(key, bg, fg, ext); - if (!w.glyph) { - w.glyph = this._drawToCache(key, bg, fg, ext); - cacheMap.set(key, bg, fg, ext, w.glyph); + $glyph = cacheMap.get(key, bg, fg, ext); + if (!$glyph) { + $glyph = this._drawToCache(key, bg, fg, ext); + cacheMap.set(key, bg, fg, ext, $glyph); } - return w.glyph; + return $glyph; } private _getColorFromAnsiIndex(idx: number): IColor { diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 43e898392..875ac6c97 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -37,10 +37,8 @@ const enum Cell { export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData()); -/** Work variables to avoid garbage collection. */ -const w: { startIndex: number } = { - startIndex: 0 -}; +// Work variables to avoid garbage collection +let $startIndex = 0; /** * Typed array based bufferline implementation. @@ -178,10 +176,10 @@ export class BufferLine implements IBufferLine { * to GC as it significantly reduced the amount of new objects/references needed. */ public loadCell(index: number, cell: ICellData): ICellData { - w.startIndex = index * CELL_SIZE; - cell.content = this._data[w.startIndex + Cell.CONTENT]; - cell.fg = this._data[w.startIndex + Cell.FG]; - cell.bg = this._data[w.startIndex + Cell.BG]; + $startIndex = index * CELL_SIZE; + cell.content = this._data[$startIndex + Cell.CONTENT]; + cell.fg = this._data[$startIndex + Cell.FG]; + cell.bg = this._data[$startIndex + Cell.BG]; if (cell.content & Content.IS_COMBINED_MASK) { cell.combinedData = this._combined[index]; } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index e5d115a14..dae801518 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -11,11 +11,9 @@ import { SortedList } from 'common/SortedList'; import { IColor } from 'common/Types'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; -/** Work variables to avoid garbage collection. */ -const w = { - xmin: 0, - xmax: 0 -}; +// Work variables to avoid garbage collection +let $xmin = 0; +let $xmax = 0; export class DecorationService extends Disposable implements IDecorationService { public serviceBrand: any; @@ -76,9 +74,9 @@ export class DecorationService extends Disposable implements IDecorationService public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { this._decorations.forEachByKey(line, d => { - w.xmin = d.options.x ?? 0; - w.xmax = w.xmin + (d.options.width ?? 1); - if (x >= w.xmin && x < w.xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { + $xmin = d.options.x ?? 0; + $xmax = $xmin + (d.options.width ?? 1); + if (x >= $xmin && x < $xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { callback(d); } }); From 3a90f7e7c0a508aeed422173df89d6b9366be341 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:39:02 -0700 Subject: [PATCH 33/41] Inline dirty row service into input handler Fixes #4147 --- src/common/CoreTerminal.ts | 12 +-- src/common/InputHandler.test.ts | 18 ++-- src/common/InputHandler.ts | 123 ++++++++++++++++++------- src/common/TestUtils.test.ts | 12 +-- src/common/services/DirtyRowService.ts | 53 ----------- src/common/services/Services.ts | 13 --- 6 files changed, 102 insertions(+), 129 deletions(-) delete mode 100644 src/common/services/DirtyRowService.ts diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 02a943927..fae3efa58 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -22,7 +22,7 @@ */ import { Disposable } from 'common/Lifecycle'; -import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; @@ -31,7 +31,6 @@ import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent, ScrollSource import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; -import { DirtyRowService } from 'common/services/DirtyRowService'; import { UnicodeService } from 'common/services/UnicodeService'; import { CharsetService } from 'common/services/CharsetService'; import { updateWindowsModeWrappedState } from 'common/WindowsMode'; @@ -49,7 +48,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected readonly _bufferService: IBufferService; protected readonly _logService: ILogService; protected readonly _charsetService: ICharsetService; - protected readonly _dirtyRowService: IDirtyRowService; protected readonly _oscLinkService: IOscLinkService; public readonly coreMouseService: ICoreMouseService; @@ -114,8 +112,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._instantiationService.setService(ICoreService, this.coreService); this.coreMouseService = this._instantiationService.createInstance(CoreMouseService); this._instantiationService.setService(ICoreMouseService, this.coreMouseService); - this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); - this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); this.unicodeService = this._instantiationService.createInstance(UnicodeService); this._instantiationService.setService(IUnicodeService, this.unicodeService); this._charsetService = this._instantiationService.createInstance(CharsetService); @@ -124,7 +120,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._instantiationService.setService(IOscLinkService, this._oscLinkService); // Register input handler and handle/forward events - this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._dirtyRowService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); this.register(this._inputHandler); @@ -136,11 +132,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); - this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); })); this.register(this._inputHandler.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); - this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); })); // Setup WriteBuffer diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index f734b002c..d9127e7b2 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -11,7 +11,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test'; +import { MockCoreService, MockBufferService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test'; import { IBufferService, ICoreService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; @@ -67,7 +67,7 @@ describe('InputHandler', () => { bufferService.resize(80, 30); coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService); - inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); describe('SL/SR/DECIC/DECDC', () => { @@ -236,7 +236,7 @@ describe('InputHandler', () => { describe('setMode', () => { it('should toggle bracketedPasteMode', () => { const coreService = new MockCoreService(); - const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(coreService.decPrivateModes.bracketedPasteMode, true); @@ -258,7 +258,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -305,7 +304,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -356,7 +354,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -394,7 +391,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -445,7 +441,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -572,7 +567,6 @@ describe('InputHandler', () => { new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -599,7 +593,7 @@ describe('InputHandler', () => { beforeEach(() => { bufferService = new MockBufferService(80, 30); - handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', async () => { await handler.parseP('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); @@ -796,7 +790,7 @@ describe('InputHandler', () => { describe('colon notation', () => { let inputHandler2: TestInputHandler; beforeEach(() => { - inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); describe('should equal to semicolon', () => { it('CSI 38:2::50:100:150 m', async () => { @@ -2278,7 +2272,7 @@ describe('InputHandler - async handlers', () => { coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService); coreService.onData(data => { console.log(data); }); - inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); it('async CUP with CPR check', async () => { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index b599bb7e4..dd61cf347 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -12,11 +12,11 @@ import { Disposable } from 'common/Lifecycle'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; +import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; @@ -104,6 +104,8 @@ export enum WindowsOptionsReportType { // create a warning log if an async handler takes longer than the limit (in ms) const SLOW_ASYNC_LIMIT = 5000; +// Work variables to avoid garbage collection +let $temp = 0; /** * The terminal's standard implementation of IInputHandler, this handles all @@ -120,6 +122,7 @@ export class InputHandler extends Disposable implements IInputHandler { private _windowTitle = ''; private _iconName = ''; private _currentLinkId?: number; + private _dirtyRowTracker: IDirtyRowTracker; protected _windowTitleStack: string[] = []; protected _iconNameStack: string[] = []; @@ -169,7 +172,6 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _bufferService: IBufferService, private readonly _charsetService: ICharsetService, private readonly _coreService: ICoreService, - private readonly _dirtyRowService: IDirtyRowService, private readonly _logService: ILogService, private readonly _optionsService: IOptionsService, private readonly _oscLinkService: IOscLinkService, @@ -179,6 +181,7 @@ export class InputHandler extends Disposable implements IInputHandler { ) { super(); this.register(this._parser); + this._dirtyRowTracker = new DirtyRowTracker(this._bufferService); // Track properties used in performance critical code manually to avoid using slow getters this._activeBuffer = this._bufferService.buffer; @@ -459,7 +462,7 @@ export class InputHandler extends Disposable implements IInputHandler { // Clear the dirty row service so we know which lines changed as a result of parsing // Important: do not clear between async calls, otherwise we lost pending update information. if (!wasPaused) { - this._dirtyRowService.clearRange(); + this._dirtyRowTracker.clearRange(); } // process big data in smaller chunks @@ -493,7 +496,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // Refresh any dirty rows accumulated as part of parsing - this._onRequestRefreshRows.fire(this._dirtyRowService.start, this._dirtyRowService.end); + this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); } public print(data: Uint32Array, start: number, end: number): void { @@ -507,7 +510,7 @@ export class InputHandler extends Disposable implements IInputHandler { const curAttr = this._curAttrData; let bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!; - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) { @@ -635,7 +638,7 @@ export class InputHandler extends Disposable implements IInputHandler { bufferRow.setCellFromCodePoint(this._activeBuffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended); } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** @@ -699,7 +702,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF." */ public lineFeed(): boolean { - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); if (this._optionsService.rawOptions.convertEol) { this._activeBuffer.x = 0; } @@ -714,7 +717,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._activeBuffer.x >= this._bufferService.cols) { this._activeBuffer.x--; } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); this._onLineFeed.fire(); return true; @@ -842,14 +845,14 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.y = this._coreService.decPrivateModes.origin ? Math.min(this._activeBuffer.scrollBottom, Math.max(this._activeBuffer.scrollTop, this._activeBuffer.y)) : Math.min(this._bufferService.rows - 1, Math.max(0, this._activeBuffer.y)); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** * Set absolute cursor position. */ private _setCursor(x: number, y: number): void { - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); if (this._coreService.decPrivateModes.origin) { this._activeBuffer.x = x; this._activeBuffer.y = this._activeBuffer.scrollTop + y; @@ -858,7 +861,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.y = y; } this._restrictCursor(); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** @@ -1178,16 +1181,16 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 0: j = this._activeBuffer.y; - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect); for (; j < this._bufferService.rows; j++) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); break; case 1: j = this._activeBuffer.y; - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); // Deleted front part of line and everything before. This line will no longer be wrapped. this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true, respectProtect); if (this._activeBuffer.x + 1 >= this._bufferService.cols) { @@ -1197,15 +1200,15 @@ export class InputHandler extends Disposable implements IInputHandler { while (j--) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(0); + this._dirtyRowTracker.markDirty(0); break; case 2: j = this._bufferService.rows; - this._dirtyRowService.markDirty(j - 1); + this._dirtyRowTracker.markDirty(j - 1); while (j--) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(0); + this._dirtyRowTracker.markDirty(0); break; case 3: // Clear scrollback (everything not in viewport) @@ -1257,7 +1260,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true, respectProtect); break; } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); return true; } @@ -1289,7 +1292,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(row, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? return true; } @@ -1323,7 +1326,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(j, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? return true; } @@ -1349,7 +1352,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -1375,7 +1378,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -1395,7 +1398,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 1); this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1411,7 +1414,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 1); this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 0, this._activeBuffer.getBlankLine(DEFAULT_ATTR_DATA)); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1443,7 +1446,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1476,7 +1479,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1499,7 +1502,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1522,7 +1525,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1544,7 +1547,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -3220,7 +3223,7 @@ export class InputHandler extends Disposable implements IInputHandler { const scrollRegionHeight = this._activeBuffer.scrollBottom - this._activeBuffer.scrollTop; this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase + this._activeBuffer.y, scrollRegionHeight, 1); this._activeBuffer.lines.set(this._activeBuffer.ybase + this._activeBuffer.y, this._activeBuffer.getBlankLine(this._eraseAttrData())); - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); } else { this._activeBuffer.y--; this._restrictCursor(); // quickfix to not run out of bounds @@ -3293,7 +3296,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } } - this._dirtyRowService.markAllDirty(); + this._dirtyRowTracker.markAllDirty(); this._setCursor(0, 0); return true; } @@ -3344,4 +3347,60 @@ export class InputHandler extends Disposable implements IInputHandler { if (data === ' q') return f(`P1$r${STYLES[opts.cursorStyle] - (opts.cursorBlink ? 1 : 0)} q`); return f(`P0$r`); } + + public markRangeDirty(y1: number, y2: number): void { + this._dirtyRowTracker.markRangeDirty(y1, y2); + } +} + +export interface IDirtyRowTracker { + readonly start: number; + readonly end: number; + + clearRange(): void; + markDirty(y: number): void; + markRangeDirty(y1: number, y2: number): void; + markAllDirty(): void; +} + +class DirtyRowTracker implements IDirtyRowTracker { + public start!: number; + public end!: number; + + constructor( + @IBufferService private readonly _bufferService: IBufferService + ) { + this.clearRange(); + } + + public clearRange(): void { + this.start = this._bufferService.buffer.y; + this.end = this._bufferService.buffer.y; + } + + public markDirty(y: number): void { + if (y < this.start) { + this.start = y; + } else if (y > this.end) { + this.end = y; + } + } + + public markRangeDirty(y1: number, y2: number): void { + if (y1 > y2) { + $temp = y1; + y1 = y2; + y2 = $temp; + } + if (y1 < this.start) { + this.start = y1; + } + if (y2 > this.end) { + this.end = y2; + } + } + + public markAllDirty(): void { + this.markRangeDirty(0, this._bufferService.rows - 1); + } } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 8fb71a5d5..e302d3e4b 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -100,16 +100,6 @@ export class MockCoreService implements ICoreService { public triggerBinaryEvent(data: string): void { } } -export class MockDirtyRowService implements IDirtyRowService { - public serviceBrand: any; - public start: number = 0; - public end: number = 0; - public clearRange(): void { } - public markDirty(y: number): void { } - public markRangeDirty(y1: number, y2: number): void { } - public markAllDirty(): void { } -} - export class MockLogService implements ILogService { public serviceBrand: any; public logLevel = LogLevelEnum.DEBUG; diff --git a/src/common/services/DirtyRowService.ts b/src/common/services/DirtyRowService.ts deleted file mode 100644 index 1c43b67ef..000000000 --- a/src/common/services/DirtyRowService.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IBufferService, IDirtyRowService } from 'common/services/Services'; - -export class DirtyRowService implements IDirtyRowService { - public serviceBrand: any; - - private _start!: number; - private _end!: number; - - public get start(): number { return this._start; } - public get end(): number { return this._end; } - - constructor( - @IBufferService private readonly _bufferService: IBufferService - ) { - this.clearRange(); - } - - public clearRange(): void { - this._start = this._bufferService.buffer.y; - this._end = this._bufferService.buffer.y; - } - - public markDirty(y: number): void { - if (y < this._start) { - this._start = y; - } else if (y > this._end) { - this._end = y; - } - } - - public markRangeDirty(y1: number, y2: number): void { - if (y1 > y2) { - const temp = y1; - y1 = y2; - y2 = temp; - } - if (y1 < this._start) { - this._start = y1; - } - if (y2 > this._end) { - this._end = y2; - } - } - - public markAllDirty(): void { - this.markRangeDirty(0, this._bufferService.rows - 1); - } -} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 22edad1dd..e2b517cde 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -122,19 +122,6 @@ export interface ICharsetService { setgCharset(g: number, charset: ICharset | undefined): void; } -export const IDirtyRowService = createDecorator('DirtyRowService'); -export interface IDirtyRowService { - serviceBrand: undefined; - - readonly start: number; - readonly end: number; - - clearRange(): void; - markDirty(y: number): void; - markRangeDirty(y1: number, y2: number): void; - markAllDirty(): void; -} - export interface IServiceIdentifier { (...args: any[]): void; type: T; From cf9949840b65059aae90589862809318e1b95959 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:52:26 -0700 Subject: [PATCH 34/41] Use readonly over getter for exposing events Fixes #4164 --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 2 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 7 +++-- .../typings/xterm-addon-webgl.d.ts | 4 +-- src/browser/Linkifier2.ts | 4 +-- src/browser/Terminal.ts | 20 +++++++------- src/browser/renderer/dom/DomRenderer.ts | 2 +- src/browser/services/CharSizeService.ts | 2 +- src/browser/services/RenderService.ts | 8 +++--- src/browser/services/SelectionService.ts | 8 +++--- src/common/CircularList.ts | 6 ++--- src/common/CoreTerminal.ts | 13 +++++----- src/common/InputHandler.ts | 26 +++++++++---------- src/common/buffer/BufferSet.ts | 2 +- src/common/buffer/Marker.ts | 2 +- src/common/input/WriteBuffer.ts | 3 ++- src/common/public/BufferNamespaceApi.ts | 3 ++- src/common/services/BufferService.ts | 4 +-- src/common/services/CoreMouseService.ts | 11 +++----- src/common/services/CoreService.ts | 6 ++--- src/common/services/DecorationService.ts | 6 ++--- src/common/services/OptionsService.ts | 2 +- src/common/services/UnicodeService.ts | 3 ++- src/headless/Terminal.ts | 11 ++++---- 24 files changed, 78 insertions(+), 81 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index b642efbca..68475829d 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -28,7 +28,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; private _onRequestRedraw = new EventEmitter(); - public get onRequestRedraw(): IEvent { return this._onRequestRedraw.event; } + public readonly onRequestRedraw = this._onRequestRedraw.event; constructor( private _colors: IColorSet, diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 5b98a048d..a96e7c64c 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -16,9 +16,9 @@ export class WebglAddon implements ITerminalAddon { private _renderer?: WebglRenderer; private _onChangeTextureAtlas = new EventEmitter(); - public get onChangeTextureAtlas(): IEvent { return this._onChangeTextureAtlas.event; } + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; private _onContextLoss = new EventEmitter(); - public get onContextLoss(): IEvent { return this._onContextLoss.event; } + public readonly onContextLoss = this._onContextLoss.event; constructor( private _preserveDrawingBuffer?: boolean diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d8daa0d18..74488c6c8 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -56,12 +56,11 @@ export class WebglRenderer extends Disposable implements IRenderer { private _contextRestorationTimeout: number | undefined; private _onChangeTextureAtlas = new EventEmitter(); - public get onChangeTextureAtlas(): IEvent { return this._onChangeTextureAtlas.event; } + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; private _onRequestRedraw = new EventEmitter(); - public get onRequestRedraw(): IEvent { return this._onRequestRedraw.event; } - + public readonly onRequestRedraw = this._onRequestRedraw.event; private _onContextLoss = new EventEmitter(); - public get onContextLoss(): IEvent { return this._onContextLoss.event; } + public readonly onContextLoss = this._onContextLoss.event; constructor( private _terminal: Terminal, diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts index 74aed0cc7..6865b6dbe 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -15,12 +15,12 @@ declare module 'xterm-addon-webgl' { /** * An event that is fired when the renderer loses its canvas context. */ - public get onContextLoss(): IEvent; + public readonly onContextLoss: IEvent; /** * An event that is fired when the texture atlas of the renderer changes. */ - public get onChangeTextureAtlas(): IEvent; + public readonly onChangeTextureAtlas: IEvent; constructor(preserveDrawingBuffer?: boolean); diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 9c978949b..6bf9fe237 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -27,9 +27,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeLine: number = -1; private _onShowLinkUnderline = this.register(new EventEmitter()); - public get onShowLinkUnderline(): IEvent { return this._onShowLinkUnderline.event; } + public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; private _onHideLinkUnderline = this.register(new EventEmitter()); - public get onHideLinkUnderline(): IEvent { return this._onHideLinkUnderline.event; } + public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; constructor( @IBufferService private readonly _bufferService: IBufferService diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index afdac748c..e03be89c3 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -123,26 +123,26 @@ export class Terminal extends CoreTerminal implements ITerminal { private _theme: ITheme | undefined; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); - public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } + public readonly onKey = this._onKey.event; private _onRender = new EventEmitter<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + public readonly onRender = this._onRender.event; private _onSelectionChange = new EventEmitter(); - public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + public readonly onSelectionChange = this._onSelectionChange.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } + public readonly onTitleChange = this._onTitleChange.event; private _onBell = new EventEmitter(); - public get onBell(): IEvent { return this._onBell.event; } + public readonly onBell = this._onBell.event; private _onFocus = new EventEmitter(); - public get onFocus(): IEvent { return this._onFocus.event; } + public readonly onFocus = this._onFocus.event; private _onBlur = new EventEmitter(); - public get onBlur(): IEvent { return this._onBlur.event; } + public readonly onBlur = this._onBlur.event; private _onA11yCharEmitter = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } + public readonly onA11yChar = this._onA11yCharEmitter.event; private _onA11yTabEmitter = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } + public readonly onA11yTab = this._onA11yTabEmitter.event; /** * Creates a new `Terminal` object. diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 8df6b3028..223e24705 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - public get onRequestRedraw(): IEvent { return new EventEmitter().event; } + public readonly onRequestRedraw = new EventEmitter().event; constructor( private _colors: IColorSet, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index b04e157f6..d5cc37c86 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -17,7 +17,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } private _onCharSizeChange = new EventEmitter(); - public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; } + public readonly onCharSizeChange = this._onCharSizeChange.event; constructor( document: Document, diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 972586098..692134297 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -40,13 +40,13 @@ export class RenderService extends Disposable implements IRenderService { }; private _onDimensionsChange = new EventEmitter(); - public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } + public readonly onDimensionsChange = this._onDimensionsChange.event; private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); - public get onRenderedViewportChange(): IEvent<{ start: number, end: number }> { return this._onRenderedViewportChange.event; } + public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; private _onRender = new EventEmitter<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + public readonly onRender = this._onRender.event; private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); - public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; } + public readonly onRefreshRequest = this._onRefreshRequest.event; public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 4ee1ffa1f..29c8e664b 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -112,13 +112,13 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionEnd: [number, number] | undefined = undefined; private _onLinuxMouseSelection = this.register(new EventEmitter()); - public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } + public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; private _onRedrawRequest = this.register(new EventEmitter()); - public get onRequestRedraw(): IEvent { return this._onRedrawRequest.event; } + public readonly onRequestRedraw = this._onRedrawRequest.event; private _onSelectionChange = this.register(new EventEmitter()); - public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + public readonly onSelectionChange = this._onSelectionChange.event; private _onRequestScrollLines = this.register(new EventEmitter()); - public get onRequestScrollLines(): IEvent { return this._onRequestScrollLines.event; } + public readonly onRequestScrollLines = this._onRequestScrollLines.event; constructor( private readonly _element: HTMLElement, diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 4d2c04eca..c0c6bb5b0 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -26,11 +26,11 @@ export class CircularList implements ICircularList { private _length: number; public onDeleteEmitter = new EventEmitter(); - public get onDelete(): IEvent { return this.onDeleteEmitter.event; } + public readonly onDelete = this.onDeleteEmitter.event; public onInsertEmitter = new EventEmitter(); - public get onInsert(): IEvent { return this.onInsertEmitter.event; } + public readonly onInsert = this.onInsertEmitter.event; public onTrimEmitter = new EventEmitter(); - public get onTrim(): IEvent { return this.onTrimEmitter.event; } + public readonly onTrim = this.onTrimEmitter.event; constructor( private _maxLength: number diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 02a943927..da70f84a3 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -62,21 +62,22 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _windowsMode: IDisposable | undefined; private _onBinary = new EventEmitter(); - public get onBinary(): IEvent { return this._onBinary.event; } + public readonly onBinary = this._onBinary.event; private _onData = new EventEmitter(); - public get onData(): IEvent { return this._onData.event; } + public readonly onData = this._onData.event; protected _onLineFeed = new EventEmitter(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } + public readonly onLineFeed = this._onLineFeed.event; private _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } - protected _onScroll = new EventEmitter(); - public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } + public readonly onResize = this._onResize.event; protected _onWriteParsed = new EventEmitter(); + public readonly onWriteParsed = this._onWriteParsed.event; + /** * Internally we track the source of the scroll but this is meaningless outside the library so * it's filtered out. */ protected _onScrollApi?: EventEmitter; + protected _onScroll = new EventEmitter(); public get onScroll(): IEvent { if (!this._onScrollApi) { this._onScrollApi = new EventEmitter(); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index b599bb7e4..bcdb20654 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -130,32 +130,32 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; private _onRequestBell = new EventEmitter(); - public get onRequestBell(): IEvent { return this._onRequestBell.event; } + public readonly onRequestBell = this._onRequestBell.event; private _onRequestRefreshRows = new EventEmitter(); - public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } + public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; private _onRequestReset = new EventEmitter(); - public get onRequestReset(): IEvent { return this._onRequestReset.event; } + public readonly onRequestReset = this._onRequestReset.event; private _onRequestSendFocus = new EventEmitter(); - public get onRequestSendFocus(): IEvent { return this._onRequestSendFocus.event; } + public readonly onRequestSendFocus = this._onRequestSendFocus.event; private _onRequestSyncScrollBar = new EventEmitter(); - public get onRequestSyncScrollBar(): IEvent { return this._onRequestSyncScrollBar.event; } + public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; private _onRequestWindowsOptionsReport = new EventEmitter(); - public get onRequestWindowsOptionsReport(): IEvent { return this._onRequestWindowsOptionsReport.event; } + public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; private _onA11yChar = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yChar.event; } + public readonly onA11yChar = this._onA11yChar.event; private _onA11yTab = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTab.event; } + public readonly onA11yTab = this._onA11yTab.event; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onLineFeed = new EventEmitter(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } + public readonly onLineFeed = this._onLineFeed.event; private _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } + public readonly onScroll = this._onScroll.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } + public readonly onTitleChange = this._onTitleChange.event; private _onColor = new EventEmitter(); - public get onColor(): IEvent { return this._onColor.event; } + public readonly onColor = this._onColor.event; private _parseStack: IParseStack = { paused: false, diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index f940bb8f3..b2223b032 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -20,7 +20,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _activeBuffer!: Buffer; private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); - public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; } + public readonly onBufferActivate = this._onBufferActivate.event; /** * Create a new BufferSet for the given terminal. diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 72c4085ca..9737b3894 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -16,7 +16,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } + public readonly onDispose = this._onDispose.event; constructor( public line: number diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 4f316f24b..9f816d6c4 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -41,8 +41,9 @@ export class WriteBuffer { private _isSyncWriting = false; private _syncCalls = 0; private _didUserInput = false; - public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } + private _onWriteParsed = new EventEmitter(); + public readonly onWriteParsed = this._onWriteParsed.event; constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index d86f6bf5a..f331a0b76 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -11,8 +11,9 @@ import { ICoreTerminal } from 'common/Types'; export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; + private _onBufferChange = new EventEmitter(); - public get onBufferChange(): IEvent { return this._onBufferChange.event; } + public readonly onBufferChange = this._onBufferChange.event; constructor(private _core: ICoreTerminal) { this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index e3b7dcd87..49f311470 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -23,9 +23,9 @@ export class BufferService extends Disposable implements IBufferService { public isUserScrolling: boolean = false; private _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + public readonly onResize = this._onResize.event; private _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } + public readonly onScroll = this._onScroll.event; public get buffer(): IBuffer { return this.buffers.active; } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 54e991f8c..465a4968b 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -170,9 +170,11 @@ export class CoreMouseService implements ICoreMouseService { private _encodings: { [name: string]: CoreMouseEncoding } = {}; private _activeProtocol: string = ''; private _activeEncoding: string = ''; - private _onProtocolChange = new EventEmitter(); private _lastEvent: ICoreMouseEvent | null = null; + private _onProtocolChange = new EventEmitter(); + public readonly onProtocolChange = this._onProtocolChange.event; + constructor( @IBufferService private readonly _bufferService: IBufferService, @ICoreService private readonly _coreService: ICoreService @@ -225,13 +227,6 @@ export class CoreMouseService implements ICoreMouseService { this._lastEvent = null; } - /** - * Event to announce changes in mouse tracking. - */ - public get onProtocolChange(): IEvent { - return this._onProtocolChange.event; - } - /** * Triggers a mouse event to be sent. * diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 20a346031..a144eb1d3 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -35,11 +35,11 @@ export class CoreService extends Disposable implements ICoreService { private _scrollToBottom: (() => void) | undefined; private _onData = this.register(new EventEmitter()); - public get onData(): IEvent { return this._onData.event; } + public readonly onData = this._onData.event; private _onUserInput = this.register(new EventEmitter()); - public get onUserInput(): IEvent { return this._onUserInput.event; } + public readonly onUserInput = this._onUserInput.event; private _onBinary = this.register(new EventEmitter()); - public get onBinary(): IEvent { return this._onBinary.event; } + public readonly onBinary = this._onBinary.event; constructor( // TODO: Move this into a service diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index e5d115a14..d0f9b9919 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -28,9 +28,9 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); private _onDecorationRegistered = this.register(new EventEmitter()); - public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } + public readonly onDecorationRegistered = this._onDecorationRegistered.event; private _onDecorationRemoved = this.register(new EventEmitter()); - public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } + public readonly onDecorationRemoved = this._onDecorationRemoved.event; public get decorations(): IterableIterator { return this._decorations.values(); } @@ -99,7 +99,7 @@ class Decoration extends Disposable implements IInternalDecoration { public readonly onRenderEmitter = this.register(new EventEmitter()); public readonly onRender = this.onRenderEmitter.event; - private _onDispose = this.register(new EventEmitter()); + private readonly _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; private _cachedBg: IColor | undefined | null = null; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index c7e8d294f..d6662c315 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -58,7 +58,7 @@ export class OptionsService implements IOptionsService { public options: Required; private _onOptionChange = new EventEmitter(); - public get onOptionChange(): IEvent { return this._onOptionChange.event; } + public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { // set the default value of each option diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index e96b75793..b5d19623b 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -13,8 +13,9 @@ export class UnicodeService implements IUnicodeService { private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null); private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; + private _onChange = new EventEmitter(); - public get onChange(): IEvent { return this._onChange.event; } + public readonly onChange = this._onChange.event; constructor() { const defaultProvider = new UnicodeV6(); diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 1cad0ee2d..1b1162e5e 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -33,16 +33,15 @@ export class Terminal extends CoreTerminal { public get options(): Required { return this.optionsService.options; } private _onBell = new EventEmitter(); - public get onBell(): IEvent { return this._onBell.event; } + public readonly onBell = this._onBell.event; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } - + public readonly onTitleChange = this._onTitleChange.event; private _onA11yCharEmitter = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } + public readonly onA11yChar = this._onA11yCharEmitter.event; private _onA11yTabEmitter = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } + public readonly onA11yTab = this._onA11yTabEmitter.event; /** * Creates a new `Terminal` object. From 1a22c44f2b7ca10d23755c86b78d3361fa2570be Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:02:32 -0700 Subject: [PATCH 35/41] Make all emitters readonly --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 2 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 6 ++--- src/browser/Linkifier2.ts | 4 +-- src/browser/Terminal.ts | 20 +++++++------- src/browser/services/CharSizeService.ts | 2 +- src/browser/services/RenderService.ts | 8 +++--- src/browser/services/SelectionService.ts | 8 +++--- src/common/CircularList.ts | 6 ++--- src/common/CoreTerminal.ts | 8 +++--- src/common/InputHandler.ts | 26 +++++++++---------- src/common/buffer/BufferSet.ts | 2 +- src/common/buffer/Marker.ts | 2 +- src/common/input/WriteBuffer.ts | 2 +- src/common/public/BufferNamespaceApi.ts | 2 +- src/common/services/BufferService.ts | 6 ++--- src/common/services/CoreMouseService.ts | 2 +- src/common/services/CoreService.ts | 6 ++--- src/common/services/DecorationService.ts | 4 +-- src/common/services/OptionsService.ts | 2 +- src/common/services/UnicodeService.ts | 2 +- src/headless/Terminal.ts | 10 +++---- 22 files changed, 67 insertions(+), 67 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 68475829d..fd9629c06 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -27,7 +27,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - private _onRequestRedraw = new EventEmitter(); + private readonly _onRequestRedraw = new EventEmitter(); public readonly onRequestRedraw = this._onRequestRedraw.event; constructor( diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index a96e7c64c..45858e8c8 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -15,9 +15,9 @@ export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = new EventEmitter(); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private _onContextLoss = new EventEmitter(); + private readonly _onContextLoss = new EventEmitter(); public readonly onContextLoss = this._onContextLoss.event; constructor( diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 74488c6c8..0fc634a79 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -55,11 +55,11 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - private _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = new EventEmitter(); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private _onRequestRedraw = new EventEmitter(); + private readonly _onRequestRedraw = new EventEmitter(); public readonly onRequestRedraw = this._onRequestRedraw.event; - private _onContextLoss = new EventEmitter(); + private readonly _onContextLoss = new EventEmitter(); public readonly onContextLoss = this._onContextLoss.event; constructor( diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 6bf9fe237..cf5b9dd2e 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -26,9 +26,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeProviderReplies: Map | undefined; private _activeLine: number = -1; - private _onShowLinkUnderline = this.register(new EventEmitter()); + private readonly _onShowLinkUnderline = this.register(new EventEmitter()); public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; - private _onHideLinkUnderline = this.register(new EventEmitter()); + private readonly _onHideLinkUnderline = this.register(new EventEmitter()); public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; constructor( diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index e03be89c3..9c10fc16e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -122,26 +122,26 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); + private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); public readonly onKey = this._onKey.event; - private _onRender = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); public readonly onRender = this._onRender.event; - private _onSelectionChange = new EventEmitter(); + private readonly _onSelectionChange = new EventEmitter(); public readonly onSelectionChange = this._onSelectionChange.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onBell = new EventEmitter(); + private readonly _onBell = new EventEmitter(); public readonly onBell = this._onBell.event; - private _onFocus = new EventEmitter(); + private readonly _onFocus = new EventEmitter(); public readonly onFocus = this._onFocus.event; - private _onBlur = new EventEmitter(); + private readonly _onBlur = new EventEmitter(); public readonly onBlur = this._onBlur.event; - private _onA11yCharEmitter = new EventEmitter(); + private readonly _onA11yCharEmitter = new EventEmitter(); public readonly onA11yChar = this._onA11yCharEmitter.event; - private _onA11yTabEmitter = new EventEmitter(); + private readonly _onA11yTabEmitter = new EventEmitter(); public readonly onA11yTab = this._onA11yTabEmitter.event; /** diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index d5cc37c86..7062deece 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -16,7 +16,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - private _onCharSizeChange = new EventEmitter(); + private readonly _onCharSizeChange = new EventEmitter(); public readonly onCharSizeChange = this._onCharSizeChange.event; constructor( diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 692134297..66dbb7aa2 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -39,13 +39,13 @@ export class RenderService extends Disposable implements IRenderService { columnSelectMode: false }; - private _onDimensionsChange = new EventEmitter(); + private readonly _onDimensionsChange = new EventEmitter(); public readonly onDimensionsChange = this._onDimensionsChange.event; - private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; - private _onRender = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); public readonly onRender = this._onRender.event; - private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); public readonly onRefreshRequest = this._onRefreshRequest.event; public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 29c8e664b..3780c7ee8 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -111,13 +111,13 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionStart: [number, number] | undefined = undefined; private _oldSelectionEnd: [number, number] | undefined = undefined; - private _onLinuxMouseSelection = this.register(new EventEmitter()); + private readonly _onLinuxMouseSelection = this.register(new EventEmitter()); public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; - private _onRedrawRequest = this.register(new EventEmitter()); + private readonly _onRedrawRequest = this.register(new EventEmitter()); public readonly onRequestRedraw = this._onRedrawRequest.event; - private _onSelectionChange = this.register(new EventEmitter()); + private readonly _onSelectionChange = this.register(new EventEmitter()); public readonly onSelectionChange = this._onSelectionChange.event; - private _onRequestScrollLines = this.register(new EventEmitter()); + private readonly _onRequestScrollLines = this.register(new EventEmitter()); public readonly onRequestScrollLines = this._onRequestScrollLines.event; constructor( diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index c0c6bb5b0..599db1aba 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -25,11 +25,11 @@ export class CircularList implements ICircularList { private _startIndex: number; private _length: number; - public onDeleteEmitter = new EventEmitter(); + public readonly onDeleteEmitter = new EventEmitter(); public readonly onDelete = this.onDeleteEmitter.event; - public onInsertEmitter = new EventEmitter(); + public readonly onInsertEmitter = new EventEmitter(); public readonly onInsert = this.onInsertEmitter.event; - public onTrimEmitter = new EventEmitter(); + public readonly onTrimEmitter = new EventEmitter(); public readonly onTrim = this.onTrimEmitter.event; constructor( diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index da70f84a3..3a17c567a 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -61,15 +61,15 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - private _onBinary = new EventEmitter(); + private readonly _onBinary = new EventEmitter(); public readonly onBinary = this._onBinary.event; - private _onData = new EventEmitter(); + private readonly _onData = new EventEmitter(); public readonly onData = this._onData.event; protected _onLineFeed = new EventEmitter(); public readonly onLineFeed = this._onLineFeed.event; - private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); public readonly onResize = this._onResize.event; - protected _onWriteParsed = new EventEmitter(); + protected readonly _onWriteParsed = new EventEmitter(); public readonly onWriteParsed = this._onWriteParsed.event; /** diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index bcdb20654..0ace846af 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -129,32 +129,32 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; - private _onRequestBell = new EventEmitter(); + private readonly _onRequestBell = new EventEmitter(); public readonly onRequestBell = this._onRequestBell.event; - private _onRequestRefreshRows = new EventEmitter(); + private readonly _onRequestRefreshRows = new EventEmitter(); public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; - private _onRequestReset = new EventEmitter(); + private readonly _onRequestReset = new EventEmitter(); public readonly onRequestReset = this._onRequestReset.event; - private _onRequestSendFocus = new EventEmitter(); + private readonly _onRequestSendFocus = new EventEmitter(); public readonly onRequestSendFocus = this._onRequestSendFocus.event; - private _onRequestSyncScrollBar = new EventEmitter(); + private readonly _onRequestSyncScrollBar = new EventEmitter(); public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; - private _onRequestWindowsOptionsReport = new EventEmitter(); + private readonly _onRequestWindowsOptionsReport = new EventEmitter(); public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; - private _onA11yChar = new EventEmitter(); + private readonly _onA11yChar = new EventEmitter(); public readonly onA11yChar = this._onA11yChar.event; - private _onA11yTab = new EventEmitter(); + private readonly _onA11yTab = new EventEmitter(); public readonly onA11yTab = this._onA11yTab.event; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onLineFeed = new EventEmitter(); + private readonly _onLineFeed = new EventEmitter(); public readonly onLineFeed = this._onLineFeed.event; - private _onScroll = new EventEmitter(); + private readonly _onScroll = new EventEmitter(); public readonly onScroll = this._onScroll.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onColor = new EventEmitter(); + private readonly _onColor = new EventEmitter(); public readonly onColor = this._onColor.event; private _parseStack: IParseStack = { diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index b2223b032..46fcb0979 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -19,7 +19,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _alt!: Buffer; private _activeBuffer!: Buffer; - private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); + private readonly _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); public readonly onBufferActivate = this._onBufferActivate.event; /** diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 9737b3894..56d64a726 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -15,7 +15,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } - private _onDispose = new EventEmitter(); + private readonly _onDispose = new EventEmitter(); public readonly onDispose = this._onDispose.event; constructor( diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 9f816d6c4..bf72ae21c 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -42,7 +42,7 @@ export class WriteBuffer { private _syncCalls = 0; private _didUserInput = false; - private _onWriteParsed = new EventEmitter(); + private readonly _onWriteParsed = new EventEmitter(); public readonly onWriteParsed = this._onWriteParsed.event; constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index f331a0b76..033f5955d 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -12,7 +12,7 @@ export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; - private _onBufferChange = new EventEmitter(); + private readonly _onBufferChange = new EventEmitter(); public readonly onBufferChange = this._onBufferChange.event; constructor(private _core: ICoreTerminal) { diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 49f311470..1bc93041c 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,7 +6,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types'; @@ -22,9 +22,9 @@ export class BufferService extends Disposable implements IBufferService { /** Whether the user is scrolling (locks the scroll position) */ public isUserScrolling: boolean = false; - private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); public readonly onResize = this._onResize.event; - private _onScroll = new EventEmitter(); + private readonly _onScroll = new EventEmitter(); public readonly onScroll = this._onScroll.event; public get buffer(): IBuffer { return this.buffers.active; } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 465a4968b..8c2a24def 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -172,7 +172,7 @@ export class CoreMouseService implements ICoreMouseService { private _activeEncoding: string = ''; private _lastEvent: ICoreMouseEvent | null = null; - private _onProtocolChange = new EventEmitter(); + private readonly _onProtocolChange = new EventEmitter(); public readonly onProtocolChange = this._onProtocolChange.event; constructor( diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index a144eb1d3..9282197b3 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -34,11 +34,11 @@ export class CoreService extends Disposable implements ICoreService { // Circular dependency, this must be unset or memory will leak after Terminal.dispose private _scrollToBottom: (() => void) | undefined; - private _onData = this.register(new EventEmitter()); + private readonly _onData = this.register(new EventEmitter()); public readonly onData = this._onData.event; - private _onUserInput = this.register(new EventEmitter()); + private readonly _onUserInput = this.register(new EventEmitter()); public readonly onUserInput = this._onUserInput.event; - private _onBinary = this.register(new EventEmitter()); + private readonly _onBinary = this.register(new EventEmitter()); public readonly onBinary = this._onBinary.event; constructor( diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index d0f9b9919..9fb611c61 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -27,9 +27,9 @@ export class DecorationService extends Disposable implements IDecorationService */ private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); - private _onDecorationRegistered = this.register(new EventEmitter()); + private readonly _onDecorationRegistered = this.register(new EventEmitter()); public readonly onDecorationRegistered = this._onDecorationRegistered.event; - private _onDecorationRemoved = this.register(new EventEmitter()); + private readonly _onDecorationRemoved = this.register(new EventEmitter()); public readonly onDecorationRemoved = this._onDecorationRemoved.event; public get decorations(): IterableIterator { return this._decorations.values(); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d6662c315..33aa3ee23 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -57,7 +57,7 @@ export class OptionsService implements IOptionsService { public readonly rawOptions: Required; public options: Required; - private _onOptionChange = new EventEmitter(); + private readonly _onOptionChange = new EventEmitter(); public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index b5d19623b..239f4d622 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -14,7 +14,7 @@ export class UnicodeService implements IUnicodeService { private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - private _onChange = new EventEmitter(); + private readonly _onChange = new EventEmitter(); public readonly onChange = this._onChange.event; constructor() { diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 1b1162e5e..639988ebd 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -32,15 +32,15 @@ export class Terminal extends CoreTerminal { // TODO: We should remove options once components adopt optionsService public get options(): Required { return this.optionsService.options; } - private _onBell = new EventEmitter(); + private readonly _onBell = new EventEmitter(); public readonly onBell = this._onBell.event; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onA11yCharEmitter = new EventEmitter(); + private readonly _onA11yCharEmitter = new EventEmitter(); public readonly onA11yChar = this._onA11yCharEmitter.event; - private _onA11yTabEmitter = new EventEmitter(); + private readonly _onA11yTabEmitter = new EventEmitter(); public readonly onA11yTab = this._onA11yTabEmitter.event; /** From ed15a8e17031b8317b5e7dfcae0cdada20523391 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:45:40 -0700 Subject: [PATCH 36/41] Create new event with emitter object to simplify code --- .../xterm-addon-canvas/src/CanvasRenderer.ts | 11 +- addons/xterm-addon-search/src/SearchAddon.ts | 11 +- addons/xterm-addon-webgl/src/WebglAddon.ts | 12 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 19 ++-- src/browser/Linkifier2.ts | 10 +- src/browser/Terminal.ts | 55 ++++------ src/browser/TestUtils.test.ts | 20 ++-- .../decorations/BufferDecorationRenderer.ts | 4 +- src/browser/renderer/dom/DomRenderer.ts | 4 +- src/browser/services/CharSizeService.ts | 7 +- src/browser/services/RenderService.ts | 20 ++-- src/browser/services/SelectionService.ts | 28 ++--- src/common/CircularList.ts | 25 ++--- src/common/CoreTerminal.ts | 27 ++--- src/common/EventEmitter.ts | 16 ++- src/common/InputHandler.ts | 103 ++++++++---------- src/common/TestUtils.test.ts | 22 ++-- src/common/Types.d.ts | 11 +- src/common/buffer/Buffer.test.ts | 6 +- src/common/buffer/Buffer.ts | 4 +- src/common/buffer/BufferReflow.ts | 2 +- src/common/buffer/BufferSet.ts | 11 +- src/common/buffer/Marker.ts | 7 +- src/common/input/WriteBuffer.ts | 7 +- src/common/public/BufferNamespaceApi.ts | 7 +- src/common/services/BufferService.ts | 14 +-- src/common/services/CoreMouseService.ts | 7 +- src/common/services/CoreService.ts | 17 ++- src/common/services/DecorationService.ts | 22 ++-- src/common/services/OptionsService.ts | 7 +- src/common/services/Services.ts | 4 +- src/common/services/UnicodeService.ts | 7 +- src/headless/Terminal.ts | 27 ++--- 33 files changed, 247 insertions(+), 307 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index fd9629c06..726ad0803 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -14,7 +14,7 @@ import { IColorSet, ILinkifier2 } from 'browser/Types'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from './atlas/CharAtlasCache'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; let nextRendererId = 1; @@ -27,8 +27,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - private readonly _onRequestRedraw = new EventEmitter(); - public readonly onRequestRedraw = this._onRequestRedraw.event; + public readonly onRequestRedraw = initEvent(); constructor( private _colors: IColorSet, @@ -48,7 +47,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), - new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) + new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this.onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) ]; this.dimensions = { scaledCharWidth: 0, @@ -128,7 +127,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode)); // Selection foreground requires a full re-render if (this._colors.selectionForeground) { - this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } @@ -201,6 +200,6 @@ export class CanvasRenderer extends Disposable implements IRenderer { } private _requestRedrawViewport(): void { - this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 689899ef1..d14e1fe76 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -4,7 +4,7 @@ */ import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm'; -import { EventEmitter } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; export interface ISearchOptions { regex?: boolean; @@ -72,8 +72,7 @@ export class SearchAddon implements ITerminalAddon { private _resultIndex: number | undefined; - private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>(); - public readonly onDidChangeResults = this._onDidChangeResults.event; + public readonly onDidChangeResults = initEvent<{ resultIndex: number, resultCount: number } | undefined>(); public activate(terminal: Terminal): void { this._terminal = terminal; @@ -89,7 +88,7 @@ export class SearchAddon implements ITerminalAddon { this._highlightTimeout = setTimeout(() => { this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true }); this._resultIndex = this._searchResults ? this._searchResults.size - 1 : -1; - this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 }); + this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 }); }, 200); } } @@ -325,9 +324,9 @@ export class SearchAddon implements ITerminalAddon { private _fireResults(term: string, found: boolean, searchOptions?: ISearchOptions): boolean { if (searchOptions?.decorations) { if (this._resultIndex !== undefined && this._searchResults?.size !== undefined) { - this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); + this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); } else { - this._onDidChangeResults.fire(undefined); + this.onDidChangeResults.fire(undefined); } } this._cachedSearchTerm = term; diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 45858e8c8..548be30c3 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -7,7 +7,7 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; -import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent, initEvent } from 'common/EventEmitter'; import { isSafari } from 'common/Platform'; import { ICoreService, IDecorationService } from 'common/services/Services'; @@ -15,10 +15,8 @@ export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private readonly _onChangeTextureAtlas = new EventEmitter(); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onContextLoss = new EventEmitter(); - public readonly onContextLoss = this._onContextLoss.event; + public readonly onChangeTextureAtlas = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _preserveDrawingBuffer?: boolean @@ -39,8 +37,8 @@ export class WebglAddon implements ITerminalAddon { const decorationService: IDecorationService = (terminal as any)._core._decorationService; const colors: IColorSet = (terminal as any)._core._colorManager.colors; this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer); - forwardEvent(this._renderer.onContextLoss, this._onContextLoss); - forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); + forwardEvent(this._renderer.onContextLoss, this.onContextLoss); + forwardEvent(this._renderer.onChangeTextureAtlas, this.onChangeTextureAtlas); renderService.setRenderer(this._renderer); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 58fbb6ed3..7a4872428 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -18,7 +18,7 @@ import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, initEvent } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; @@ -53,12 +53,9 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - private readonly _onChangeTextureAtlas = new EventEmitter(); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onRequestRedraw = new EventEmitter(); - public readonly onRequestRedraw = this._onRequestRedraw.event; - private readonly _onContextLoss = new EventEmitter(); - public readonly onContextLoss = this._onContextLoss.event; + public readonly onChangeTextureAtlas = initEvent(); + public readonly onRequestRedraw = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _terminal: Terminal, @@ -75,7 +72,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers = [ new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core, this._coreBrowserService), - new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._onRequestRedraw, this._coreBrowserService, coreService) + new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this.onRequestRedraw, this._coreBrowserService, coreService) ]; this.dimensions = { scaledCharWidth: 0, @@ -115,7 +112,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._contextRestorationTimeout = setTimeout(() => { this._contextRestorationTimeout = undefined; console.warn('webgl context not restored; firing onContextLoss'); - this._onContextLoss.fire(e); + this.onContextLoss.fire(e); }, 3000 /* ms */); })); this.register(addDisposableDomListener(this._canvas, 'webglcontextrestored', (e) => { @@ -283,7 +280,7 @@ export class WebglRenderer extends Disposable implements IRenderer { throw new Error('The webgl renderer only works with the webgl char atlas'); } if (this._charAtlas !== atlas) { - this._onChangeTextureAtlas.fire(atlas.cacheCanvas); + this.onChangeTextureAtlas.fire(atlas.cacheCanvas); } this._charAtlas = atlas; this._charAtlas.warmUp(); @@ -676,7 +673,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _requestRedrawViewport(): void { - this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } } diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index cf5b9dd2e..844b64f82 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -7,7 +7,7 @@ import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent import { IDisposable } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -26,10 +26,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeProviderReplies: Map | undefined; private _activeLine: number = -1; - private readonly _onShowLinkUnderline = this.register(new EventEmitter()); - public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; - private readonly _onHideLinkUnderline = this.register(new EventEmitter()); - public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; + public readonly onShowLinkUnderline = this.register(initEvent()); + public readonly onHideLinkUnderline = this.register(initEvent()); constructor( @IBufferService private readonly _bufferService: IBufferService @@ -343,7 +341,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { const range = link.range; const scrollOffset = this._bufferService.buffer.ydisp; const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined); - const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline; + const emitter = showEvent ? this.onShowLinkUnderline : this.onHideLinkUnderline; emitter.fire(event); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9c10fc16e..90c1b1345 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -37,7 +37,7 @@ import { ITheme, IMarker, IDisposable, ILinkProvider, IDecorationOptions, IDecor import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; @@ -122,27 +122,16 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); - public readonly onKey = this._onKey.event; - private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); - public readonly onRender = this._onRender.event; - private readonly _onSelectionChange = new EventEmitter(); - public readonly onSelectionChange = this._onSelectionChange.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onBell = new EventEmitter(); - public readonly onBell = this._onBell.event; - - private readonly _onFocus = new EventEmitter(); - public readonly onFocus = this._onFocus.event; - private readonly _onBlur = new EventEmitter(); - public readonly onBlur = this._onBlur.event; - private readonly _onA11yCharEmitter = new EventEmitter(); - public readonly onA11yChar = this._onA11yCharEmitter.event; - private readonly _onA11yTabEmitter = new EventEmitter(); - public readonly onA11yTab = this._onA11yTabEmitter.event; + public readonly onCursorMove = initEvent(); + public readonly onKey = initEvent<{ key: string, domEvent: KeyboardEvent }>(); + public readonly onRender = initEvent<{ start: number, end: number }>(); + public readonly onSelectionChange = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onBell = initEvent(); + public readonly onFocus = initEvent(); + public readonly onBlur = initEvent(); + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); /** * Creates a new `Terminal` object. @@ -169,16 +158,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this._instantiationService.setService(IDecorationService, this._decorationService); // Setup InputHandler listeners - this.register(this._inputHandler.onRequestBell(() => this._onBell.fire())); + this.register(this._inputHandler.onRequestBell(() => this.onBell.fire())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event))); - this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); - this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); - this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); - this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); + this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab)); // Setup listeners this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); @@ -326,7 +315,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.updateCursorStyle(ev); this.element!.classList.add('focus'); this._showCursor(); - this._onFocus.fire(); + this.onFocus.fire(); } /** @@ -349,7 +338,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.coreService.triggerDataEvent(C0.ESC + '[O'); } this.element!.classList.remove('focus'); - this._onBlur.fire(); + this.onBlur.fire(); } private _syncTextArea(): void { @@ -512,7 +501,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const renderer = this._createRenderer(); this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement)); this._instantiationService.setService(IRenderService, this._renderService); - this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); + this.register(this._renderService.onRenderedViewportChange(e => this.onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); this._compositionView = document.createElement('div'); @@ -552,7 +541,7 @@ export class Terminal extends CoreTerminal implements ITerminal { )); this._instantiationService.setService(ISelectionService, this._selectionService); this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent))); - this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire())); + this.register(this._selectionService.onSelectionChange(() => this.onSelectionChange.fire())); this.register(this._selectionService.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode))); this.register(this._selectionService.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it @@ -1101,7 +1090,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.textarea!.value = ''; } - this._onKey.fire({ key: result.key, domEvent: event }); + this.onKey.fire({ key: result.key, domEvent: event }); this._showCursor(); this.coreService.triggerDataEvent(result.key, true); @@ -1184,7 +1173,7 @@ export class Terminal extends CoreTerminal implements ITerminal { key = String.fromCharCode(key); - this._onKey.fire({ key, domEvent: ev }); + this.onKey.fire({ key, domEvent: ev }); this._showCursor(); this.coreService.triggerDataEvent(key, true); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 0b5e00c17..959203756 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; @@ -352,7 +352,7 @@ export class MockCoreBrowserService implements ICoreBrowserService { export class MockCharSizeService implements ICharSizeService { public serviceBrand: undefined; public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - public onCharSizeChange: IEvent = new EventEmitter().event; + public onCharSizeChange: IEvent = initEvent(); constructor(public width: number, public height: number) {} public measure(): void {} } @@ -370,10 +370,10 @@ export class MockMouseService implements IMouseService { export class MockRenderService implements IRenderService { public serviceBrand: undefined; - public onDimensionsChange: IEvent = new EventEmitter().event; - public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; - public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; - public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event; + public onDimensionsChange: IEvent = initEvent(); + public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>(); + public onRender: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>(); + public onRefreshRequest: IEvent<{ start: number, end: number}, void> = initEvent<{ start: number, end: number }>(); public dimensions: IRenderDimensions = { scaledCharWidth: 0, scaledCharHeight: 0, @@ -457,10 +457,10 @@ export class MockSelectionService implements ISelectionService { public hasSelection: boolean = false; public selectionStart: [number, number] | undefined; public selectionEnd: [number, number] | undefined; - public onLinuxMouseSelection = new EventEmitter().event; - public onRequestRedraw = new EventEmitter().event; - public onRequestScrollLines = new EventEmitter().event; - public onSelectionChange = new EventEmitter().event; + public onLinuxMouseSelection = initEvent(); + public onRequestRedraw = initEvent(); + public onRequestScrollLines = initEvent(); + public onSelectionChange = initEvent(); public disable(): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 7fcc5ea95..23ba174f8 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -95,7 +95,7 @@ export class BufferDecorationRenderer extends Disposable { // outside of viewport if (decoration.element) { decoration.element.style.display = 'none'; - decoration.onRenderEmitter.fire(decoration.element); + decoration.onRender.fire(decoration.element); } } else { let element = this._decorationElements.get(decoration); @@ -108,7 +108,7 @@ export class BufferDecorationRenderer extends Disposable { } element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`; element.style.display = this._altBufferIsActive ? 'none' : 'block'; - decoration.onRenderEmitter.fire(element); + decoration.onRender.fire(element); } } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 223e24705..39de7b2cf 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { color } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - public readonly onRequestRedraw = new EventEmitter().event; + public readonly onRequestRedraw = initEvent(); constructor( private _colors: IColorSet, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 7062deece..583006d61 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -4,7 +4,7 @@ */ import { IOptionsService } from 'common/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; export class CharSizeService implements ICharSizeService { @@ -16,8 +16,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - private readonly _onCharSizeChange = new EventEmitter(); - public readonly onCharSizeChange = this._onCharSizeChange.event; + public readonly onCharSizeChange = initEvent(); constructor( document: Document, @@ -32,7 +31,7 @@ export class CharSizeService implements ICharSizeService { if (result.width !== this.width || result.height !== this.height) { this.width = result.width; this.height = result.height; - this._onCharSizeChange.fire(); + this.onCharSizeChange.fire(); } } } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 66dbb7aa2..dfb4df80b 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -5,7 +5,7 @@ import { IRenderer, IRenderDimensions } from 'browser/renderer/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -39,14 +39,10 @@ export class RenderService extends Disposable implements IRenderService { columnSelectMode: false }; - private readonly _onDimensionsChange = new EventEmitter(); - public readonly onDimensionsChange = this._onDimensionsChange.event; - private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); - public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; - private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); - public readonly onRender = this._onRender.event; - private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); - public readonly onRefreshRequest = this._onRefreshRequest.event; + public readonly onDimensionsChange = initEvent(); + public readonly onRenderedViewportChange = initEvent<{ start: number, end: number }>(); + public readonly onRender = initEvent<{ start: number, end: number }>(); + public readonly onRefreshRequest = initEvent<{ start: number, end: number }>(); public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } @@ -135,9 +131,9 @@ export class RenderService extends Disposable implements IRenderService { // Fire render event only if it was not a redraw if (!this._isNextRenderRedrawOnly) { - this._onRenderedViewportChange.fire({ start, end }); + this.onRenderedViewportChange.fire({ start, end }); } - this._onRender.fire({ start, end }); + this.onRender.fire({ start, end }); this._isNextRenderRedrawOnly = true; } @@ -157,7 +153,7 @@ export class RenderService extends Disposable implements IRenderService { if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) { return; } - this._onDimensionsChange.fire(this._renderer.dimensions); + this.onDimensionsChange.fire(this._renderer.dimensions); } public dispose(): void { diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 3780c7ee8..5da197b13 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -9,7 +9,7 @@ import { IBufferLine, IDisposable } from 'common/Types'; import * as Browser from 'common/Platform'; import { SelectionModel } from 'browser/selection/SelectionModel'; import { CellData } from 'common/buffer/CellData'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IMouseService, ISelectionService, IRenderService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferRange, ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; @@ -111,14 +111,10 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionStart: [number, number] | undefined = undefined; private _oldSelectionEnd: [number, number] | undefined = undefined; - private readonly _onLinuxMouseSelection = this.register(new EventEmitter()); - public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; - private readonly _onRedrawRequest = this.register(new EventEmitter()); - public readonly onRequestRedraw = this._onRedrawRequest.event; - private readonly _onSelectionChange = this.register(new EventEmitter()); - public readonly onSelectionChange = this._onSelectionChange.event; - private readonly _onRequestScrollLines = this.register(new EventEmitter()); - public readonly onRequestScrollLines = this._onRequestScrollLines.event; + public readonly onLinuxMouseSelection = this.register(initEvent()); + public readonly onRequestRedraw = this.register(initEvent()); + public readonly onSelectionChange = this.register(initEvent()); + public readonly onRequestScrollLines = this.register(initEvent()); constructor( private readonly _element: HTMLElement, @@ -260,7 +256,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.clearSelection(); this._removeMouseDownListeners(); this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } /** @@ -279,7 +275,7 @@ export class SelectionService extends Disposable implements ISelectionService { if (Browser.isLinux && isLinuxMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { - this._onLinuxMouseSelection.fire(this.selectionText); + this.onLinuxMouseSelection.fire(this.selectionText); } } } @@ -290,7 +286,7 @@ export class SelectionService extends Disposable implements ISelectionService { */ private _refresh(): void { this._refreshAnimationFrame = undefined; - this._onRedrawRequest.fire({ + this.onRequestRedraw.fire({ start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd, columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN @@ -358,7 +354,7 @@ export class SelectionService extends Disposable implements ISelectionService { public selectAll(): void { this._model.isSelectAllActive = true; this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } public selectLines(start: number, end: number): void { @@ -368,7 +364,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.selectionStart = [0, start]; this._model.selectionEnd = [this._bufferService.cols, end]; this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } /** @@ -665,7 +661,7 @@ export class SelectionService extends Disposable implements ISelectionService { return; } if (this._dragScrollAmount) { - this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false }); + this.onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false }); // Re-evaluate selection // If the cursor was above or below the viewport, make sure it's at the // start or end of the viewport respectively. This should only happen when @@ -743,7 +739,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._oldSelectionStart = start; this._oldSelectionEnd = end; this._oldHasSelection = hasSelection; - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void { diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 599db1aba..537434494 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -4,7 +4,7 @@ */ import { ICircularList } from 'common/Types'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; export interface IInsertEvent { index: number; @@ -25,12 +25,9 @@ export class CircularList implements ICircularList { private _startIndex: number; private _length: number; - public readonly onDeleteEmitter = new EventEmitter(); - public readonly onDelete = this.onDeleteEmitter.event; - public readonly onInsertEmitter = new EventEmitter(); - public readonly onInsert = this.onInsertEmitter.event; - public readonly onTrimEmitter = new EventEmitter(); - public readonly onTrim = this.onTrimEmitter.event; + public readonly onDelete = initEvent(); + public readonly onInsert = initEvent(); + public readonly onTrim = initEvent(); constructor( private _maxLength: number @@ -107,7 +104,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(this._length)] = value; if (this._length === this._maxLength) { this._startIndex = ++this._startIndex % this._maxLength; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); } else { this._length++; } @@ -123,7 +120,7 @@ export class CircularList implements ICircularList { throw new Error('Can only recycle when the buffer is full'); } this._startIndex = ++this._startIndex % this._maxLength; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); return this._array[this._getCyclicIndex(this._length - 1)]!; } @@ -158,7 +155,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; } this._length -= deleteCount; - this.onDeleteEmitter.fire({ index: start, amount: deleteCount }); + this.onDelete.fire({ index: start, amount: deleteCount }); } // Add items @@ -169,7 +166,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(start + i)] = items[i]; } if (items.length) { - this.onInsertEmitter.fire({ index: start, amount: items.length }); + this.onInsert.fire({ index: start, amount: items.length }); } // Adjust length as needed @@ -177,7 +174,7 @@ export class CircularList implements ICircularList { const countToTrim = (this._length + items.length) - this._maxLength; this._startIndex += countToTrim; this._length = this._maxLength; - this.onTrimEmitter.fire(countToTrim); + this.onTrim.fire(countToTrim); } else { this._length += items.length; } @@ -193,7 +190,7 @@ export class CircularList implements ICircularList { } this._startIndex += count; this._length -= count; - this.onTrimEmitter.fire(count); + this.onTrim.fire(count); } public shiftElements(start: number, count: number, offset: number): void { @@ -217,7 +214,7 @@ export class CircularList implements ICircularList { while (this._length > this._maxLength) { this._length--; this._startIndex++; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); } } } else { diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 2ad6f7351..dd6953bbe 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -29,7 +29,7 @@ import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/Buffe import { OptionsService } from 'common/services/OptionsService'; import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent, ScrollSource } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; -import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { UnicodeService } from 'common/services/UnicodeService'; import { CharsetService } from 'common/services/CharsetService'; @@ -59,16 +59,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - private readonly _onBinary = new EventEmitter(); - public readonly onBinary = this._onBinary.event; - private readonly _onData = new EventEmitter(); - public readonly onData = this._onData.event; - protected _onLineFeed = new EventEmitter(); - public readonly onLineFeed = this._onLineFeed.event; - private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public readonly onResize = this._onResize.event; - protected readonly _onWriteParsed = new EventEmitter(); - public readonly onWriteParsed = this._onWriteParsed.event; + public readonly onBinary = initEvent(); + public readonly onData = initEvent(); + public readonly onLineFeed = initEvent(); + public readonly onResize = initEvent<{ cols: number, rows: number }>(); + public readonly onWriteParsed = initEvent(); /** * Internally we track the source of the scroll but this is meaningless outside the library so @@ -122,13 +117,13 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Register input handler and handle/forward events this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); - this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); + this.register(forwardEvent(this._inputHandler.onLineFeed, this.onLineFeed)); this.register(this._inputHandler); // Setup listeners - this.register(forwardEvent(this._bufferService.onResize, this._onResize)); - this.register(forwardEvent(this.coreService.onData, this._onData)); - this.register(forwardEvent(this.coreService.onBinary, this._onBinary)); + this.register(forwardEvent(this._bufferService.onResize, this.onResize)); + this.register(forwardEvent(this.coreService.onData, this.onData)); + this.register(forwardEvent(this.coreService.onBinary, this.onBinary)); this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput())); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { @@ -142,7 +137,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup WriteBuffer this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); - this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); + this.register(forwardEvent(this._writeBuffer.onWriteParsed, this.onWriteParsed)); } public dispose(): void { diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 4684809f3..c0074bba3 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -14,11 +14,13 @@ export interface IEvent { } export interface IEventEmitter { - event: IEvent; fire(arg1: T, arg2: U): void; dispose(): void; } +export interface IEventWithEmitter extends IEventEmitter, IEvent { +} + export class EventEmitter implements IEventEmitter { private _listeners: IListener[] = []; private _event?: IEvent; @@ -64,6 +66,18 @@ export class EventEmitter implements IEventEmitter { } } +export function initEvent(): IEventWithEmitter { + const emitter = new EventEmitter(); + const event = emitter.event; + Object.defineProperty(event, 'fire', { + value: emitter.fire.bind(emitter) + }); + Object.defineProperty(event, 'dispose', { + value: emitter.dispose.bind(emitter) + }); + return event as any; +} + export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable { return from(e => to.fire(e)); } diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 7f0879efc..d4711c1f0 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -11,7 +11,7 @@ import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; @@ -132,33 +132,20 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; - private readonly _onRequestBell = new EventEmitter(); - public readonly onRequestBell = this._onRequestBell.event; - private readonly _onRequestRefreshRows = new EventEmitter(); - public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; - private readonly _onRequestReset = new EventEmitter(); - public readonly onRequestReset = this._onRequestReset.event; - private readonly _onRequestSendFocus = new EventEmitter(); - public readonly onRequestSendFocus = this._onRequestSendFocus.event; - private readonly _onRequestSyncScrollBar = new EventEmitter(); - public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; - private readonly _onRequestWindowsOptionsReport = new EventEmitter(); - public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; + public readonly onRequestBell = initEvent(); + public readonly onRequestRefreshRows = initEvent(); + public readonly onRequestReset = initEvent(); + public readonly onRequestSendFocus = initEvent(); + public readonly onRequestSyncScrollBar = initEvent(); + public readonly onRequestWindowsOptionsReport = initEvent(); - private readonly _onA11yChar = new EventEmitter(); - public readonly onA11yChar = this._onA11yChar.event; - private readonly _onA11yTab = new EventEmitter(); - public readonly onA11yTab = this._onA11yTab.event; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onLineFeed = new EventEmitter(); - public readonly onLineFeed = this._onLineFeed.event; - private readonly _onScroll = new EventEmitter(); - public readonly onScroll = this._onScroll.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onColor = new EventEmitter(); - public readonly onColor = this._onColor.event; + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); + public readonly onCursorMove = initEvent(); + public readonly onLineFeed = initEvent(); + public readonly onScroll = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onColor = initEvent(); private _parseStack: IParseStack = { paused: false, @@ -492,11 +479,11 @@ export class InputHandler extends Disposable implements IInputHandler { } if (this._activeBuffer.x !== cursorStartX || this._activeBuffer.y !== cursorStartY) { - this._onCursorMove.fire(); + this.onCursorMove.fire(); } // Refresh any dirty rows accumulated as part of parsing - this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); + this.onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); } public print(data: Uint32Array, start: number, end: number): void { @@ -535,7 +522,7 @@ export class InputHandler extends Disposable implements IInputHandler { } if (screenReaderMode) { - this._onA11yChar.fire(stringFromCodePoint(code)); + this.onA11yChar.fire(stringFromCodePoint(code)); } if (this._currentLinkId !== undefined) { this._oscLinkService.addLineToLink(this._currentLinkId, this._activeBuffer.ybase + this._activeBuffer.y); @@ -687,7 +674,7 @@ export class InputHandler extends Disposable implements IInputHandler { * and `ITerminalOptions.bellSound`. */ public bell(): boolean { - this._onRequestBell.fire(); + this.onRequestBell.fire(); return true; } @@ -719,7 +706,7 @@ export class InputHandler extends Disposable implements IInputHandler { } this._dirtyRowTracker.markDirty(this._activeBuffer.y); - this._onLineFeed.fire(); + this.onLineFeed.fire(); return true; } @@ -808,7 +795,7 @@ export class InputHandler extends Disposable implements IInputHandler { const originalX = this._activeBuffer.x; this._activeBuffer.x = this._activeBuffer.nextStop(); if (this._optionsService.rawOptions.screenReaderMode) { - this._onA11yTab.fire(this._activeBuffer.x - originalX); + this.onA11yTab.fire(this._activeBuffer.x - originalX); } return true; } @@ -1218,7 +1205,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0); this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0); // Force a scroll event to refresh viewport - this._onScroll.fire(0); + this.onScroll.fire(0); } break; } @@ -1849,7 +1836,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(132, this._bufferService.rows); - this._onRequestReset.fire(); + this.onRequestReset.fire(); } break; case 6: @@ -1868,7 +1855,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1890,7 +1877,7 @@ export class InputHandler extends Disposable implements IInputHandler { // focusin: ^[[I // focusout: ^[[O this._coreService.decPrivateModes.sendFocus = true; - this._onRequestSendFocus.fire(); + this.onRequestSendFocus.fire(); break; case 1005: // utf8 ext mode mouse - removed in #2507 this._logService.debug('DECSET 1005 not supported (see #2507)'); @@ -1917,8 +1904,8 @@ export class InputHandler extends Disposable implements IInputHandler { case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); this._coreService.isCursorInitialized = true; - this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._onRequestSyncScrollBar.fire(); + this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1); + this.onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = true; @@ -2087,7 +2074,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(80, this._bufferService.rows); - this._onRequestReset.fire(); + this.onRequestReset.fire(); } break; case 6: @@ -2106,7 +2093,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -2145,8 +2132,8 @@ export class InputHandler extends Disposable implements IInputHandler { this.restoreCursor(); } this._coreService.isCursorInitialized = true; - this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._onRequestSyncScrollBar.fire(); + this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1); + this.onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = false; @@ -2645,7 +2632,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public softReset(params: IParams): boolean { this._coreService.isCursorHidden = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); this._activeBuffer.scrollTop = 0; this._activeBuffer.scrollBottom = this._bufferService.rows - 1; this._curAttrData = DEFAULT_ATTR_DATA.clone(); @@ -2765,11 +2752,11 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (second !== 2) { - this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); + this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); } break; case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t - this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); + this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); break; case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t if (this._bufferService) { @@ -2859,7 +2846,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public setTitle(data: string): boolean { this._windowTitle = data; - this._onTitleChange.fire(data); + this.onTitleChange.fire(data); return true; } @@ -2901,7 +2888,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } if (event.length) { - this._onColor.fire(event); + this.onColor.fire(event); } return true; } @@ -2975,11 +2962,11 @@ export class InputHandler extends Disposable implements IInputHandler { for (let i = 0; i < slots.length; ++i, ++offset) { if (offset >= this._specialColors.length) break; if (slots[i] === '?') { - this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); + this.onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); } else { const color = parseColor(slots[i]); if (color) { - this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); + this.onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); } } } @@ -3040,7 +3027,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public restoreIndexedColor(data: string): boolean { if (!data) { - this._onColor.fire([{ type: ColorRequestType.RESTORE }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE }]); return true; } const event: IColorEvent = []; @@ -3054,7 +3041,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } if (event.length) { - this._onColor.fire(event); + this.onColor.fire(event); } return true; } @@ -3065,7 +3052,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color." */ public restoreFgColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); return true; } @@ -3075,7 +3062,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color." */ public restoreBgColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); return true; } @@ -3085,7 +3072,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color." */ public restoreCursorColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); return true; } @@ -3112,7 +3099,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadApplicationMode(): boolean { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); return true; } @@ -3124,7 +3111,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadNumericMode(): boolean { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); return true; } @@ -3238,7 +3225,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public fullReset(): boolean { this._parser.reset(); - this._onRequestReset.fire(); + this.onRequestReset.fire(); return true; } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index e302d3e4b..2ac70a5e8 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; @@ -17,8 +17,8 @@ export class MockBufferService implements IBufferService { public serviceBrand: any; public get buffer(): IBuffer { return this.buffers.active; } public buffers: IBufferSet = {} as any; - public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event; - public onScroll: IEvent = new EventEmitter().event; + public onResize: IEvent<{ cols: number, rows: number }> = initEvent<{ cols: number, rows: number }>(); + public onScroll: IEvent = initEvent(); public isUserScrolling: boolean = false; constructor( public cols: number, @@ -60,7 +60,7 @@ export class MockCoreMouseService implements ICoreMouseService { public addProtocol(name: string): void { } public reset(): void { } public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } - public onProtocolChange: IEvent = new EventEmitter().event; + public onProtocolChange: IEvent = initEvent(); public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } { throw new Error('Method not implemented.'); } @@ -92,9 +92,9 @@ export class MockCoreService implements ICoreService { sendFocus: false, wraparound: true }; - public onData: IEvent = new EventEmitter().event; - public onUserInput: IEvent = new EventEmitter().event; - public onBinary: IEvent = new EventEmitter().event; + public onData: IEvent = initEvent(); + public onUserInput: IEvent = initEvent(); + public onBinary: IEvent = initEvent(); public reset(): void { } public triggerDataEvent(data: string, wasUserInput?: boolean): void { } public triggerBinaryEvent(data: string): void { } @@ -113,7 +113,7 @@ export class MockOptionsService implements IOptionsService { public serviceBrand: any; public readonly rawOptions: Required = clone(DEFAULT_OPTIONS); public options: Required = this.rawOptions; - public onOptionChange: IEvent = new EventEmitter().event; + public onOptionChange: IEvent = initEvent(); constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { @@ -149,7 +149,7 @@ export class MockUnicodeService implements IUnicodeService { } public versions: string[] = []; public activeVersion: string = ''; - public onChange: IEvent = new EventEmitter().event; + public onChange: IEvent = initEvent(); public wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint); public getStringCellWidth(s: string): number { throw new Error('Method not implemented.'); @@ -159,8 +159,8 @@ export class MockUnicodeService implements IUnicodeService { export class MockDecorationService implements IDecorationService { public serviceBrand: any; public get decorations(): IterableIterator { return [].values(); } - public onDecorationRegistered = new EventEmitter().event; - public onDecorationRemoved = new EventEmitter().event; + public onDecorationRegistered = initEvent(); + public onDecorationRemoved = initEvent(); public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } public reset(): void { } public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index d44bb1977..6e6e93e7e 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -4,7 +4,7 @@ */ import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm'; -import { IEvent, IEventEmitter } from 'common/EventEmitter'; +import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; import { IParams } from 'common/parser/Types'; import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services'; @@ -71,12 +71,9 @@ export interface ICircularList { maxLength: number; isFull: boolean; - onDeleteEmitter: IEventEmitter; - onDelete: IEvent; - onInsertEmitter: IEventEmitter; - onInsert: IEvent; - onTrimEmitter: IEventEmitter; - onTrim: IEvent; + onDelete: IEventWithEmitter; + onInsert: IEventWithEmitter; + onTrim: IEventWithEmitter; get(index: number): T | undefined; set(index: number, value: T): void; diff --git a/src/common/buffer/Buffer.test.ts b/src/common/buffer/Buffer.test.ts index e5ea7f5ec..03297ba62 100644 --- a/src/common/buffer/Buffer.test.ts +++ b/src/common/buffer/Buffer.test.ts @@ -1071,7 +1071,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { @@ -1081,7 +1081,7 @@ describe('Buffer', () => { const marker = buffer.addMarker(0); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); }); @@ -1094,7 +1094,7 @@ describe('Buffer', () => { marker.onDispose(() => eventStack.push('disposed')); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); assert.deepEqual(eventStack, ['disposed']); diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index c8b0d1b25..ec3b9fcd5 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -463,12 +463,12 @@ export class Buffer implements IBuffer { let insertCountEmitted = 0; for (let i = insertEvents.length - 1; i >= 0; i--) { insertEvents[i].index += insertCountEmitted; - this.lines.onInsertEmitter.fire(insertEvents[i]); + this.lines.onInsert.fire(insertEvents[i]); insertCountEmitted += insertEvents[i].amount; } const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength); if (amountToTrim > 0) { - this.lines.onTrimEmitter.fire(amountToTrim); + this.lines.onTrim.fire(amountToTrim); } } } diff --git a/src/common/buffer/BufferReflow.ts b/src/common/buffer/BufferReflow.ts index ece9a96ed..e496cbbb6 100644 --- a/src/common/buffer/BufferReflow.ts +++ b/src/common/buffer/BufferReflow.ts @@ -118,7 +118,7 @@ export function reflowLargerCreateNewLayout(lines: CircularList, to const countToRemove = toRemove[++nextToRemoveIndex]; // Tell markers that there was a deletion - lines.onDeleteEmitter.fire({ + lines.onDelete.fire({ index: i - countRemovedSoFar, amount: countToRemove }); diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index 46fcb0979..1fa6fc275 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -6,7 +6,7 @@ import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { Disposable } from 'common/Lifecycle'; @@ -19,8 +19,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _alt!: Buffer; private _activeBuffer!: Buffer; - private readonly _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); - public readonly onBufferActivate = this._onBufferActivate.event; + public readonly onBufferActivate = this.register(initEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); /** * Create a new BufferSet for the given terminal. @@ -42,7 +41,7 @@ export class BufferSet extends Disposable implements IBufferSet { // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer this._alt = new Buffer(false, this._optionsService, this._bufferService); this._activeBuffer = this._normal; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -86,7 +85,7 @@ export class BufferSet extends Disposable implements IBufferSet { this._alt.clearAllMarkers(); this._alt.clear(); this._activeBuffer = this._normal; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -105,7 +104,7 @@ export class BufferSet extends Disposable implements IBufferSet { this._alt.x = this._normal.x; this._alt.y = this._normal.y; this._activeBuffer = this._alt; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._alt, inactiveBuffer: this._normal }); diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 56d64a726..958bd9edf 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IMarker } from 'common/Types'; @@ -15,8 +15,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } - private readonly _onDispose = new EventEmitter(); - public readonly onDispose = this._onDispose.event; + public readonly onDispose = initEvent(); constructor( public line: number @@ -31,7 +30,7 @@ export class Marker extends Disposable implements IMarker { this.isDisposed = true; this.line = -1; // Emit before super.dispose such that dispose listeners get a change to react - this._onDispose.fire(); + this.onDispose.fire(); super.dispose(); } } diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index bf72ae21c..b8efa54b2 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter'; declare const setTimeout: (handler: () => void, timeout?: number) => void; @@ -42,8 +42,7 @@ export class WriteBuffer { private _syncCalls = 0; private _didUserInput = false; - private readonly _onWriteParsed = new EventEmitter(); - public readonly onWriteParsed = this._onWriteParsed.event; + public readonly onWriteParsed = initEvent(); constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } @@ -237,6 +236,6 @@ export class WriteBuffer { this._pendingData = 0; this._bufferOffset = 0; } - this._onWriteParsed.fire(); + this.onWriteParsed.fire(); } } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index 033f5955d..a00962acd 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -5,20 +5,19 @@ import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm'; import { BufferApiView } from 'common/public/BufferApiView'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICoreTerminal } from 'common/Types'; export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; - private readonly _onBufferChange = new EventEmitter(); - public readonly onBufferChange = this._onBufferChange.event; + public readonly onBufferChange = initEvent(); constructor(private _core: ICoreTerminal) { this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate'); - this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active)); + this._core.buffers.onBufferActivate(() => this.onBufferChange.fire(this.active)); } public get active(): IBufferApi { if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 1bc93041c..3614fcfad 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,7 +6,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; -import { EventEmitter, IEventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types'; @@ -22,10 +22,8 @@ export class BufferService extends Disposable implements IBufferService { /** Whether the user is scrolling (locks the scroll position) */ public isUserScrolling: boolean = false; - private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public readonly onResize = this._onResize.event; - private readonly _onScroll = new EventEmitter(); - public readonly onScroll = this._onScroll.event; + public readonly onResize = initEvent<{ cols: number, rows: number }>(); + public readonly onScroll = initEvent(); public get buffer(): IBuffer { return this.buffers.active; } @@ -49,7 +47,7 @@ export class BufferService extends Disposable implements IBufferService { this.rows = rows; this.buffers.resize(cols, rows); this.buffers.setupTabStops(this.cols); - this._onResize.fire({ cols, rows }); + this.onResize.fire({ cols, rows }); } public reset(): void { @@ -118,7 +116,7 @@ export class BufferService extends Disposable implements IBufferService { buffer.ydisp = buffer.ybase; } - this._onScroll.fire(buffer.ydisp); + this.onScroll.fire(buffer.ydisp); } /** @@ -148,7 +146,7 @@ export class BufferService extends Disposable implements IBufferService { } if (!suppressScrollEvent) { - this._onScroll.fire(buffer.ydisp); + this.onScroll.fire(buffer.ydisp); } } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 8c2a24def..a5f1528a8 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -3,7 +3,7 @@ * @license MIT */ import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; /** @@ -172,8 +172,7 @@ export class CoreMouseService implements ICoreMouseService { private _activeEncoding: string = ''; private _lastEvent: ICoreMouseEvent | null = null; - private readonly _onProtocolChange = new EventEmitter(); - public readonly onProtocolChange = this._onProtocolChange.event; + public readonly onProtocolChange = initEvent(); constructor( @IBufferService private readonly _bufferService: IBufferService, @@ -207,7 +206,7 @@ export class CoreMouseService implements ICoreMouseService { throw new Error(`unknown protocol "${name}"`); } this._activeProtocol = name; - this._onProtocolChange.fire(this._protocols[name].events); + this.onProtocolChange.fire(this._protocols[name].events); } public get activeEncoding(): string { diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 9282197b3..35c759193 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -4,7 +4,7 @@ */ import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IDecPrivateModes, IModes } from 'common/Types'; import { clone } from 'common/Clone'; import { Disposable } from 'common/Lifecycle'; @@ -34,12 +34,9 @@ export class CoreService extends Disposable implements ICoreService { // Circular dependency, this must be unset or memory will leak after Terminal.dispose private _scrollToBottom: (() => void) | undefined; - private readonly _onData = this.register(new EventEmitter()); - public readonly onData = this._onData.event; - private readonly _onUserInput = this.register(new EventEmitter()); - public readonly onUserInput = this._onUserInput.event; - private readonly _onBinary = this.register(new EventEmitter()); - public readonly onBinary = this._onBinary.event; + public readonly onData = this.register(initEvent()); + public readonly onUserInput = this.register(initEvent()); + public readonly onBinary = this.register(initEvent()); constructor( // TODO: Move this into a service @@ -74,12 +71,12 @@ export class CoreService extends Disposable implements ICoreService { // Fire onUserInput so listeners can react as well (eg. clear selection) if (wasUserInput) { - this._onUserInput.fire(); + this.onUserInput.fire(); } // Fire onData API this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); - this._onData.fire(data); + this.onData.fire(data); } public triggerBinaryEvent(data: string): void { @@ -87,6 +84,6 @@ export class CoreService extends Disposable implements ICoreService { return; } this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); - this._onBinary.fire(data); + this.onBinary.fire(data); } } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 522b04dea..5efeb77d8 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -4,7 +4,7 @@ */ import { css } from 'common/Color'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { SortedList } from 'common/SortedList'; @@ -25,10 +25,8 @@ export class DecorationService extends Disposable implements IDecorationService */ private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); - private readonly _onDecorationRegistered = this.register(new EventEmitter()); - public readonly onDecorationRegistered = this._onDecorationRegistered.event; - private readonly _onDecorationRemoved = this.register(new EventEmitter()); - public readonly onDecorationRemoved = this._onDecorationRemoved.event; + public readonly onDecorationRegistered = this.register(initEvent()); + public readonly onDecorationRemoved = this.register(initEvent()); public get decorations(): IterableIterator { return this._decorations.values(); } @@ -42,13 +40,13 @@ export class DecorationService extends Disposable implements IDecorationService decoration.onDispose(() => { if (decoration) { if (this._decorations.delete(decoration)) { - this._onDecorationRemoved.fire(decoration); + this.onDecorationRemoved.fire(decoration); } markerDispose.dispose(); } }); this._decorations.insert(decoration); - this._onDecorationRegistered.fire(decoration); + this.onDecorationRegistered.fire(decoration); } return decoration; } @@ -84,7 +82,7 @@ export class DecorationService extends Disposable implements IDecorationService public dispose(): void { for (const d of this._decorations.values()) { - this._onDecorationRemoved.fire(d); + this.onDecorationRemoved.fire(d); } this.reset(); } @@ -95,10 +93,8 @@ class Decoration extends Disposable implements IInternalDecoration { public element: HTMLElement | undefined; public isDisposed: boolean = false; - public readonly onRenderEmitter = this.register(new EventEmitter()); - public readonly onRender = this.onRenderEmitter.event; - private readonly _onDispose = this.register(new EventEmitter()); - public readonly onDispose = this._onDispose.event; + public readonly onRender = this.register(initEvent()); + public readonly onDispose = this.register(initEvent()); private _cachedBg: IColor | undefined | null = null; public get backgroundColorRGB(): IColor | undefined { @@ -139,7 +135,7 @@ class Decoration extends Disposable implements IInternalDecoration { return; } this._isDisposed = true; - this._onDispose.fire(); + this.onDispose.fire(); super.dispose(); } } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 33aa3ee23..16beddbab 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -4,9 +4,9 @@ */ import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { CursorStyle } from 'common/Types'; +import { initEvent } from 'common/EventEmitter'; export const DEFAULT_OPTIONS: Readonly> = { cols: 80, @@ -57,8 +57,7 @@ export class OptionsService implements IOptionsService { public readonly rawOptions: Required; public options: Required; - private readonly _onOptionChange = new EventEmitter(); - public readonly onOptionChange = this._onOptionChange.event; + public readonly onOptionChange = initEvent(); constructor(options: Partial) { // set the default value of each option @@ -97,7 +96,7 @@ export class OptionsService implements IOptionsService { // Don't fire an option change event if they didn't change if (this.rawOptions[propName] !== value) { this.rawOptions[propName] = value; - this._onOptionChange.fire(propName); + this.onOptionChange.fire(propName); } }; diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e2b517cde..c47b1c2a9 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent, IEventEmitter } from 'common/EventEmitter'; +import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, IOscLinkData } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; @@ -318,5 +318,5 @@ export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; readonly backgroundColorRGB: IColor | undefined; readonly foregroundColorRGB: IColor | undefined; - readonly onRenderEmitter: IEventEmitter; + readonly onRender: IEventWithEmitter; } diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 239f4d622..7306db93f 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -3,7 +3,7 @@ * @license MIT */ import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { UnicodeV6 } from 'common/input/UnicodeV6'; @@ -14,8 +14,7 @@ export class UnicodeService implements IUnicodeService { private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - private readonly _onChange = new EventEmitter(); - public readonly onChange = this._onChange.event; + public readonly onChange = initEvent(); constructor() { const defaultProvider = new UnicodeV6(); @@ -38,7 +37,7 @@ export class UnicodeService implements IUnicodeService { } this._active = version; this._activeProvider = this._providers[version]; - this._onChange.fire(version); + this.onChange.fire(version); } public register(provider: IUnicodeVersionProvider): void { diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 639988ebd..45b8aa749 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -24,7 +24,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBuffer } from 'common/buffer/Types'; import { CoreTerminal } from 'common/CoreTerminal'; -import { EventEmitter, forwardEvent, IEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent, IEvent, initEvent } from 'common/EventEmitter'; import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; import { IMarker, ITerminalOptions, ScrollSource } from 'common/Types'; @@ -32,16 +32,11 @@ export class Terminal extends CoreTerminal { // TODO: We should remove options once components adopt optionsService public get options(): Required { return this.optionsService.options; } - private readonly _onBell = new EventEmitter(); - public readonly onBell = this._onBell.event; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onA11yCharEmitter = new EventEmitter(); - public readonly onA11yChar = this._onA11yCharEmitter.event; - private readonly _onA11yTabEmitter = new EventEmitter(); - public readonly onA11yTab = this._onA11yTabEmitter.event; + public readonly onBell = initEvent(); + public readonly onCursorMove = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); /** * Creates a new `Terminal` object. @@ -65,10 +60,10 @@ export class Terminal extends CoreTerminal { // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestReset(() => this.reset())); - this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); - this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); - this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); - this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); + this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab)); } public dispose(): void { @@ -111,7 +106,7 @@ export class Terminal extends CoreTerminal { } public bell(): void { - this._onBell.fire(); + this.onBell.fire(); } /** From 03fcf60aaf71bb2c5c0f830d2cef3303f5617d5f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:55:01 -0700 Subject: [PATCH 37/41] Docs --- src/common/EventEmitter.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index c0074bba3..979a64644 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -66,6 +66,19 @@ export class EventEmitter implements IEventEmitter { } } +/** + * Creates an object that implements both the {@link IEvent} and {@link IEmitter} interfaces. This + * allows more concise instantiation. The idea is to internally use the combined + * {@link IEventWithEmitter} interface and only expose {@link IEvent} externally. + * + * @example + * ```ts + * public readonly onFoo = initEvent(); + * // ... + * onFoo(e => handle(e)); + * onFoo.fire('bar'); + * ``` + */ export function initEvent(): IEventWithEmitter { const emitter = new EventEmitter(); const event = emitter.event; From 2e8b793ba0329dc84cda871155288a89fc4cb95d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:56:56 -0700 Subject: [PATCH 38/41] Format files --- addons/xterm-addon-webgl/src/WebglAddon.ts | 2 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 548be30c3..b26c565c3 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -20,7 +20,7 @@ export class WebglAddon implements ITerminalAddon { constructor( private _preserveDrawingBuffer?: boolean - ) {} + ) { } public activate(terminal: Terminal): void { if (!terminal.element) { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 7a4872428..8d8b24f71 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -54,8 +54,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _contextRestorationTimeout: number | undefined; public readonly onChangeTextureAtlas = initEvent(); - public readonly onRequestRedraw = initEvent(); - public readonly onContextLoss = initEvent(); + public readonly onRequestRedraw = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _terminal: Terminal, @@ -419,9 +419,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg && - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg && + this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) { continue; } From 2a602ef231abcbb431d240679a6d02f44a99c83b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 10:20:37 -0700 Subject: [PATCH 39/41] Ensure texture atlas comparison uses rgba not object Fixes #4128 --- addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts | 4 ++-- addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts index b0151e30d..fa4be1e53 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts @@ -45,8 +45,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.allowTransparency === b.allowTransparency && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground.rgba === b.colors.foreground.rgba && + a.colors.background.rgba === b.colors.background.rgba; } export function is256Color(colorCode: number): boolean { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 83f82fa76..dc503d756 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -70,8 +70,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.scaledCharHeight === b.scaledCharHeight && a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors && a.minimumContrastRatio === b.minimumContrastRatio && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground.rgba === b.colors.foreground.rgba && + a.colors.background.rgba === b.colors.background.rgba; } export function is256Color(colorCode: number): boolean { From f087bd121ea7f1006bad7564eb4e95cf06490d87 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 21:38:01 -0700 Subject: [PATCH 40/41] Add willReadFrequently to canvas renderer too --- addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index 3098538dd..b809378e6 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -96,7 +96,10 @@ export class DynamicCharAtlas extends BaseCharAtlas { const tmpCanvas = document.createElement('canvas'); tmpCanvas.width = this._config.scaledCharWidth; tmpCanvas.height = this._config.scaledCharHeight; - this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency })); + this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { + alpha: this._config.allowTransparency, + willReadFrequently: true + })); this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth); this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight); From 5b785d29a19e851330fcce6a89058f203b81a716 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 09:09:05 -0700 Subject: [PATCH 41/41] Expose _listeners for debug/test --- src/common/EventEmitter.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 979a64644..29e931d22 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -22,7 +22,7 @@ export interface IEventWithEmitter extends IEventEmitter, IEv } export class EventEmitter implements IEventEmitter { - private _listeners: IListener[] = []; + private readonly _listeners: IListener[] = []; private _event?: IEvent; private _disposed: boolean = false; @@ -82,6 +82,9 @@ export class EventEmitter implements IEventEmitter { export function initEvent(): IEventWithEmitter { const emitter = new EventEmitter(); const event = emitter.event; + Object.defineProperty(event, '_listeners', { + value: (emitter as any)._listeners + }); Object.defineProperty(event, 'fire', { value: emitter.fire.bind(emitter) });