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 1c0a986a..7453b7e2 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 d66e3b03..0e2b0357 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 b4593d90..9ba83de6 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 4c3874c7..32256cf4 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 9ba83de6..092cf2e5 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 d8dcde6f..4ec0053c 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 d8dcde6f..5bd6319c 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 a66e39c1..a9f8cc02 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 05f2c61c..df4a72da 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 019bf42a..cf60a1f5 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 5bd6319c..a22a423f 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 9aba3818..afdac748 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 a9f8cc02..35bcdb88 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 7b823b12..6be0584f 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 092cf2e5..d23caac0 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 00000000..6d3b341b --- /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 6d3b341b..240a26ff 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 7db0e5cd..880a0667 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 240a26ff..167e79e9 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 880a0667..ca1321d6 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 167e79e9..529d38a4 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 ca1321d6..3849bdd3 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 d23caac0..dfcede38 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 529d38a4..2aebc599 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 2aebc599..e05ad068 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 e05ad068..49e3f5ec 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 49e3f5ec..27e3a492 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 27e3a492..3b1acfa6 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 dfcede38..4764ede4 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 3849bdd3..97258609 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 8d295942..8bb684a2 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 67fd751e..c9dfd039 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 2c450e42..5c4d2084 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 c9dfd039..3c7727bb 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 c9dfd039..c324d434 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 3b1acfa6..94c5c53b 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 d3eb9e8e..b6ee7bf1 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 d7cb0f7e..02a94392 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -132,6 +132,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(forwardEvent(this._bufferService.onResize, this._onResize)); this.register(forwardEvent(this.coreService.onData, this._onData)); this.register(forwardEvent(this.coreService.onBinary, this._onBinary)); + this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput())); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 2cdf4e3c..1ac98b51 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -40,11 +40,16 @@ export class WriteBuffer { private _bufferOffset = 0; private _isSyncWriting = false; private _syncCalls = 0; + private _didUserInput = false; public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } private _onWriteParsed = new EventEmitter(); constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } + public handleUserInput(): void { + this._didUserInput = true; + } + /** * @deprecated Unreliable, to be removed soon. */ @@ -99,7 +104,15 @@ export class WriteBuffer { // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; - queueMicrotask(() => this._innerWrite()); + // If this is the first write call after the user has done some input, + // parse it immediately in an upcoming microtask to minimize reduce input, + // otherwise schedule for the next event + if (this._didUserInput) { + this._didUserInput = false; + queueMicrotask(() => this._innerWrite()); + } else { + setTimeout(() => this._innerWrite()); + } } this._pendingData += data.length; From 34df17e36dc309171880bd996b8b37f412bbb4de Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:51:28 -0700 Subject: [PATCH 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 1ac98b51..4f316f24 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -104,15 +104,20 @@ export class WriteBuffer { // schedule chunk processing for next event loop run if (!this._writeBuffer.length) { this._bufferOffset = 0; + // If this is the first write call after the user has done some input, // parse it immediately in an upcoming microtask to minimize reduce input, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; - queueMicrotask(() => this._innerWrite()); - } else { - setTimeout(() => this._innerWrite()); + this._pendingData += data.length; + this._writeBuffer.push(data); + this._callbacks.push(callback); + this._innerWrite(); + return; } + + setTimeout(() => this._innerWrite()); } this._pendingData += data.length; From cd6f370c2855ff0d4ed99030a5a4494323858cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 28 Sep 2022 20:03:55 +0200 Subject: [PATCH 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 8bb684a2..c594da3e 100644 --- a/demo/server.js +++ b/demo/server.js @@ -79,14 +79,25 @@ function startServer() { console.log('Connected to terminal ' + term.pid); ws.send(logs[term.pid]); + // unbuffered delivery after user input + let userInput = false; + // string message buffering - function buffer(socket, timeout) { + function buffer(socket, timeout, maxSize) { let s = ''; let sender = null; return (data) => { s += data; - if (!sender) { - sender = queueMicrotask(() => { + if (s.length > maxSize || userInput) { + userInput = false; + socket.send(s); + s = ''; + if (sender) { + clearTimeout(sender); + sender = null; + } + } else if (!sender) { + sender = setTimeout(() => { socket.send(s); s = ''; sender = null; @@ -95,15 +106,24 @@ function startServer() { }; } // binary message buffering - function bufferUtf8(socket, timeout) { + function bufferUtf8(socket, timeout, maxSize) { let buffer = []; let sender = null; let length = 0; return (data) => { buffer.push(data); length += data.length; - if (!sender) { - sender = queueMicrotask(() => { + if (length > maxSize || userInput) { + userInput = false; + socket.send(Buffer.concat(buffer, length)); + buffer = []; + length = 0; + if (sender) { + clearTimeout(sender); + sender = null; + } + } else if (!sender) { + sender = setTimeout(() => { socket.send(Buffer.concat(buffer, length)); buffer = []; sender = null; @@ -112,7 +132,7 @@ function startServer() { } }; } - const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5); + const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 2, 262144); // WARNING: This is a naive implementation that will not throttle the flow of data. This means // it could flood the communication channel and make the terminal unresponsive. Learn more about @@ -126,6 +146,7 @@ function startServer() { }); ws.on('message', function(msg) { term.write(msg); + userInput = true; }); ws.on('close', function () { term.kill(); From 193d305dcd37537aa47758e580059ffd220b8723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 29 Sep 2022 10:36:08 +0200 Subject: [PATCH 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 c594da3e..0e82f9e9 100644 --- a/demo/server.js +++ b/demo/server.js @@ -16,8 +16,7 @@ function startServer() { var app = express(); expressWs(app); - var terminals = {}, - logs = {}; + var terminals = {}; app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.get('/logo.png', (req, res) => { @@ -55,10 +54,6 @@ function startServer() { console.log('Created terminal with PID: ' + term.pid); terminals[term.pid] = term; - logs[term.pid] = ''; - term.on('data', function(data) { - logs[term.pid] += data; - }); res.send(term.pid.toString()); res.end(); }); @@ -77,7 +72,6 @@ function startServer() { app.ws('/terminals/:pid', function (ws, req) { var term = terminals[parseInt(req.params.pid)]; console.log('Connected to terminal ' + term.pid); - ws.send(logs[term.pid]); // unbuffered delivery after user input let userInput = false; @@ -132,17 +126,13 @@ function startServer() { } }; } - const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 2, 262144); + const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 5, 262144); // WARNING: This is a naive implementation that will not throttle the flow of data. This means // it could flood the communication channel and make the terminal unresponsive. Learn more about // the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/ term.on('data', function(data) { - try { - send(data); - } catch (ex) { - // The WebSocket is not open, ignore - } + send(data); }); ws.on('message', function(msg) { term.write(msg); @@ -153,7 +143,6 @@ function startServer() { console.log('Closed terminal ' + term.pid); // Clean things up delete terminals[term.pid]; - delete logs[term.pid]; }); }); diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 4f316f24..8cb1edf4 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -106,7 +106,7 @@ export class WriteBuffer { this._bufferOffset = 0; // If this is the first write call after the user has done some input, - // parse it immediately in an upcoming microtask to minimize reduce input, + // parse it immediately to minimize reduce input, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; From 150499b7e5ee56fa4bc0985f0099ce739ff6f802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 29 Sep 2022 10:46:25 +0200 Subject: [PATCH 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 8cb1edf4..5e19105d 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -106,7 +106,7 @@ export class WriteBuffer { this._bufferOffset = 0; // If this is the first write call after the user has done some input, - // parse it immediately to minimize reduce input, + // parse it immediately to minimize input latency, // otherwise schedule for the next event if (this._didUserInput) { this._didUserInput = false; From 52ed65d7bda0c041d9ce29e2b53558d2a3fbf237 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:26:07 -0700 Subject: [PATCH 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 a7f4a700..689b847a 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -70,13 +70,11 @@ const INDICES_PER_CELL = 10; const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; const CELL_POSITION_INDICES = 2; -/** Work variables to avoid garbage collection. */ -const w: { i: number, glyph: IRasterizedGlyph | undefined, leftCellPadding: number, clippedPixels: number } = { - i: 0, - glyph: undefined, - leftCellPadding: 0, - clippedPixels: 0 -}; +// Work variables to avoid garbage collection +let $i = 0; +let $glyph: IRasterizedGlyph | undefined = undefined; +let $leftCellPadding = 0; +let $clippedPixels = 0; export class GlyphRenderer extends Disposable { private _atlas: WebglCharAtlas | undefined; @@ -186,12 +184,12 @@ export class GlyphRenderer extends Disposable { } private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { - w.i = (y * this._terminal.cols + x) * INDICES_PER_CELL; + $i = (y * this._terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null character, allow space character to continue as it may have // underline/strikethrough styles if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { - fill(array, 0, w.i, w.i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); + fill(array, 0, $i, $i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -201,39 +199,39 @@ export class GlyphRenderer extends Disposable { // Get the glyph if (chars && chars.length > 1) { - w.glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext); + $glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext); } else { - w.glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext); + $glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext); } - w.leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2); - if (bg !== lastBg && w.glyph.offset.x > w.leftCellPadding) { - w.clippedPixels = w.glyph.offset.x - w.leftCellPadding; + $leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2); + if (bg !== lastBg && $glyph.offset.x > $leftCellPadding) { + $clippedPixels = $glyph.offset.x - $leftCellPadding; // a_origin - array[w.i ] = -(w.glyph.offset.x - w.clippedPixels) + this._dimensions.scaledCharLeft; - array[w.i + 1] = -w.glyph.offset.y + this._dimensions.scaledCharTop; + array[$i ] = -($glyph.offset.x - $clippedPixels) + this._dimensions.scaledCharLeft; + array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop; // a_size - array[w.i + 2] = (w.glyph.size.x - w.clippedPixels) / this._dimensions.scaledCanvasWidth; - array[w.i + 3] = w.glyph.size.y / this._dimensions.scaledCanvasHeight; + array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.scaledCanvasWidth; + array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight; // a_texcoord - array[w.i + 4] = w.glyph.texturePositionClipSpace.x + w.clippedPixels / this._atlas.cacheCanvas.width; - array[w.i + 5] = w.glyph.texturePositionClipSpace.y; + array[$i + 4] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 5] = $glyph.texturePositionClipSpace.y; // a_texsize - array[w.i + 6] = w.glyph.sizeClipSpace.x - w.clippedPixels / this._atlas.cacheCanvas.width; - array[w.i + 7] = w.glyph.sizeClipSpace.y; + array[$i + 6] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.cacheCanvas.width; + array[$i + 7] = $glyph.sizeClipSpace.y; } else { // a_origin - array[w.i ] = -w.glyph.offset.x + this._dimensions.scaledCharLeft; - array[w.i + 1] = -w.glyph.offset.y + this._dimensions.scaledCharTop; + array[$i ] = -$glyph.offset.x + this._dimensions.scaledCharLeft; + array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop; // a_size - array[w.i + 2] = w.glyph.size.x / this._dimensions.scaledCanvasWidth; - array[w.i + 3] = w.glyph.size.y / this._dimensions.scaledCanvasHeight; + array[$i + 2] = $glyph.size.x / this._dimensions.scaledCanvasWidth; + array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight; // a_texcoord - array[w.i + 4] = w.glyph.texturePositionClipSpace.x; - array[w.i + 5] = w.glyph.texturePositionClipSpace.y; + array[$i + 4] = $glyph.texturePositionClipSpace.x; + array[$i + 5] = $glyph.texturePositionClipSpace.y; // a_texsize - array[w.i + 6] = w.glyph.sizeClipSpace.x; - array[w.i + 7] = w.glyph.sizeClipSpace.y; + array[$i + 6] = $glyph.sizeClipSpace.x; + array[$i + 7] = $glyph.sizeClipSpace.y; } // a_cellpos only changes on resize } diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ef08fb7c..dccc7f6a 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -58,17 +58,15 @@ const BYTES_PER_RECTANGLE = INDICES_PER_RECTANGLE * Float32Array.BYTES_PER_ELEME const INITIAL_BUFFER_RECTANGLE_CAPACITY = 20 * INDICES_PER_RECTANGLE; -/** Work variables to avoid garbage collection. */ -const w: { rgba: number, isDefault: boolean, x1: number, y1: number, r: number, g: number, b: number, a: number } = { - rgba: 0, - isDefault: false, - x1: 0, - y1: 0, - r: 0, - g: 0, - b: 0, - a: 0 -}; +// Work variables to avoid garbage collection +let $rgba = 0; +let $isDefault = false; +let $x1 = 0; +let $y1 = 0; +let $r = 0; +let $g = 0; +let $b = 0; +let $a = 0; export class RectangleRenderer extends Disposable { @@ -232,47 +230,47 @@ export class RectangleRenderer extends Disposable { } private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void { - w.isDefault = false; + $isDefault = false; if (fg & FgFlags.INVERSE) { switch (fg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: - w.rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; + $rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - w.rgba = (fg & Attributes.RGB_MASK) << 8; + $rgba = (fg & Attributes.RGB_MASK) << 8; break; case Attributes.CM_DEFAULT: default: - w.rgba = this._colors.foreground.rgba; + $rgba = this._colors.foreground.rgba; } } else { switch (bg & Attributes.CM_MASK) { case Attributes.CM_P16: case Attributes.CM_P256: - w.rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; + $rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - w.rgba = (bg & Attributes.RGB_MASK) << 8; + $rgba = (bg & Attributes.RGB_MASK) << 8; break; case Attributes.CM_DEFAULT: default: - w.rgba = this._colors.background.rgba; - w.isDefault = true; + $rgba = this._colors.background.rgba; + $isDefault = true; } } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); } - w.x1 = startX * this._dimensions.scaledCellWidth; - w.y1 = y * this._dimensions.scaledCellHeight; - w.r = ((w.rgba >> 24) & 0xFF) / 255; - w.g = ((w.rgba >> 16) & 0xFF) / 255; - w.b = ((w.rgba >> 8 ) & 0xFF) / 255; - w.a = (!w.isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1; + $x1 = startX * this._dimensions.scaledCellWidth; + $y1 = y * this._dimensions.scaledCellHeight; + $r = (($rgba >> 24) & 0xFF) / 255; + $g = (($rgba >> 16) & 0xFF) / 255; + $b = (($rgba >> 8 ) & 0xFF) / 255; + $a = (!$isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1; - this._addRectangle(vertices.attributes, offset, w.x1, w.y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, w.r, w.g, w.b, w.a); + this._addRectangle(vertices.attributes, offset, $x1, $y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, $r, $g, $b, $a); } private _addRectangle(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, r: number, g: number, b: number, a: number): void { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d8daa0d1..b276cd05 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -26,14 +26,12 @@ import { CharData, IBufferLine, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; import { ICoreService, IDecorationService } from 'common/services/Services'; -/** Work variables to avoid garbage collection. */ -const w: { fg: number, bg: number, hasFg: boolean, hasBg: boolean, isSelected: boolean } = { - fg: 0, - bg: 0, - hasFg: false, - hasBg: false, - isSelected: false -}; +// Work variables to avoid garbage collection +let $fg = 0; +let $bg = 0; +let $hasFg = false; +let $hasBg = false; +let $isSelected = false; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -475,89 +473,89 @@ export class WebglRenderer extends Disposable implements IRenderer { // override logic throughout the different sub-renderers // Reset overrides work variables - w.bg = 0; - w.fg = 0; - w.hasBg = false; - w.hasFg = false; - w.isSelected = false; + $bg = 0; + $fg = 0; + $hasBg = false; + $hasFg = false; + $isSelected = false; // Apply decorations on the bottom layer this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { if (d.backgroundColorRGB) { - w.bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; } if (d.foregroundColorRGB) { - w.fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } }); // Apply the selection color if needed - w.isSelected = this._isCellSelected(x, y); - if (w.isSelected) { - w.bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $isSelected = this._isCellSelected(x, y); + if ($isSelected) { + $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + $hasBg = true; if (this._colors.selectionForeground) { - w.fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } } // Apply decorations on the top layer this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { if (d.backgroundColorRGB) { - w.bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasBg = true; + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; } if (d.foregroundColorRGB) { - w.fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - w.hasFg = true; + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; } }); // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag // ahead of time in order to use the correct cache key - if (w.hasBg) { - if (w.isSelected) { + if ($hasBg) { + if ($isSelected) { // Non-RGB attributes from model + force non-dim + override + force RGB color mode - w.bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | w.bg | Attributes.CM_RGB; + $bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; } else { // Non-RGB attributes from model + override + force RGB color mode - w.bg = (this._workCell.bg & ~Attributes.RGB_MASK) | w.bg | Attributes.CM_RGB; + $bg = (this._workCell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; } } - if (w.hasFg) { + if ($hasFg) { // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - w.fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | w.fg | Attributes.CM_RGB; + $fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; } // Handle case where inverse was specified by only one of bg override or fg override was set, // resolving the other inverse color and setting the inverse flag if needed. if (this._workColors.fg & FgFlags.INVERSE) { - if (w.hasBg && !w.hasFg) { + if ($hasBg && !$hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - w.fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { - w.fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); + $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); } - w.hasFg = true; + $hasFg = true; } - if (!w.hasBg && w.hasFg) { + if (!$hasBg && $hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - w.bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { - w.bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); + $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); } - w.hasBg = true; + $hasBg = true; } } // Use the override if it exists - this._workColors.bg = w.hasBg ? w.bg : this._workColors.bg; - this._workColors.fg = w.hasFg ? w.fg : this._workColors.fg; + this._workColors.bg = $hasBg ? $bg : this._workColors.bg; + this._workColors.fg = $hasFg ? $fg : this._workColors.fg; } private _isCellSelected(x: number, y: number): boolean { diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 4764ede4..dee1e02c 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -54,10 +54,8 @@ interface ICharAtlasActiveRow { height: number; } -/** Work variables to avoid garbage collection. */ -const w: { glyph: IRasterizedGlyph | undefined } = { - glyph: undefined -}; +// Work variables to avoid garbage collection +let $glyph = undefined; export class WebglCharAtlas implements IDisposable { private _didWarmUp: boolean = false; @@ -184,12 +182,12 @@ export class WebglCharAtlas implements IDisposable { fg: number, ext: number ): IRasterizedGlyph { - w.glyph = cacheMap.get(key, bg, fg, ext); - if (!w.glyph) { - w.glyph = this._drawToCache(key, bg, fg, ext); - cacheMap.set(key, bg, fg, ext, w.glyph); + $glyph = cacheMap.get(key, bg, fg, ext); + if (!$glyph) { + $glyph = this._drawToCache(key, bg, fg, ext); + cacheMap.set(key, bg, fg, ext, $glyph); } - return w.glyph; + return $glyph; } private _getColorFromAnsiIndex(idx: number): IColor { diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 43e89839..875ac6c9 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -37,10 +37,8 @@ const enum Cell { export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData()); -/** Work variables to avoid garbage collection. */ -const w: { startIndex: number } = { - startIndex: 0 -}; +// Work variables to avoid garbage collection +let $startIndex = 0; /** * Typed array based bufferline implementation. @@ -178,10 +176,10 @@ export class BufferLine implements IBufferLine { * to GC as it significantly reduced the amount of new objects/references needed. */ public loadCell(index: number, cell: ICellData): ICellData { - w.startIndex = index * CELL_SIZE; - cell.content = this._data[w.startIndex + Cell.CONTENT]; - cell.fg = this._data[w.startIndex + Cell.FG]; - cell.bg = this._data[w.startIndex + Cell.BG]; + $startIndex = index * CELL_SIZE; + cell.content = this._data[$startIndex + Cell.CONTENT]; + cell.fg = this._data[$startIndex + Cell.FG]; + cell.bg = this._data[$startIndex + Cell.BG]; if (cell.content & Content.IS_COMBINED_MASK) { cell.combinedData = this._combined[index]; } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index e5d115a1..dae80151 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -11,11 +11,9 @@ import { SortedList } from 'common/SortedList'; import { IColor } from 'common/Types'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; -/** Work variables to avoid garbage collection. */ -const w = { - xmin: 0, - xmax: 0 -}; +// Work variables to avoid garbage collection +let $xmin = 0; +let $xmax = 0; export class DecorationService extends Disposable implements IDecorationService { public serviceBrand: any; @@ -76,9 +74,9 @@ export class DecorationService extends Disposable implements IDecorationService public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { this._decorations.forEachByKey(line, d => { - w.xmin = d.options.x ?? 0; - w.xmax = w.xmin + (d.options.width ?? 1); - if (x >= w.xmin && x < w.xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { + $xmin = d.options.x ?? 0; + $xmax = $xmin + (d.options.width ?? 1); + if (x >= $xmin && x < $xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { callback(d); } }); From 3a90f7e7c0a508aeed422173df89d6b9366be341 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:39:02 -0700 Subject: [PATCH 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 02a94392..fae3efa5 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -22,7 +22,7 @@ */ import { Disposable } from 'common/Lifecycle'; -import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; @@ -31,7 +31,6 @@ import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent, ScrollSource import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; -import { DirtyRowService } from 'common/services/DirtyRowService'; import { UnicodeService } from 'common/services/UnicodeService'; import { CharsetService } from 'common/services/CharsetService'; import { updateWindowsModeWrappedState } from 'common/WindowsMode'; @@ -49,7 +48,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected readonly _bufferService: IBufferService; protected readonly _logService: ILogService; protected readonly _charsetService: ICharsetService; - protected readonly _dirtyRowService: IDirtyRowService; protected readonly _oscLinkService: IOscLinkService; public readonly coreMouseService: ICoreMouseService; @@ -114,8 +112,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._instantiationService.setService(ICoreService, this.coreService); this.coreMouseService = this._instantiationService.createInstance(CoreMouseService); this._instantiationService.setService(ICoreMouseService, this.coreMouseService); - this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); - this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); this.unicodeService = this._instantiationService.createInstance(UnicodeService); this._instantiationService.setService(IUnicodeService, this.unicodeService); this._charsetService = this._instantiationService.createInstance(CharsetService); @@ -124,7 +120,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._instantiationService.setService(IOscLinkService, this._oscLinkService); // Register input handler and handle/forward events - this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._dirtyRowService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); this.register(this._inputHandler); @@ -136,11 +132,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); - this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); })); this.register(this._inputHandler.onScroll(event => { this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL }); - this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); + this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom); })); // Setup WriteBuffer diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index f734b002..d9127e7b 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -11,7 +11,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; import { AttributeData } from 'common/buffer/AttributeData'; import { Params } from 'common/parser/Params'; -import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test'; +import { MockCoreService, MockBufferService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test'; import { IBufferService, ICoreService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; @@ -67,7 +67,7 @@ describe('InputHandler', () => { bufferService.resize(80, 30); coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService); - inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); describe('SL/SR/DECIC/DECDC', () => { @@ -236,7 +236,7 @@ describe('InputHandler', () => { describe('setMode', () => { it('should toggle bracketedPasteMode', () => { const coreService = new MockCoreService(); - const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(coreService.decPrivateModes.bracketedPasteMode, true); @@ -258,7 +258,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -305,7 +304,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -356,7 +354,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -394,7 +391,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -445,7 +441,6 @@ describe('InputHandler', () => { bufferService, new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -572,7 +567,6 @@ describe('InputHandler', () => { new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), - new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), @@ -599,7 +593,7 @@ describe('InputHandler', () => { beforeEach(() => { bufferService = new MockBufferService(80, 30); - handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', async () => { await handler.parseP('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); @@ -796,7 +790,7 @@ describe('InputHandler', () => { describe('colon notation', () => { let inputHandler2: TestInputHandler; beforeEach(() => { - inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); describe('should equal to semicolon', () => { it('CSI 38:2::50:100:150 m', async () => { @@ -2278,7 +2272,7 @@ describe('InputHandler - async handlers', () => { coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService); coreService.onData(data => { console.log(data); }); - inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService()); }); it('async CUP with CPR check', async () => { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index b599bb7e..dd61cf34 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -12,11 +12,11 @@ import { Disposable } from 'common/Lifecycle'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; +import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; @@ -104,6 +104,8 @@ export enum WindowsOptionsReportType { // create a warning log if an async handler takes longer than the limit (in ms) const SLOW_ASYNC_LIMIT = 5000; +// Work variables to avoid garbage collection +let $temp = 0; /** * The terminal's standard implementation of IInputHandler, this handles all @@ -120,6 +122,7 @@ export class InputHandler extends Disposable implements IInputHandler { private _windowTitle = ''; private _iconName = ''; private _currentLinkId?: number; + private _dirtyRowTracker: IDirtyRowTracker; protected _windowTitleStack: string[] = []; protected _iconNameStack: string[] = []; @@ -169,7 +172,6 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _bufferService: IBufferService, private readonly _charsetService: ICharsetService, private readonly _coreService: ICoreService, - private readonly _dirtyRowService: IDirtyRowService, private readonly _logService: ILogService, private readonly _optionsService: IOptionsService, private readonly _oscLinkService: IOscLinkService, @@ -179,6 +181,7 @@ export class InputHandler extends Disposable implements IInputHandler { ) { super(); this.register(this._parser); + this._dirtyRowTracker = new DirtyRowTracker(this._bufferService); // Track properties used in performance critical code manually to avoid using slow getters this._activeBuffer = this._bufferService.buffer; @@ -459,7 +462,7 @@ export class InputHandler extends Disposable implements IInputHandler { // Clear the dirty row service so we know which lines changed as a result of parsing // Important: do not clear between async calls, otherwise we lost pending update information. if (!wasPaused) { - this._dirtyRowService.clearRange(); + this._dirtyRowTracker.clearRange(); } // process big data in smaller chunks @@ -493,7 +496,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // Refresh any dirty rows accumulated as part of parsing - this._onRequestRefreshRows.fire(this._dirtyRowService.start, this._dirtyRowService.end); + this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); } public print(data: Uint32Array, start: number, end: number): void { @@ -507,7 +510,7 @@ export class InputHandler extends Disposable implements IInputHandler { const curAttr = this._curAttrData; let bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!; - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) { @@ -635,7 +638,7 @@ export class InputHandler extends Disposable implements IInputHandler { bufferRow.setCellFromCodePoint(this._activeBuffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended); } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** @@ -699,7 +702,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF." */ public lineFeed(): boolean { - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); if (this._optionsService.rawOptions.convertEol) { this._activeBuffer.x = 0; } @@ -714,7 +717,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._activeBuffer.x >= this._bufferService.cols) { this._activeBuffer.x--; } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); this._onLineFeed.fire(); return true; @@ -842,14 +845,14 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.y = this._coreService.decPrivateModes.origin ? Math.min(this._activeBuffer.scrollBottom, Math.max(this._activeBuffer.scrollTop, this._activeBuffer.y)) : Math.min(this._bufferService.rows - 1, Math.max(0, this._activeBuffer.y)); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** * Set absolute cursor position. */ private _setCursor(x: number, y: number): void { - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); if (this._coreService.decPrivateModes.origin) { this._activeBuffer.x = x; this._activeBuffer.y = this._activeBuffer.scrollTop + y; @@ -858,7 +861,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.y = y; } this._restrictCursor(); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } /** @@ -1178,16 +1181,16 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 0: j = this._activeBuffer.y; - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect); for (; j < this._bufferService.rows; j++) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); break; case 1: j = this._activeBuffer.y; - this._dirtyRowService.markDirty(j); + this._dirtyRowTracker.markDirty(j); // Deleted front part of line and everything before. This line will no longer be wrapped. this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true, respectProtect); if (this._activeBuffer.x + 1 >= this._bufferService.cols) { @@ -1197,15 +1200,15 @@ export class InputHandler extends Disposable implements IInputHandler { while (j--) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(0); + this._dirtyRowTracker.markDirty(0); break; case 2: j = this._bufferService.rows; - this._dirtyRowService.markDirty(j - 1); + this._dirtyRowTracker.markDirty(j - 1); while (j--) { this._resetBufferLine(j, respectProtect); } - this._dirtyRowService.markDirty(0); + this._dirtyRowTracker.markDirty(0); break; case 3: // Clear scrollback (everything not in viewport) @@ -1257,7 +1260,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true, respectProtect); break; } - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); return true; } @@ -1289,7 +1292,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(row, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? return true; } @@ -1323,7 +1326,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(j, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom); this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? return true; } @@ -1349,7 +1352,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -1375,7 +1378,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -1395,7 +1398,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 1); this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 0, this._activeBuffer.getBlankLine(this._eraseAttrData())); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1411,7 +1414,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 1); this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 0, this._activeBuffer.getBlankLine(DEFAULT_ATTR_DATA)); } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1443,7 +1446,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1476,7 +1479,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1499,7 +1502,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1522,7 +1525,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; } @@ -1544,7 +1547,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData() ); - this._dirtyRowService.markDirty(this._activeBuffer.y); + this._dirtyRowTracker.markDirty(this._activeBuffer.y); } return true; } @@ -3220,7 +3223,7 @@ export class InputHandler extends Disposable implements IInputHandler { const scrollRegionHeight = this._activeBuffer.scrollBottom - this._activeBuffer.scrollTop; this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase + this._activeBuffer.y, scrollRegionHeight, 1); this._activeBuffer.lines.set(this._activeBuffer.ybase + this._activeBuffer.y, this._activeBuffer.getBlankLine(this._eraseAttrData())); - this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); + this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); } else { this._activeBuffer.y--; this._restrictCursor(); // quickfix to not run out of bounds @@ -3293,7 +3296,7 @@ export class InputHandler extends Disposable implements IInputHandler { line.isWrapped = false; } } - this._dirtyRowService.markAllDirty(); + this._dirtyRowTracker.markAllDirty(); this._setCursor(0, 0); return true; } @@ -3344,4 +3347,60 @@ export class InputHandler extends Disposable implements IInputHandler { if (data === ' q') return f(`P1$r${STYLES[opts.cursorStyle] - (opts.cursorBlink ? 1 : 0)} q`); return f(`P0$r`); } + + public markRangeDirty(y1: number, y2: number): void { + this._dirtyRowTracker.markRangeDirty(y1, y2); + } +} + +export interface IDirtyRowTracker { + readonly start: number; + readonly end: number; + + clearRange(): void; + markDirty(y: number): void; + markRangeDirty(y1: number, y2: number): void; + markAllDirty(): void; +} + +class DirtyRowTracker implements IDirtyRowTracker { + public start!: number; + public end!: number; + + constructor( + @IBufferService private readonly _bufferService: IBufferService + ) { + this.clearRange(); + } + + public clearRange(): void { + this.start = this._bufferService.buffer.y; + this.end = this._bufferService.buffer.y; + } + + public markDirty(y: number): void { + if (y < this.start) { + this.start = y; + } else if (y > this.end) { + this.end = y; + } + } + + public markRangeDirty(y1: number, y2: number): void { + if (y1 > y2) { + $temp = y1; + y1 = y2; + y2 = $temp; + } + if (y1 < this.start) { + this.start = y1; + } + if (y2 > this.end) { + this.end = y2; + } + } + + public markAllDirty(): void { + this.markRangeDirty(0, this._bufferService.rows - 1); + } } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 8fb71a5d..e302d3e4 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -100,16 +100,6 @@ export class MockCoreService implements ICoreService { public triggerBinaryEvent(data: string): void { } } -export class MockDirtyRowService implements IDirtyRowService { - public serviceBrand: any; - public start: number = 0; - public end: number = 0; - public clearRange(): void { } - public markDirty(y: number): void { } - public markRangeDirty(y1: number, y2: number): void { } - public markAllDirty(): void { } -} - export class MockLogService implements ILogService { public serviceBrand: any; public logLevel = LogLevelEnum.DEBUG; diff --git a/src/common/services/DirtyRowService.ts b/src/common/services/DirtyRowService.ts deleted file mode 100644 index 1c43b67e..00000000 --- a/src/common/services/DirtyRowService.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IBufferService, IDirtyRowService } from 'common/services/Services'; - -export class DirtyRowService implements IDirtyRowService { - public serviceBrand: any; - - private _start!: number; - private _end!: number; - - public get start(): number { return this._start; } - public get end(): number { return this._end; } - - constructor( - @IBufferService private readonly _bufferService: IBufferService - ) { - this.clearRange(); - } - - public clearRange(): void { - this._start = this._bufferService.buffer.y; - this._end = this._bufferService.buffer.y; - } - - public markDirty(y: number): void { - if (y < this._start) { - this._start = y; - } else if (y > this._end) { - this._end = y; - } - } - - public markRangeDirty(y1: number, y2: number): void { - if (y1 > y2) { - const temp = y1; - y1 = y2; - y2 = temp; - } - if (y1 < this._start) { - this._start = y1; - } - if (y2 > this._end) { - this._end = y2; - } - } - - public markAllDirty(): void { - this.markRangeDirty(0, this._bufferService.rows - 1); - } -} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 22edad1d..e2b517cd 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -122,19 +122,6 @@ export interface ICharsetService { setgCharset(g: number, charset: ICharset | undefined): void; } -export const IDirtyRowService = createDecorator('DirtyRowService'); -export interface IDirtyRowService { - serviceBrand: undefined; - - readonly start: number; - readonly end: number; - - clearRange(): void; - markDirty(y: number): void; - markRangeDirty(y1: number, y2: number): void; - markAllDirty(): void; -} - export interface IServiceIdentifier { (...args: any[]): void; type: T; From cf9949840b65059aae90589862809318e1b95959 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 07:52:26 -0700 Subject: [PATCH 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 b642efbc..68475829 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -28,7 +28,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; private _onRequestRedraw = new EventEmitter(); - public get onRequestRedraw(): IEvent { return this._onRequestRedraw.event; } + public readonly onRequestRedraw = this._onRequestRedraw.event; constructor( private _colors: IColorSet, diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 5b98a048..a96e7c64 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -16,9 +16,9 @@ export class WebglAddon implements ITerminalAddon { private _renderer?: WebglRenderer; private _onChangeTextureAtlas = new EventEmitter(); - public get onChangeTextureAtlas(): IEvent { return this._onChangeTextureAtlas.event; } + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; private _onContextLoss = new EventEmitter(); - public get onContextLoss(): IEvent { return this._onContextLoss.event; } + public readonly onContextLoss = this._onContextLoss.event; constructor( private _preserveDrawingBuffer?: boolean diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d8daa0d1..74488c6c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -56,12 +56,11 @@ export class WebglRenderer extends Disposable implements IRenderer { private _contextRestorationTimeout: number | undefined; private _onChangeTextureAtlas = new EventEmitter(); - public get onChangeTextureAtlas(): IEvent { return this._onChangeTextureAtlas.event; } + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; private _onRequestRedraw = new EventEmitter(); - public get onRequestRedraw(): IEvent { return this._onRequestRedraw.event; } - + public readonly onRequestRedraw = this._onRequestRedraw.event; private _onContextLoss = new EventEmitter(); - public get onContextLoss(): IEvent { return this._onContextLoss.event; } + public readonly onContextLoss = this._onContextLoss.event; constructor( private _terminal: Terminal, diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts index 74aed0cc..6865b6db 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -15,12 +15,12 @@ declare module 'xterm-addon-webgl' { /** * An event that is fired when the renderer loses its canvas context. */ - public get onContextLoss(): IEvent; + public readonly onContextLoss: IEvent; /** * An event that is fired when the texture atlas of the renderer changes. */ - public get onChangeTextureAtlas(): IEvent; + public readonly onChangeTextureAtlas: IEvent; constructor(preserveDrawingBuffer?: boolean); diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 9c978949..6bf9fe23 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -27,9 +27,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeLine: number = -1; private _onShowLinkUnderline = this.register(new EventEmitter()); - public get onShowLinkUnderline(): IEvent { return this._onShowLinkUnderline.event; } + public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; private _onHideLinkUnderline = this.register(new EventEmitter()); - public get onHideLinkUnderline(): IEvent { return this._onHideLinkUnderline.event; } + public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; constructor( @IBufferService private readonly _bufferService: IBufferService diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index afdac748..e03be89c 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -123,26 +123,26 @@ export class Terminal extends CoreTerminal implements ITerminal { private _theme: ITheme | undefined; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); - public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } + public readonly onKey = this._onKey.event; private _onRender = new EventEmitter<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + public readonly onRender = this._onRender.event; private _onSelectionChange = new EventEmitter(); - public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + public readonly onSelectionChange = this._onSelectionChange.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } + public readonly onTitleChange = this._onTitleChange.event; private _onBell = new EventEmitter(); - public get onBell(): IEvent { return this._onBell.event; } + public readonly onBell = this._onBell.event; private _onFocus = new EventEmitter(); - public get onFocus(): IEvent { return this._onFocus.event; } + public readonly onFocus = this._onFocus.event; private _onBlur = new EventEmitter(); - public get onBlur(): IEvent { return this._onBlur.event; } + public readonly onBlur = this._onBlur.event; private _onA11yCharEmitter = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } + public readonly onA11yChar = this._onA11yCharEmitter.event; private _onA11yTabEmitter = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } + public readonly onA11yTab = this._onA11yTabEmitter.event; /** * Creates a new `Terminal` object. diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 8df6b302..223e2470 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - public get onRequestRedraw(): IEvent { return new EventEmitter().event; } + public readonly onRequestRedraw = new EventEmitter().event; constructor( private _colors: IColorSet, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index b04e157f..d5cc37c8 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -17,7 +17,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } private _onCharSizeChange = new EventEmitter(); - public get onCharSizeChange(): IEvent { return this._onCharSizeChange.event; } + public readonly onCharSizeChange = this._onCharSizeChange.event; constructor( document: Document, diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 97258609..69213429 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -40,13 +40,13 @@ export class RenderService extends Disposable implements IRenderService { }; private _onDimensionsChange = new EventEmitter(); - public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } + public readonly onDimensionsChange = this._onDimensionsChange.event; private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); - public get onRenderedViewportChange(): IEvent<{ start: number, end: number }> { return this._onRenderedViewportChange.event; } + public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; private _onRender = new EventEmitter<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + public readonly onRender = this._onRender.event; private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); - public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; } + public readonly onRefreshRequest = this._onRefreshRequest.event; public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 4ee1ffa1..29c8e664 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -112,13 +112,13 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionEnd: [number, number] | undefined = undefined; private _onLinuxMouseSelection = this.register(new EventEmitter()); - public get onLinuxMouseSelection(): IEvent { return this._onLinuxMouseSelection.event; } + public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; private _onRedrawRequest = this.register(new EventEmitter()); - public get onRequestRedraw(): IEvent { return this._onRedrawRequest.event; } + public readonly onRequestRedraw = this._onRedrawRequest.event; private _onSelectionChange = this.register(new EventEmitter()); - public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } + public readonly onSelectionChange = this._onSelectionChange.event; private _onRequestScrollLines = this.register(new EventEmitter()); - public get onRequestScrollLines(): IEvent { return this._onRequestScrollLines.event; } + public readonly onRequestScrollLines = this._onRequestScrollLines.event; constructor( private readonly _element: HTMLElement, diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 4d2c04ec..c0c6bb5b 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -26,11 +26,11 @@ export class CircularList implements ICircularList { private _length: number; public onDeleteEmitter = new EventEmitter(); - public get onDelete(): IEvent { return this.onDeleteEmitter.event; } + public readonly onDelete = this.onDeleteEmitter.event; public onInsertEmitter = new EventEmitter(); - public get onInsert(): IEvent { return this.onInsertEmitter.event; } + public readonly onInsert = this.onInsertEmitter.event; public onTrimEmitter = new EventEmitter(); - public get onTrim(): IEvent { return this.onTrimEmitter.event; } + public readonly onTrim = this.onTrimEmitter.event; constructor( private _maxLength: number diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 02a94392..da70f84a 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -62,21 +62,22 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _windowsMode: IDisposable | undefined; private _onBinary = new EventEmitter(); - public get onBinary(): IEvent { return this._onBinary.event; } + public readonly onBinary = this._onBinary.event; private _onData = new EventEmitter(); - public get onData(): IEvent { return this._onData.event; } + public readonly onData = this._onData.event; protected _onLineFeed = new EventEmitter(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } + public readonly onLineFeed = this._onLineFeed.event; private _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } - protected _onScroll = new EventEmitter(); - public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } + public readonly onResize = this._onResize.event; protected _onWriteParsed = new EventEmitter(); + public readonly onWriteParsed = this._onWriteParsed.event; + /** * Internally we track the source of the scroll but this is meaningless outside the library so * it's filtered out. */ protected _onScrollApi?: EventEmitter; + protected _onScroll = new EventEmitter(); public get onScroll(): IEvent { if (!this._onScrollApi) { this._onScrollApi = new EventEmitter(); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index b599bb7e..bcdb2065 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -130,32 +130,32 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; private _onRequestBell = new EventEmitter(); - public get onRequestBell(): IEvent { return this._onRequestBell.event; } + public readonly onRequestBell = this._onRequestBell.event; private _onRequestRefreshRows = new EventEmitter(); - public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } + public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; private _onRequestReset = new EventEmitter(); - public get onRequestReset(): IEvent { return this._onRequestReset.event; } + public readonly onRequestReset = this._onRequestReset.event; private _onRequestSendFocus = new EventEmitter(); - public get onRequestSendFocus(): IEvent { return this._onRequestSendFocus.event; } + public readonly onRequestSendFocus = this._onRequestSendFocus.event; private _onRequestSyncScrollBar = new EventEmitter(); - public get onRequestSyncScrollBar(): IEvent { return this._onRequestSyncScrollBar.event; } + public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; private _onRequestWindowsOptionsReport = new EventEmitter(); - public get onRequestWindowsOptionsReport(): IEvent { return this._onRequestWindowsOptionsReport.event; } + public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; private _onA11yChar = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yChar.event; } + public readonly onA11yChar = this._onA11yChar.event; private _onA11yTab = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTab.event; } + public readonly onA11yTab = this._onA11yTab.event; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onLineFeed = new EventEmitter(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } + public readonly onLineFeed = this._onLineFeed.event; private _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } + public readonly onScroll = this._onScroll.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } + public readonly onTitleChange = this._onTitleChange.event; private _onColor = new EventEmitter(); - public get onColor(): IEvent { return this._onColor.event; } + public readonly onColor = this._onColor.event; private _parseStack: IParseStack = { paused: false, diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index f940bb8f..b2223b03 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -20,7 +20,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _activeBuffer!: Buffer; private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); - public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; } + public readonly onBufferActivate = this._onBufferActivate.event; /** * Create a new BufferSet for the given terminal. diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 72c4085c..9737b389 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -16,7 +16,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } + public readonly onDispose = this._onDispose.event; constructor( public line: number diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 4f316f24..9f816d6c 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -41,8 +41,9 @@ export class WriteBuffer { private _isSyncWriting = false; private _syncCalls = 0; private _didUserInput = false; - public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } + private _onWriteParsed = new EventEmitter(); + public readonly onWriteParsed = this._onWriteParsed.event; constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index d86f6bf5..f331a0b7 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -11,8 +11,9 @@ import { ICoreTerminal } from 'common/Types'; export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; + private _onBufferChange = new EventEmitter(); - public get onBufferChange(): IEvent { return this._onBufferChange.event; } + public readonly onBufferChange = this._onBufferChange.event; constructor(private _core: ICoreTerminal) { this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index e3b7dcd8..49f31147 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -23,9 +23,9 @@ export class BufferService extends Disposable implements IBufferService { public isUserScrolling: boolean = false; private _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + public readonly onResize = this._onResize.event; private _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } + public readonly onScroll = this._onScroll.event; public get buffer(): IBuffer { return this.buffers.active; } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 54e991f8..465a4968 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -170,9 +170,11 @@ export class CoreMouseService implements ICoreMouseService { private _encodings: { [name: string]: CoreMouseEncoding } = {}; private _activeProtocol: string = ''; private _activeEncoding: string = ''; - private _onProtocolChange = new EventEmitter(); private _lastEvent: ICoreMouseEvent | null = null; + private _onProtocolChange = new EventEmitter(); + public readonly onProtocolChange = this._onProtocolChange.event; + constructor( @IBufferService private readonly _bufferService: IBufferService, @ICoreService private readonly _coreService: ICoreService @@ -225,13 +227,6 @@ export class CoreMouseService implements ICoreMouseService { this._lastEvent = null; } - /** - * Event to announce changes in mouse tracking. - */ - public get onProtocolChange(): IEvent { - return this._onProtocolChange.event; - } - /** * Triggers a mouse event to be sent. * diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 20a34603..a144eb1d 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -35,11 +35,11 @@ export class CoreService extends Disposable implements ICoreService { private _scrollToBottom: (() => void) | undefined; private _onData = this.register(new EventEmitter()); - public get onData(): IEvent { return this._onData.event; } + public readonly onData = this._onData.event; private _onUserInput = this.register(new EventEmitter()); - public get onUserInput(): IEvent { return this._onUserInput.event; } + public readonly onUserInput = this._onUserInput.event; private _onBinary = this.register(new EventEmitter()); - public get onBinary(): IEvent { return this._onBinary.event; } + public readonly onBinary = this._onBinary.event; constructor( // TODO: Move this into a service diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index e5d115a1..d0f9b991 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -28,9 +28,9 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); private _onDecorationRegistered = this.register(new EventEmitter()); - public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } + public readonly onDecorationRegistered = this._onDecorationRegistered.event; private _onDecorationRemoved = this.register(new EventEmitter()); - public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } + public readonly onDecorationRemoved = this._onDecorationRemoved.event; public get decorations(): IterableIterator { return this._decorations.values(); } @@ -99,7 +99,7 @@ class Decoration extends Disposable implements IInternalDecoration { public readonly onRenderEmitter = this.register(new EventEmitter()); public readonly onRender = this.onRenderEmitter.event; - private _onDispose = this.register(new EventEmitter()); + private readonly _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; private _cachedBg: IColor | undefined | null = null; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index c7e8d294..d6662c31 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -58,7 +58,7 @@ export class OptionsService implements IOptionsService { public options: Required; private _onOptionChange = new EventEmitter(); - public get onOptionChange(): IEvent { return this._onOptionChange.event; } + public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { // set the default value of each option diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index e96b7579..b5d19623 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -13,8 +13,9 @@ export class UnicodeService implements IUnicodeService { private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null); private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; + private _onChange = new EventEmitter(); - public get onChange(): IEvent { return this._onChange.event; } + public readonly onChange = this._onChange.event; constructor() { const defaultProvider = new UnicodeV6(); diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 1cad0ee2..1b1162e5 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -33,16 +33,15 @@ export class Terminal extends CoreTerminal { public get options(): Required { return this.optionsService.options; } private _onBell = new EventEmitter(); - public get onBell(): IEvent { return this._onBell.event; } + public readonly onBell = this._onBell.event; private _onCursorMove = new EventEmitter(); - public get onCursorMove(): IEvent { return this._onCursorMove.event; } + public readonly onCursorMove = this._onCursorMove.event; private _onTitleChange = new EventEmitter(); - public get onTitleChange(): IEvent { return this._onTitleChange.event; } - + public readonly onTitleChange = this._onTitleChange.event; private _onA11yCharEmitter = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } + public readonly onA11yChar = this._onA11yCharEmitter.event; private _onA11yTabEmitter = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } + public readonly onA11yTab = this._onA11yTabEmitter.event; /** * Creates a new `Terminal` object. From 1a22c44f2b7ca10d23755c86b78d3361fa2570be Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:02:32 -0700 Subject: [PATCH 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 68475829..fd9629c0 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -27,7 +27,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - private _onRequestRedraw = new EventEmitter(); + private readonly _onRequestRedraw = new EventEmitter(); public readonly onRequestRedraw = this._onRequestRedraw.event; constructor( diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index a96e7c64..45858e8c 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -15,9 +15,9 @@ export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = new EventEmitter(); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private _onContextLoss = new EventEmitter(); + private readonly _onContextLoss = new EventEmitter(); public readonly onContextLoss = this._onContextLoss.event; constructor( diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 74488c6c..0fc634a7 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -55,11 +55,11 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - private _onChangeTextureAtlas = new EventEmitter(); + private readonly _onChangeTextureAtlas = new EventEmitter(); public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private _onRequestRedraw = new EventEmitter(); + private readonly _onRequestRedraw = new EventEmitter(); public readonly onRequestRedraw = this._onRequestRedraw.event; - private _onContextLoss = new EventEmitter(); + private readonly _onContextLoss = new EventEmitter(); public readonly onContextLoss = this._onContextLoss.event; constructor( diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 6bf9fe23..cf5b9dd2 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -26,9 +26,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeProviderReplies: Map | undefined; private _activeLine: number = -1; - private _onShowLinkUnderline = this.register(new EventEmitter()); + private readonly _onShowLinkUnderline = this.register(new EventEmitter()); public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; - private _onHideLinkUnderline = this.register(new EventEmitter()); + private readonly _onHideLinkUnderline = this.register(new EventEmitter()); public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; constructor( diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index e03be89c..9c10fc16 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -122,26 +122,26 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); + private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); public readonly onKey = this._onKey.event; - private _onRender = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); public readonly onRender = this._onRender.event; - private _onSelectionChange = new EventEmitter(); + private readonly _onSelectionChange = new EventEmitter(); public readonly onSelectionChange = this._onSelectionChange.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onBell = new EventEmitter(); + private readonly _onBell = new EventEmitter(); public readonly onBell = this._onBell.event; - private _onFocus = new EventEmitter(); + private readonly _onFocus = new EventEmitter(); public readonly onFocus = this._onFocus.event; - private _onBlur = new EventEmitter(); + private readonly _onBlur = new EventEmitter(); public readonly onBlur = this._onBlur.event; - private _onA11yCharEmitter = new EventEmitter(); + private readonly _onA11yCharEmitter = new EventEmitter(); public readonly onA11yChar = this._onA11yCharEmitter.event; - private _onA11yTabEmitter = new EventEmitter(); + private readonly _onA11yTabEmitter = new EventEmitter(); public readonly onA11yTab = this._onA11yTabEmitter.event; /** diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index d5cc37c8..7062deec 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -16,7 +16,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - private _onCharSizeChange = new EventEmitter(); + private readonly _onCharSizeChange = new EventEmitter(); public readonly onCharSizeChange = this._onCharSizeChange.event; constructor( diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 69213429..66dbb7aa 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -39,13 +39,13 @@ export class RenderService extends Disposable implements IRenderService { columnSelectMode: false }; - private _onDimensionsChange = new EventEmitter(); + private readonly _onDimensionsChange = new EventEmitter(); public readonly onDimensionsChange = this._onDimensionsChange.event; - private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; - private _onRender = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); public readonly onRender = this._onRender.event; - private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); + private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); public readonly onRefreshRequest = this._onRefreshRequest.event; public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 29c8e664..3780c7ee 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -111,13 +111,13 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionStart: [number, number] | undefined = undefined; private _oldSelectionEnd: [number, number] | undefined = undefined; - private _onLinuxMouseSelection = this.register(new EventEmitter()); + private readonly _onLinuxMouseSelection = this.register(new EventEmitter()); public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; - private _onRedrawRequest = this.register(new EventEmitter()); + private readonly _onRedrawRequest = this.register(new EventEmitter()); public readonly onRequestRedraw = this._onRedrawRequest.event; - private _onSelectionChange = this.register(new EventEmitter()); + private readonly _onSelectionChange = this.register(new EventEmitter()); public readonly onSelectionChange = this._onSelectionChange.event; - private _onRequestScrollLines = this.register(new EventEmitter()); + private readonly _onRequestScrollLines = this.register(new EventEmitter()); public readonly onRequestScrollLines = this._onRequestScrollLines.event; constructor( diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index c0c6bb5b..599db1ab 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -25,11 +25,11 @@ export class CircularList implements ICircularList { private _startIndex: number; private _length: number; - public onDeleteEmitter = new EventEmitter(); + public readonly onDeleteEmitter = new EventEmitter(); public readonly onDelete = this.onDeleteEmitter.event; - public onInsertEmitter = new EventEmitter(); + public readonly onInsertEmitter = new EventEmitter(); public readonly onInsert = this.onInsertEmitter.event; - public onTrimEmitter = new EventEmitter(); + public readonly onTrimEmitter = new EventEmitter(); public readonly onTrim = this.onTrimEmitter.event; constructor( diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index da70f84a..3a17c567 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -61,15 +61,15 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - private _onBinary = new EventEmitter(); + private readonly _onBinary = new EventEmitter(); public readonly onBinary = this._onBinary.event; - private _onData = new EventEmitter(); + private readonly _onData = new EventEmitter(); public readonly onData = this._onData.event; protected _onLineFeed = new EventEmitter(); public readonly onLineFeed = this._onLineFeed.event; - private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); public readonly onResize = this._onResize.event; - protected _onWriteParsed = new EventEmitter(); + protected readonly _onWriteParsed = new EventEmitter(); public readonly onWriteParsed = this._onWriteParsed.event; /** diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index bcdb2065..0ace846a 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -129,32 +129,32 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; - private _onRequestBell = new EventEmitter(); + private readonly _onRequestBell = new EventEmitter(); public readonly onRequestBell = this._onRequestBell.event; - private _onRequestRefreshRows = new EventEmitter(); + private readonly _onRequestRefreshRows = new EventEmitter(); public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; - private _onRequestReset = new EventEmitter(); + private readonly _onRequestReset = new EventEmitter(); public readonly onRequestReset = this._onRequestReset.event; - private _onRequestSendFocus = new EventEmitter(); + private readonly _onRequestSendFocus = new EventEmitter(); public readonly onRequestSendFocus = this._onRequestSendFocus.event; - private _onRequestSyncScrollBar = new EventEmitter(); + private readonly _onRequestSyncScrollBar = new EventEmitter(); public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; - private _onRequestWindowsOptionsReport = new EventEmitter(); + private readonly _onRequestWindowsOptionsReport = new EventEmitter(); public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; - private _onA11yChar = new EventEmitter(); + private readonly _onA11yChar = new EventEmitter(); public readonly onA11yChar = this._onA11yChar.event; - private _onA11yTab = new EventEmitter(); + private readonly _onA11yTab = new EventEmitter(); public readonly onA11yTab = this._onA11yTab.event; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onLineFeed = new EventEmitter(); + private readonly _onLineFeed = new EventEmitter(); public readonly onLineFeed = this._onLineFeed.event; - private _onScroll = new EventEmitter(); + private readonly _onScroll = new EventEmitter(); public readonly onScroll = this._onScroll.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onColor = new EventEmitter(); + private readonly _onColor = new EventEmitter(); public readonly onColor = this._onColor.event; private _parseStack: IParseStack = { diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index b2223b03..46fcb097 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -19,7 +19,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _alt!: Buffer; private _activeBuffer!: Buffer; - private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); + private readonly _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); public readonly onBufferActivate = this._onBufferActivate.event; /** diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 9737b389..56d64a72 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -15,7 +15,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } - private _onDispose = new EventEmitter(); + private readonly _onDispose = new EventEmitter(); public readonly onDispose = this._onDispose.event; constructor( diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 9f816d6c..bf72ae21 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -42,7 +42,7 @@ export class WriteBuffer { private _syncCalls = 0; private _didUserInput = false; - private _onWriteParsed = new EventEmitter(); + private readonly _onWriteParsed = new EventEmitter(); public readonly onWriteParsed = this._onWriteParsed.event; constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index f331a0b7..033f5955 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -12,7 +12,7 @@ export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; - private _onBufferChange = new EventEmitter(); + private readonly _onBufferChange = new EventEmitter(); public readonly onBufferChange = this._onBufferChange.event; constructor(private _core: ICoreTerminal) { diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 49f31147..1bc93041 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,7 +6,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types'; @@ -22,9 +22,9 @@ export class BufferService extends Disposable implements IBufferService { /** Whether the user is scrolling (locks the scroll position) */ public isUserScrolling: boolean = false; - private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); public readonly onResize = this._onResize.event; - private _onScroll = new EventEmitter(); + private readonly _onScroll = new EventEmitter(); public readonly onScroll = this._onScroll.event; public get buffer(): IBuffer { return this.buffers.active; } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 465a4968..8c2a24de 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -172,7 +172,7 @@ export class CoreMouseService implements ICoreMouseService { private _activeEncoding: string = ''; private _lastEvent: ICoreMouseEvent | null = null; - private _onProtocolChange = new EventEmitter(); + private readonly _onProtocolChange = new EventEmitter(); public readonly onProtocolChange = this._onProtocolChange.event; constructor( diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index a144eb1d..9282197b 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -34,11 +34,11 @@ export class CoreService extends Disposable implements ICoreService { // Circular dependency, this must be unset or memory will leak after Terminal.dispose private _scrollToBottom: (() => void) | undefined; - private _onData = this.register(new EventEmitter()); + private readonly _onData = this.register(new EventEmitter()); public readonly onData = this._onData.event; - private _onUserInput = this.register(new EventEmitter()); + private readonly _onUserInput = this.register(new EventEmitter()); public readonly onUserInput = this._onUserInput.event; - private _onBinary = this.register(new EventEmitter()); + private readonly _onBinary = this.register(new EventEmitter()); public readonly onBinary = this._onBinary.event; constructor( diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index d0f9b991..9fb611c6 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -27,9 +27,9 @@ export class DecorationService extends Disposable implements IDecorationService */ private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); - private _onDecorationRegistered = this.register(new EventEmitter()); + private readonly _onDecorationRegistered = this.register(new EventEmitter()); public readonly onDecorationRegistered = this._onDecorationRegistered.event; - private _onDecorationRemoved = this.register(new EventEmitter()); + private readonly _onDecorationRemoved = this.register(new EventEmitter()); public readonly onDecorationRemoved = this._onDecorationRemoved.event; public get decorations(): IterableIterator { return this._decorations.values(); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d6662c31..33aa3ee2 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -57,7 +57,7 @@ export class OptionsService implements IOptionsService { public readonly rawOptions: Required; public options: Required; - private _onOptionChange = new EventEmitter(); + private readonly _onOptionChange = new EventEmitter(); public readonly onOptionChange = this._onOptionChange.event; constructor(options: Partial) { diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index b5d19623..239f4d62 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -14,7 +14,7 @@ export class UnicodeService implements IUnicodeService { private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - private _onChange = new EventEmitter(); + private readonly _onChange = new EventEmitter(); public readonly onChange = this._onChange.event; constructor() { diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 1b1162e5..639988eb 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -32,15 +32,15 @@ export class Terminal extends CoreTerminal { // TODO: We should remove options once components adopt optionsService public get options(): Required { return this.optionsService.options; } - private _onBell = new EventEmitter(); + private readonly _onBell = new EventEmitter(); public readonly onBell = this._onBell.event; - private _onCursorMove = new EventEmitter(); + private readonly _onCursorMove = new EventEmitter(); public readonly onCursorMove = this._onCursorMove.event; - private _onTitleChange = new EventEmitter(); + private readonly _onTitleChange = new EventEmitter(); public readonly onTitleChange = this._onTitleChange.event; - private _onA11yCharEmitter = new EventEmitter(); + private readonly _onA11yCharEmitter = new EventEmitter(); public readonly onA11yChar = this._onA11yCharEmitter.event; - private _onA11yTabEmitter = new EventEmitter(); + private readonly _onA11yTabEmitter = new EventEmitter(); public readonly onA11yTab = this._onA11yTabEmitter.event; /** From ed15a8e17031b8317b5e7dfcae0cdada20523391 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:45:40 -0700 Subject: [PATCH 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 fd9629c0..726ad080 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -14,7 +14,7 @@ import { IColorSet, ILinkifier2 } from 'browser/Types'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from './atlas/CharAtlasCache'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; let nextRendererId = 1; @@ -27,8 +27,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - private readonly _onRequestRedraw = new EventEmitter(); - public readonly onRequestRedraw = this._onRequestRedraw.event; + public readonly onRequestRedraw = initEvent(); constructor( private _colors: IColorSet, @@ -48,7 +47,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), - new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) + new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this.onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) ]; this.dimensions = { scaledCharWidth: 0, @@ -128,7 +127,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode)); // Selection foreground requires a full re-render if (this._colors.selectionForeground) { - this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } @@ -201,6 +200,6 @@ export class CanvasRenderer extends Disposable implements IRenderer { } private _requestRedrawViewport(): void { - this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); } } diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 689899ef..d14e1fe7 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -4,7 +4,7 @@ */ import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm'; -import { EventEmitter } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; export interface ISearchOptions { regex?: boolean; @@ -72,8 +72,7 @@ export class SearchAddon implements ITerminalAddon { private _resultIndex: number | undefined; - private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>(); - public readonly onDidChangeResults = this._onDidChangeResults.event; + public readonly onDidChangeResults = initEvent<{ resultIndex: number, resultCount: number } | undefined>(); public activate(terminal: Terminal): void { this._terminal = terminal; @@ -89,7 +88,7 @@ export class SearchAddon implements ITerminalAddon { this._highlightTimeout = setTimeout(() => { this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true }); this._resultIndex = this._searchResults ? this._searchResults.size - 1 : -1; - this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 }); + this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 }); }, 200); } } @@ -325,9 +324,9 @@ export class SearchAddon implements ITerminalAddon { private _fireResults(term: string, found: boolean, searchOptions?: ISearchOptions): boolean { if (searchOptions?.decorations) { if (this._resultIndex !== undefined && this._searchResults?.size !== undefined) { - this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); + this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); } else { - this._onDidChangeResults.fire(undefined); + this.onDidChangeResults.fire(undefined); } } this._cachedSearchTerm = term; diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 45858e8c..548be30c 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -7,7 +7,7 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; -import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent, initEvent } from 'common/EventEmitter'; import { isSafari } from 'common/Platform'; import { ICoreService, IDecorationService } from 'common/services/Services'; @@ -15,10 +15,8 @@ export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: WebglRenderer; - private readonly _onChangeTextureAtlas = new EventEmitter(); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onContextLoss = new EventEmitter(); - public readonly onContextLoss = this._onContextLoss.event; + public readonly onChangeTextureAtlas = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _preserveDrawingBuffer?: boolean @@ -39,8 +37,8 @@ export class WebglAddon implements ITerminalAddon { const decorationService: IDecorationService = (terminal as any)._core._decorationService; const colors: IColorSet = (terminal as any)._core._colorManager.colors; this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer); - forwardEvent(this._renderer.onContextLoss, this._onContextLoss); - forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); + forwardEvent(this._renderer.onContextLoss, this.onContextLoss); + forwardEvent(this._renderer.onChangeTextureAtlas, this.onChangeTextureAtlas); renderService.setRenderer(this._renderer); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 58fbb6ed..7a487242 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -18,7 +18,7 @@ import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, initEvent } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; @@ -53,12 +53,9 @@ export class WebglRenderer extends Disposable implements IRenderer { private _isAttached: boolean; private _contextRestorationTimeout: number | undefined; - private readonly _onChangeTextureAtlas = new EventEmitter(); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onRequestRedraw = new EventEmitter(); - public readonly onRequestRedraw = this._onRequestRedraw.event; - private readonly _onContextLoss = new EventEmitter(); - public readonly onContextLoss = this._onContextLoss.event; + public readonly onChangeTextureAtlas = initEvent(); + public readonly onRequestRedraw = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _terminal: Terminal, @@ -75,7 +72,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers = [ new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core, this._coreBrowserService), - new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._onRequestRedraw, this._coreBrowserService, coreService) + new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this.onRequestRedraw, this._coreBrowserService, coreService) ]; this.dimensions = { scaledCharWidth: 0, @@ -115,7 +112,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._contextRestorationTimeout = setTimeout(() => { this._contextRestorationTimeout = undefined; console.warn('webgl context not restored; firing onContextLoss'); - this._onContextLoss.fire(e); + this.onContextLoss.fire(e); }, 3000 /* ms */); })); this.register(addDisposableDomListener(this._canvas, 'webglcontextrestored', (e) => { @@ -283,7 +280,7 @@ export class WebglRenderer extends Disposable implements IRenderer { throw new Error('The webgl renderer only works with the webgl char atlas'); } if (this._charAtlas !== atlas) { - this._onChangeTextureAtlas.fire(atlas.cacheCanvas); + this.onChangeTextureAtlas.fire(atlas.cacheCanvas); } this._charAtlas = atlas; this._charAtlas.warmUp(); @@ -676,7 +673,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _requestRedrawViewport(): void { - this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + this.onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } } diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index cf5b9dd2..844b64f8 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -7,7 +7,7 @@ import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent import { IDisposable } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -26,10 +26,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _activeProviderReplies: Map | undefined; private _activeLine: number = -1; - private readonly _onShowLinkUnderline = this.register(new EventEmitter()); - public readonly onShowLinkUnderline = this._onShowLinkUnderline.event; - private readonly _onHideLinkUnderline = this.register(new EventEmitter()); - public readonly onHideLinkUnderline = this._onHideLinkUnderline.event; + public readonly onShowLinkUnderline = this.register(initEvent()); + public readonly onHideLinkUnderline = this.register(initEvent()); constructor( @IBufferService private readonly _bufferService: IBufferService @@ -343,7 +341,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { const range = link.range; const scrollOffset = this._bufferService.buffer.ydisp; const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined); - const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline; + const emitter = showEvent ? this.onShowLinkUnderline : this.onHideLinkUnderline; emitter.fire(event); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9c10fc16..90c1b134 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -37,7 +37,7 @@ import { ITheme, IMarker, IDisposable, ILinkProvider, IDecorationOptions, IDecor import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; @@ -122,27 +122,16 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); - public readonly onKey = this._onKey.event; - private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); - public readonly onRender = this._onRender.event; - private readonly _onSelectionChange = new EventEmitter(); - public readonly onSelectionChange = this._onSelectionChange.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onBell = new EventEmitter(); - public readonly onBell = this._onBell.event; - - private readonly _onFocus = new EventEmitter(); - public readonly onFocus = this._onFocus.event; - private readonly _onBlur = new EventEmitter(); - public readonly onBlur = this._onBlur.event; - private readonly _onA11yCharEmitter = new EventEmitter(); - public readonly onA11yChar = this._onA11yCharEmitter.event; - private readonly _onA11yTabEmitter = new EventEmitter(); - public readonly onA11yTab = this._onA11yTabEmitter.event; + public readonly onCursorMove = initEvent(); + public readonly onKey = initEvent<{ key: string, domEvent: KeyboardEvent }>(); + public readonly onRender = initEvent<{ start: number, end: number }>(); + public readonly onSelectionChange = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onBell = initEvent(); + public readonly onFocus = initEvent(); + public readonly onBlur = initEvent(); + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); /** * Creates a new `Terminal` object. @@ -169,16 +158,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this._instantiationService.setService(IDecorationService, this._decorationService); // Setup InputHandler listeners - this.register(this._inputHandler.onRequestBell(() => this._onBell.fire())); + this.register(this._inputHandler.onRequestBell(() => this.onBell.fire())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event))); - this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); - this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); - this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); - this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); + this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab)); // Setup listeners this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows))); @@ -326,7 +315,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.updateCursorStyle(ev); this.element!.classList.add('focus'); this._showCursor(); - this._onFocus.fire(); + this.onFocus.fire(); } /** @@ -349,7 +338,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.coreService.triggerDataEvent(C0.ESC + '[O'); } this.element!.classList.remove('focus'); - this._onBlur.fire(); + this.onBlur.fire(); } private _syncTextArea(): void { @@ -512,7 +501,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const renderer = this._createRenderer(); this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement)); this._instantiationService.setService(IRenderService, this._renderService); - this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); + this.register(this._renderService.onRenderedViewportChange(e => this.onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); this._compositionView = document.createElement('div'); @@ -552,7 +541,7 @@ export class Terminal extends CoreTerminal implements ITerminal { )); this._instantiationService.setService(ISelectionService, this._selectionService); this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent))); - this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire())); + this.register(this._selectionService.onSelectionChange(() => this.onSelectionChange.fire())); this.register(this._selectionService.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode))); this.register(this._selectionService.onLinuxMouseSelection(text => { // If there's a new selection, put it into the textarea, focus and select it @@ -1101,7 +1090,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.textarea!.value = ''; } - this._onKey.fire({ key: result.key, domEvent: event }); + this.onKey.fire({ key: result.key, domEvent: event }); this._showCursor(); this.coreService.triggerDataEvent(result.key, true); @@ -1184,7 +1173,7 @@ export class Terminal extends CoreTerminal implements ITerminal { key = String.fromCharCode(key); - this._onKey.fire({ key, domEvent: ev }); + this.onKey.fire({ key, domEvent: ev }); this._showCursor(); this.coreService.triggerDataEvent(key, true); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 0b5e00c1..95920375 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; @@ -352,7 +352,7 @@ export class MockCoreBrowserService implements ICoreBrowserService { export class MockCharSizeService implements ICharSizeService { public serviceBrand: undefined; public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - public onCharSizeChange: IEvent = new EventEmitter().event; + public onCharSizeChange: IEvent = initEvent(); constructor(public width: number, public height: number) {} public measure(): void {} } @@ -370,10 +370,10 @@ export class MockMouseService implements IMouseService { export class MockRenderService implements IRenderService { public serviceBrand: undefined; - public onDimensionsChange: IEvent = new EventEmitter().event; - public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; - public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; - public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event; + public onDimensionsChange: IEvent = initEvent(); + public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>(); + public onRender: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>(); + public onRefreshRequest: IEvent<{ start: number, end: number}, void> = initEvent<{ start: number, end: number }>(); public dimensions: IRenderDimensions = { scaledCharWidth: 0, scaledCharHeight: 0, @@ -457,10 +457,10 @@ export class MockSelectionService implements ISelectionService { public hasSelection: boolean = false; public selectionStart: [number, number] | undefined; public selectionEnd: [number, number] | undefined; - public onLinuxMouseSelection = new EventEmitter().event; - public onRequestRedraw = new EventEmitter().event; - public onRequestScrollLines = new EventEmitter().event; - public onSelectionChange = new EventEmitter().event; + public onLinuxMouseSelection = initEvent(); + public onRequestRedraw = initEvent(); + public onRequestScrollLines = initEvent(); + public onSelectionChange = initEvent(); public disable(): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 7fcc5ea9..23ba174f 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -95,7 +95,7 @@ export class BufferDecorationRenderer extends Disposable { // outside of viewport if (decoration.element) { decoration.element.style.display = 'none'; - decoration.onRenderEmitter.fire(decoration.element); + decoration.onRender.fire(decoration.element); } } else { let element = this._decorationElements.get(decoration); @@ -108,7 +108,7 @@ export class BufferDecorationRenderer extends Disposable { } element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`; element.style.display = this._altBufferIsActive ? 'none' : 'block'; - decoration.onRenderEmitter.fire(element); + decoration.onRender.fire(element); } } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 223e2470..39de7b2c 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { color } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; - public readonly onRequestRedraw = new EventEmitter().event; + public readonly onRequestRedraw = initEvent(); constructor( private _colors: IColorSet, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 7062deec..583006d6 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -4,7 +4,7 @@ */ import { IOptionsService } from 'common/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; export class CharSizeService implements ICharSizeService { @@ -16,8 +16,7 @@ export class CharSizeService implements ICharSizeService { public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } - private readonly _onCharSizeChange = new EventEmitter(); - public readonly onCharSizeChange = this._onCharSizeChange.event; + public readonly onCharSizeChange = initEvent(); constructor( document: Document, @@ -32,7 +31,7 @@ export class CharSizeService implements ICharSizeService { if (result.width !== this.width || result.height !== this.height) { this.width = result.width; this.height = result.height; - this._onCharSizeChange.fire(); + this.onCharSizeChange.fire(); } } } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 66dbb7aa..dfb4df80 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -5,7 +5,7 @@ import { IRenderer, IRenderDimensions } from 'browser/renderer/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -39,14 +39,10 @@ export class RenderService extends Disposable implements IRenderService { columnSelectMode: false }; - private readonly _onDimensionsChange = new EventEmitter(); - public readonly onDimensionsChange = this._onDimensionsChange.event; - private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); - public readonly onRenderedViewportChange = this._onRenderedViewportChange.event; - private readonly _onRender = new EventEmitter<{ start: number, end: number }>(); - public readonly onRender = this._onRender.event; - private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); - public readonly onRefreshRequest = this._onRefreshRequest.event; + public readonly onDimensionsChange = initEvent(); + public readonly onRenderedViewportChange = initEvent<{ start: number, end: number }>(); + public readonly onRender = initEvent<{ start: number, end: number }>(); + public readonly onRefreshRequest = initEvent<{ start: number, end: number }>(); public get dimensions(): IRenderDimensions { return this._renderer.dimensions; } @@ -135,9 +131,9 @@ export class RenderService extends Disposable implements IRenderService { // Fire render event only if it was not a redraw if (!this._isNextRenderRedrawOnly) { - this._onRenderedViewportChange.fire({ start, end }); + this.onRenderedViewportChange.fire({ start, end }); } - this._onRender.fire({ start, end }); + this.onRender.fire({ start, end }); this._isNextRenderRedrawOnly = true; } @@ -157,7 +153,7 @@ export class RenderService extends Disposable implements IRenderService { if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) { return; } - this._onDimensionsChange.fire(this._renderer.dimensions); + this.onDimensionsChange.fire(this._renderer.dimensions); } public dispose(): void { diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 3780c7ee..5da197b1 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -9,7 +9,7 @@ import { IBufferLine, IDisposable } from 'common/Types'; import * as Browser from 'common/Platform'; import { SelectionModel } from 'browser/selection/SelectionModel'; import { CellData } from 'common/buffer/CellData'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IMouseService, ISelectionService, IRenderService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferRange, ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; @@ -111,14 +111,10 @@ export class SelectionService extends Disposable implements ISelectionService { private _oldSelectionStart: [number, number] | undefined = undefined; private _oldSelectionEnd: [number, number] | undefined = undefined; - private readonly _onLinuxMouseSelection = this.register(new EventEmitter()); - public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event; - private readonly _onRedrawRequest = this.register(new EventEmitter()); - public readonly onRequestRedraw = this._onRedrawRequest.event; - private readonly _onSelectionChange = this.register(new EventEmitter()); - public readonly onSelectionChange = this._onSelectionChange.event; - private readonly _onRequestScrollLines = this.register(new EventEmitter()); - public readonly onRequestScrollLines = this._onRequestScrollLines.event; + public readonly onLinuxMouseSelection = this.register(initEvent()); + public readonly onRequestRedraw = this.register(initEvent()); + public readonly onSelectionChange = this.register(initEvent()); + public readonly onRequestScrollLines = this.register(initEvent()); constructor( private readonly _element: HTMLElement, @@ -260,7 +256,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.clearSelection(); this._removeMouseDownListeners(); this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } /** @@ -279,7 +275,7 @@ export class SelectionService extends Disposable implements ISelectionService { if (Browser.isLinux && isLinuxMouseSelection) { const selectionText = this.selectionText; if (selectionText.length) { - this._onLinuxMouseSelection.fire(this.selectionText); + this.onLinuxMouseSelection.fire(this.selectionText); } } } @@ -290,7 +286,7 @@ export class SelectionService extends Disposable implements ISelectionService { */ private _refresh(): void { this._refreshAnimationFrame = undefined; - this._onRedrawRequest.fire({ + this.onRequestRedraw.fire({ start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd, columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN @@ -358,7 +354,7 @@ export class SelectionService extends Disposable implements ISelectionService { public selectAll(): void { this._model.isSelectAllActive = true; this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } public selectLines(start: number, end: number): void { @@ -368,7 +364,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.selectionStart = [0, start]; this._model.selectionEnd = [this._bufferService.cols, end]; this.refresh(); - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } /** @@ -665,7 +661,7 @@ export class SelectionService extends Disposable implements ISelectionService { return; } if (this._dragScrollAmount) { - this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false }); + this.onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false }); // Re-evaluate selection // If the cursor was above or below the viewport, make sure it's at the // start or end of the viewport respectively. This should only happen when @@ -743,7 +739,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._oldSelectionStart = start; this._oldSelectionEnd = end; this._oldHasSelection = hasSelection; - this._onSelectionChange.fire(); + this.onSelectionChange.fire(); } private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void { diff --git a/src/common/CircularList.ts b/src/common/CircularList.ts index 599db1ab..53743449 100644 --- a/src/common/CircularList.ts +++ b/src/common/CircularList.ts @@ -4,7 +4,7 @@ */ import { ICircularList } from 'common/Types'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; export interface IInsertEvent { index: number; @@ -25,12 +25,9 @@ export class CircularList implements ICircularList { private _startIndex: number; private _length: number; - public readonly onDeleteEmitter = new EventEmitter(); - public readonly onDelete = this.onDeleteEmitter.event; - public readonly onInsertEmitter = new EventEmitter(); - public readonly onInsert = this.onInsertEmitter.event; - public readonly onTrimEmitter = new EventEmitter(); - public readonly onTrim = this.onTrimEmitter.event; + public readonly onDelete = initEvent(); + public readonly onInsert = initEvent(); + public readonly onTrim = initEvent(); constructor( private _maxLength: number @@ -107,7 +104,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(this._length)] = value; if (this._length === this._maxLength) { this._startIndex = ++this._startIndex % this._maxLength; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); } else { this._length++; } @@ -123,7 +120,7 @@ export class CircularList implements ICircularList { throw new Error('Can only recycle when the buffer is full'); } this._startIndex = ++this._startIndex % this._maxLength; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); return this._array[this._getCyclicIndex(this._length - 1)]!; } @@ -158,7 +155,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)]; } this._length -= deleteCount; - this.onDeleteEmitter.fire({ index: start, amount: deleteCount }); + this.onDelete.fire({ index: start, amount: deleteCount }); } // Add items @@ -169,7 +166,7 @@ export class CircularList implements ICircularList { this._array[this._getCyclicIndex(start + i)] = items[i]; } if (items.length) { - this.onInsertEmitter.fire({ index: start, amount: items.length }); + this.onInsert.fire({ index: start, amount: items.length }); } // Adjust length as needed @@ -177,7 +174,7 @@ export class CircularList implements ICircularList { const countToTrim = (this._length + items.length) - this._maxLength; this._startIndex += countToTrim; this._length = this._maxLength; - this.onTrimEmitter.fire(countToTrim); + this.onTrim.fire(countToTrim); } else { this._length += items.length; } @@ -193,7 +190,7 @@ export class CircularList implements ICircularList { } this._startIndex += count; this._length -= count; - this.onTrimEmitter.fire(count); + this.onTrim.fire(count); } public shiftElements(start: number, count: number, offset: number): void { @@ -217,7 +214,7 @@ export class CircularList implements ICircularList { while (this._length > this._maxLength) { this._length--; this._startIndex++; - this.onTrimEmitter.fire(1); + this.onTrim.fire(1); } } } else { diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 2ad6f735..dd6953bb 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -29,7 +29,7 @@ import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/Buffe import { OptionsService } from 'common/services/OptionsService'; import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent, ScrollSource } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; -import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { UnicodeService } from 'common/services/UnicodeService'; import { CharsetService } from 'common/services/CharsetService'; @@ -59,16 +59,11 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; - private readonly _onBinary = new EventEmitter(); - public readonly onBinary = this._onBinary.event; - private readonly _onData = new EventEmitter(); - public readonly onData = this._onData.event; - protected _onLineFeed = new EventEmitter(); - public readonly onLineFeed = this._onLineFeed.event; - private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public readonly onResize = this._onResize.event; - protected readonly _onWriteParsed = new EventEmitter(); - public readonly onWriteParsed = this._onWriteParsed.event; + public readonly onBinary = initEvent(); + public readonly onData = initEvent(); + public readonly onLineFeed = initEvent(); + public readonly onResize = initEvent<{ cols: number, rows: number }>(); + public readonly onWriteParsed = initEvent(); /** * Internally we track the source of the scroll but this is meaningless outside the library so @@ -122,13 +117,13 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Register input handler and handle/forward events this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService); - this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); + this.register(forwardEvent(this._inputHandler.onLineFeed, this.onLineFeed)); this.register(this._inputHandler); // Setup listeners - this.register(forwardEvent(this._bufferService.onResize, this._onResize)); - this.register(forwardEvent(this.coreService.onData, this._onData)); - this.register(forwardEvent(this.coreService.onBinary, this._onBinary)); + this.register(forwardEvent(this._bufferService.onResize, this.onResize)); + this.register(forwardEvent(this.coreService.onData, this.onData)); + this.register(forwardEvent(this.coreService.onBinary, this.onBinary)); this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput())); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); this.register(this._bufferService.onScroll(event => { @@ -142,7 +137,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup WriteBuffer this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); - this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); + this.register(forwardEvent(this._writeBuffer.onWriteParsed, this.onWriteParsed)); } public dispose(): void { diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 4684809f..c0074bba 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -14,11 +14,13 @@ export interface IEvent { } export interface IEventEmitter { - event: IEvent; fire(arg1: T, arg2: U): void; dispose(): void; } +export interface IEventWithEmitter extends IEventEmitter, IEvent { +} + export class EventEmitter implements IEventEmitter { private _listeners: IListener[] = []; private _event?: IEvent; @@ -64,6 +66,18 @@ export class EventEmitter implements IEventEmitter { } } +export function initEvent(): IEventWithEmitter { + const emitter = new EventEmitter(); + const event = emitter.event; + Object.defineProperty(event, 'fire', { + value: emitter.fire.bind(emitter) + }); + Object.defineProperty(event, 'dispose', { + value: emitter.dispose.bind(emitter) + }); + return event as any; +} + export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable { return from(e => to.fire(e)); } diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 7f0879ef..d4711c1f 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -11,7 +11,7 @@ import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; import { Disposable } from 'common/Lifecycle'; import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; @@ -132,33 +132,20 @@ export class InputHandler extends Disposable implements IInputHandler { private _activeBuffer: IBuffer; - private readonly _onRequestBell = new EventEmitter(); - public readonly onRequestBell = this._onRequestBell.event; - private readonly _onRequestRefreshRows = new EventEmitter(); - public readonly onRequestRefreshRows = this._onRequestRefreshRows.event; - private readonly _onRequestReset = new EventEmitter(); - public readonly onRequestReset = this._onRequestReset.event; - private readonly _onRequestSendFocus = new EventEmitter(); - public readonly onRequestSendFocus = this._onRequestSendFocus.event; - private readonly _onRequestSyncScrollBar = new EventEmitter(); - public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event; - private readonly _onRequestWindowsOptionsReport = new EventEmitter(); - public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event; + public readonly onRequestBell = initEvent(); + public readonly onRequestRefreshRows = initEvent(); + public readonly onRequestReset = initEvent(); + public readonly onRequestSendFocus = initEvent(); + public readonly onRequestSyncScrollBar = initEvent(); + public readonly onRequestWindowsOptionsReport = initEvent(); - private readonly _onA11yChar = new EventEmitter(); - public readonly onA11yChar = this._onA11yChar.event; - private readonly _onA11yTab = new EventEmitter(); - public readonly onA11yTab = this._onA11yTab.event; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onLineFeed = new EventEmitter(); - public readonly onLineFeed = this._onLineFeed.event; - private readonly _onScroll = new EventEmitter(); - public readonly onScroll = this._onScroll.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onColor = new EventEmitter(); - public readonly onColor = this._onColor.event; + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); + public readonly onCursorMove = initEvent(); + public readonly onLineFeed = initEvent(); + public readonly onScroll = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onColor = initEvent(); private _parseStack: IParseStack = { paused: false, @@ -492,11 +479,11 @@ export class InputHandler extends Disposable implements IInputHandler { } if (this._activeBuffer.x !== cursorStartX || this._activeBuffer.y !== cursorStartY) { - this._onCursorMove.fire(); + this.onCursorMove.fire(); } // Refresh any dirty rows accumulated as part of parsing - this._onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); + this.onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end); } public print(data: Uint32Array, start: number, end: number): void { @@ -535,7 +522,7 @@ export class InputHandler extends Disposable implements IInputHandler { } if (screenReaderMode) { - this._onA11yChar.fire(stringFromCodePoint(code)); + this.onA11yChar.fire(stringFromCodePoint(code)); } if (this._currentLinkId !== undefined) { this._oscLinkService.addLineToLink(this._currentLinkId, this._activeBuffer.ybase + this._activeBuffer.y); @@ -687,7 +674,7 @@ export class InputHandler extends Disposable implements IInputHandler { * and `ITerminalOptions.bellSound`. */ public bell(): boolean { - this._onRequestBell.fire(); + this.onRequestBell.fire(); return true; } @@ -719,7 +706,7 @@ export class InputHandler extends Disposable implements IInputHandler { } this._dirtyRowTracker.markDirty(this._activeBuffer.y); - this._onLineFeed.fire(); + this.onLineFeed.fire(); return true; } @@ -808,7 +795,7 @@ export class InputHandler extends Disposable implements IInputHandler { const originalX = this._activeBuffer.x; this._activeBuffer.x = this._activeBuffer.nextStop(); if (this._optionsService.rawOptions.screenReaderMode) { - this._onA11yTab.fire(this._activeBuffer.x - originalX); + this.onA11yTab.fire(this._activeBuffer.x - originalX); } return true; } @@ -1218,7 +1205,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0); this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0); // Force a scroll event to refresh viewport - this._onScroll.fire(0); + this.onScroll.fire(0); } break; } @@ -1849,7 +1836,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(132, this._bufferService.rows); - this._onRequestReset.fire(); + this.onRequestReset.fire(); } break; case 6: @@ -1868,7 +1855,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1890,7 +1877,7 @@ export class InputHandler extends Disposable implements IInputHandler { // focusin: ^[[I // focusout: ^[[O this._coreService.decPrivateModes.sendFocus = true; - this._onRequestSendFocus.fire(); + this.onRequestSendFocus.fire(); break; case 1005: // utf8 ext mode mouse - removed in #2507 this._logService.debug('DECSET 1005 not supported (see #2507)'); @@ -1917,8 +1904,8 @@ export class InputHandler extends Disposable implements IInputHandler { case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); this._coreService.isCursorInitialized = true; - this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._onRequestSyncScrollBar.fire(); + this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1); + this.onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = true; @@ -2087,7 +2074,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ if (this._optionsService.rawOptions.windowOptions.setWinLines) { this._bufferService.resize(80, this._bufferService.rows); - this._onRequestReset.fire(); + this.onRequestReset.fire(); } break; case 6: @@ -2106,7 +2093,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -2145,8 +2132,8 @@ export class InputHandler extends Disposable implements IInputHandler { this.restoreCursor(); } this._coreService.isCursorInitialized = true; - this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._onRequestSyncScrollBar.fire(); + this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1); + this.onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = false; @@ -2645,7 +2632,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public softReset(params: IParams): boolean { this._coreService.isCursorHidden = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); this._activeBuffer.scrollTop = 0; this._activeBuffer.scrollBottom = this._bufferService.rows - 1; this._curAttrData = DEFAULT_ATTR_DATA.clone(); @@ -2765,11 +2752,11 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (second !== 2) { - this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); + this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); } break; case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t - this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); + this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); break; case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t if (this._bufferService) { @@ -2859,7 +2846,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public setTitle(data: string): boolean { this._windowTitle = data; - this._onTitleChange.fire(data); + this.onTitleChange.fire(data); return true; } @@ -2901,7 +2888,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } if (event.length) { - this._onColor.fire(event); + this.onColor.fire(event); } return true; } @@ -2975,11 +2962,11 @@ export class InputHandler extends Disposable implements IInputHandler { for (let i = 0; i < slots.length; ++i, ++offset) { if (offset >= this._specialColors.length) break; if (slots[i] === '?') { - this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); + this.onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]); } else { const color = parseColor(slots[i]); if (color) { - this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); + this.onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]); } } } @@ -3040,7 +3027,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public restoreIndexedColor(data: string): boolean { if (!data) { - this._onColor.fire([{ type: ColorRequestType.RESTORE }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE }]); return true; } const event: IColorEvent = []; @@ -3054,7 +3041,7 @@ export class InputHandler extends Disposable implements IInputHandler { } } if (event.length) { - this._onColor.fire(event); + this.onColor.fire(event); } return true; } @@ -3065,7 +3052,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color." */ public restoreFgColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]); return true; } @@ -3075,7 +3062,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color." */ public restoreBgColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]); return true; } @@ -3085,7 +3072,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color." */ public restoreCursorColor(data: string): boolean { - this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); + this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]); return true; } @@ -3112,7 +3099,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadApplicationMode(): boolean { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); return true; } @@ -3124,7 +3111,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadNumericMode(): boolean { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._onRequestSyncScrollBar.fire(); + this.onRequestSyncScrollBar.fire(); return true; } @@ -3238,7 +3225,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public fullReset(): boolean { this._parser.reset(); - this._onRequestReset.fire(); + this.onRequestReset.fire(); return true; } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index e302d3e4..2ac70a5e 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; @@ -17,8 +17,8 @@ export class MockBufferService implements IBufferService { public serviceBrand: any; public get buffer(): IBuffer { return this.buffers.active; } public buffers: IBufferSet = {} as any; - public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event; - public onScroll: IEvent = new EventEmitter().event; + public onResize: IEvent<{ cols: number, rows: number }> = initEvent<{ cols: number, rows: number }>(); + public onScroll: IEvent = initEvent(); public isUserScrolling: boolean = false; constructor( public cols: number, @@ -60,7 +60,7 @@ export class MockCoreMouseService implements ICoreMouseService { public addProtocol(name: string): void { } public reset(): void { } public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; } - public onProtocolChange: IEvent = new EventEmitter().event; + public onProtocolChange: IEvent = initEvent(); public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } { throw new Error('Method not implemented.'); } @@ -92,9 +92,9 @@ export class MockCoreService implements ICoreService { sendFocus: false, wraparound: true }; - public onData: IEvent = new EventEmitter().event; - public onUserInput: IEvent = new EventEmitter().event; - public onBinary: IEvent = new EventEmitter().event; + public onData: IEvent = initEvent(); + public onUserInput: IEvent = initEvent(); + public onBinary: IEvent = initEvent(); public reset(): void { } public triggerDataEvent(data: string, wasUserInput?: boolean): void { } public triggerBinaryEvent(data: string): void { } @@ -113,7 +113,7 @@ export class MockOptionsService implements IOptionsService { public serviceBrand: any; public readonly rawOptions: Required = clone(DEFAULT_OPTIONS); public options: Required = this.rawOptions; - public onOptionChange: IEvent = new EventEmitter().event; + public onOptionChange: IEvent = initEvent(); constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { @@ -149,7 +149,7 @@ export class MockUnicodeService implements IUnicodeService { } public versions: string[] = []; public activeVersion: string = ''; - public onChange: IEvent = new EventEmitter().event; + public onChange: IEvent = initEvent(); public wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint); public getStringCellWidth(s: string): number { throw new Error('Method not implemented.'); @@ -159,8 +159,8 @@ export class MockUnicodeService implements IUnicodeService { export class MockDecorationService implements IDecorationService { public serviceBrand: any; public get decorations(): IterableIterator { return [].values(); } - public onDecorationRegistered = new EventEmitter().event; - public onDecorationRemoved = new EventEmitter().event; + public onDecorationRegistered = initEvent(); + public onDecorationRemoved = initEvent(); public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } public reset(): void { } public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index d44bb197..6e6e93e7 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -4,7 +4,7 @@ */ import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm'; -import { IEvent, IEventEmitter } from 'common/EventEmitter'; +import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; import { IParams } from 'common/parser/Types'; import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services'; @@ -71,12 +71,9 @@ export interface ICircularList { maxLength: number; isFull: boolean; - onDeleteEmitter: IEventEmitter; - onDelete: IEvent; - onInsertEmitter: IEventEmitter; - onInsert: IEvent; - onTrimEmitter: IEventEmitter; - onTrim: IEvent; + onDelete: IEventWithEmitter; + onInsert: IEventWithEmitter; + onTrim: IEventWithEmitter; get(index: number): T | undefined; set(index: number, value: T): void; diff --git a/src/common/buffer/Buffer.test.ts b/src/common/buffer/Buffer.test.ts index e5ea7f5e..03297ba6 100644 --- a/src/common/buffer/Buffer.test.ts +++ b/src/common/buffer/Buffer.test.ts @@ -1071,7 +1071,7 @@ describe('Buffer', () => { buffer.fillViewportRows(); const marker = buffer.addMarker(buffer.lines.length - 1); assert.equal(marker.line, buffer.lines.length - 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.line, buffer.lines.length - 2); }); it('should dispose of a marker if it is trimmed off the buffer', () => { @@ -1081,7 +1081,7 @@ describe('Buffer', () => { const marker = buffer.addMarker(0); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); }); @@ -1094,7 +1094,7 @@ describe('Buffer', () => { marker.onDispose(() => eventStack.push('disposed')); assert.equal(marker.isDisposed, false); assert.equal(buffer.markers.length, 1); - buffer.lines.onTrimEmitter.fire(1); + buffer.lines.onTrim.fire(1); assert.equal(marker.isDisposed, true); assert.equal(buffer.markers.length, 0); assert.deepEqual(eventStack, ['disposed']); diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index c8b0d1b2..ec3b9fcd 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -463,12 +463,12 @@ export class Buffer implements IBuffer { let insertCountEmitted = 0; for (let i = insertEvents.length - 1; i >= 0; i--) { insertEvents[i].index += insertCountEmitted; - this.lines.onInsertEmitter.fire(insertEvents[i]); + this.lines.onInsert.fire(insertEvents[i]); insertCountEmitted += insertEvents[i].amount; } const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength); if (amountToTrim > 0) { - this.lines.onTrimEmitter.fire(amountToTrim); + this.lines.onTrim.fire(amountToTrim); } } } diff --git a/src/common/buffer/BufferReflow.ts b/src/common/buffer/BufferReflow.ts index ece9a96e..e496cbbb 100644 --- a/src/common/buffer/BufferReflow.ts +++ b/src/common/buffer/BufferReflow.ts @@ -118,7 +118,7 @@ export function reflowLargerCreateNewLayout(lines: CircularList, to const countToRemove = toRemove[++nextToRemoveIndex]; // Tell markers that there was a deletion - lines.onDeleteEmitter.fire({ + lines.onDelete.fire({ index: i - countRemovedSoFar, amount: countToRemove }); diff --git a/src/common/buffer/BufferSet.ts b/src/common/buffer/BufferSet.ts index 46fcb097..1fa6fc27 100644 --- a/src/common/buffer/BufferSet.ts +++ b/src/common/buffer/BufferSet.ts @@ -6,7 +6,7 @@ import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IAttributeData } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { Disposable } from 'common/Lifecycle'; @@ -19,8 +19,7 @@ export class BufferSet extends Disposable implements IBufferSet { private _alt!: Buffer; private _activeBuffer!: Buffer; - private readonly _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); - public readonly onBufferActivate = this._onBufferActivate.event; + public readonly onBufferActivate = this.register(initEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>()); /** * Create a new BufferSet for the given terminal. @@ -42,7 +41,7 @@ export class BufferSet extends Disposable implements IBufferSet { // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer this._alt = new Buffer(false, this._optionsService, this._bufferService); this._activeBuffer = this._normal; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -86,7 +85,7 @@ export class BufferSet extends Disposable implements IBufferSet { this._alt.clearAllMarkers(); this._alt.clear(); this._activeBuffer = this._normal; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._normal, inactiveBuffer: this._alt }); @@ -105,7 +104,7 @@ export class BufferSet extends Disposable implements IBufferSet { this._alt.x = this._normal.x; this._alt.y = this._normal.y; this._activeBuffer = this._alt; - this._onBufferActivate.fire({ + this.onBufferActivate.fire({ activeBuffer: this._alt, inactiveBuffer: this._normal }); diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 56d64a72..958bd9ed 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IMarker } from 'common/Types'; @@ -15,8 +15,7 @@ export class Marker extends Disposable implements IMarker { public get id(): number { return this._id; } - private readonly _onDispose = new EventEmitter(); - public readonly onDispose = this._onDispose.event; + public readonly onDispose = initEvent(); constructor( public line: number @@ -31,7 +30,7 @@ export class Marker extends Disposable implements IMarker { this.isDisposed = true; this.line = -1; // Emit before super.dispose such that dispose listeners get a change to react - this._onDispose.fire(); + this.onDispose.fire(); super.dispose(); } } diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index bf72ae21..b8efa54b 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter'; declare const setTimeout: (handler: () => void, timeout?: number) => void; @@ -42,8 +42,7 @@ export class WriteBuffer { private _syncCalls = 0; private _didUserInput = false; - private readonly _onWriteParsed = new EventEmitter(); - public readonly onWriteParsed = this._onWriteParsed.event; + public readonly onWriteParsed = initEvent(); constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } @@ -237,6 +236,6 @@ export class WriteBuffer { this._pendingData = 0; this._bufferOffset = 0; } - this._onWriteParsed.fire(); + this.onWriteParsed.fire(); } } diff --git a/src/common/public/BufferNamespaceApi.ts b/src/common/public/BufferNamespaceApi.ts index 033f5955..a00962ac 100644 --- a/src/common/public/BufferNamespaceApi.ts +++ b/src/common/public/BufferNamespaceApi.ts @@ -5,20 +5,19 @@ import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm'; import { BufferApiView } from 'common/public/BufferApiView'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter'; import { ICoreTerminal } from 'common/Types'; export class BufferNamespaceApi implements IBufferNamespaceApi { private _normal: BufferApiView; private _alternate: BufferApiView; - private readonly _onBufferChange = new EventEmitter(); - public readonly onBufferChange = this._onBufferChange.event; + public readonly onBufferChange = initEvent(); constructor(private _core: ICoreTerminal) { this._normal = new BufferApiView(this._core.buffers.normal, 'normal'); this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate'); - this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active)); + this._core.buffers.onBufferActivate(() => this.onBufferChange.fire(this.active)); } public get active(): IBufferApi { if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 1bc93041..3614fcfa 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,7 +6,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services'; import { BufferSet } from 'common/buffer/BufferSet'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; -import { EventEmitter, IEventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types'; @@ -22,10 +22,8 @@ export class BufferService extends Disposable implements IBufferService { /** Whether the user is scrolling (locks the scroll position) */ public isUserScrolling: boolean = false; - private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public readonly onResize = this._onResize.event; - private readonly _onScroll = new EventEmitter(); - public readonly onScroll = this._onScroll.event; + public readonly onResize = initEvent<{ cols: number, rows: number }>(); + public readonly onScroll = initEvent(); public get buffer(): IBuffer { return this.buffers.active; } @@ -49,7 +47,7 @@ export class BufferService extends Disposable implements IBufferService { this.rows = rows; this.buffers.resize(cols, rows); this.buffers.setupTabStops(this.cols); - this._onResize.fire({ cols, rows }); + this.onResize.fire({ cols, rows }); } public reset(): void { @@ -118,7 +116,7 @@ export class BufferService extends Disposable implements IBufferService { buffer.ydisp = buffer.ybase; } - this._onScroll.fire(buffer.ydisp); + this.onScroll.fire(buffer.ydisp); } /** @@ -148,7 +146,7 @@ export class BufferService extends Disposable implements IBufferService { } if (!suppressScrollEvent) { - this._onScroll.fire(buffer.ydisp); + this.onScroll.fire(buffer.ydisp); } } diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index 8c2a24de..a5f1528a 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -3,7 +3,7 @@ * @license MIT */ import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; /** @@ -172,8 +172,7 @@ export class CoreMouseService implements ICoreMouseService { private _activeEncoding: string = ''; private _lastEvent: ICoreMouseEvent | null = null; - private readonly _onProtocolChange = new EventEmitter(); - public readonly onProtocolChange = this._onProtocolChange.event; + public readonly onProtocolChange = initEvent(); constructor( @IBufferService private readonly _bufferService: IBufferService, @@ -207,7 +206,7 @@ export class CoreMouseService implements ICoreMouseService { throw new Error(`unknown protocol "${name}"`); } this._activeProtocol = name; - this._onProtocolChange.fire(this._protocols[name].events); + this.onProtocolChange.fire(this._protocols[name].events); } public get activeEncoding(): string { diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 9282197b..35c75919 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -4,7 +4,7 @@ */ import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { IDecPrivateModes, IModes } from 'common/Types'; import { clone } from 'common/Clone'; import { Disposable } from 'common/Lifecycle'; @@ -34,12 +34,9 @@ export class CoreService extends Disposable implements ICoreService { // Circular dependency, this must be unset or memory will leak after Terminal.dispose private _scrollToBottom: (() => void) | undefined; - private readonly _onData = this.register(new EventEmitter()); - public readonly onData = this._onData.event; - private readonly _onUserInput = this.register(new EventEmitter()); - public readonly onUserInput = this._onUserInput.event; - private readonly _onBinary = this.register(new EventEmitter()); - public readonly onBinary = this._onBinary.event; + public readonly onData = this.register(initEvent()); + public readonly onUserInput = this.register(initEvent()); + public readonly onBinary = this.register(initEvent()); constructor( // TODO: Move this into a service @@ -74,12 +71,12 @@ export class CoreService extends Disposable implements ICoreService { // Fire onUserInput so listeners can react as well (eg. clear selection) if (wasUserInput) { - this._onUserInput.fire(); + this.onUserInput.fire(); } // Fire onData API this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); - this._onData.fire(data); + this.onData.fire(data); } public triggerBinaryEvent(data: string): void { @@ -87,6 +84,6 @@ export class CoreService extends Disposable implements ICoreService { return; } this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0))); - this._onBinary.fire(data); + this.onBinary.fire(data); } } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 522b04de..5efeb77d 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -4,7 +4,7 @@ */ import { css } from 'common/Color'; -import { EventEmitter } from 'common/EventEmitter'; +import { EventEmitter, initEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { SortedList } from 'common/SortedList'; @@ -25,10 +25,8 @@ export class DecorationService extends Disposable implements IDecorationService */ private readonly _decorations: SortedList = new SortedList(e => e?.marker.line); - private readonly _onDecorationRegistered = this.register(new EventEmitter()); - public readonly onDecorationRegistered = this._onDecorationRegistered.event; - private readonly _onDecorationRemoved = this.register(new EventEmitter()); - public readonly onDecorationRemoved = this._onDecorationRemoved.event; + public readonly onDecorationRegistered = this.register(initEvent()); + public readonly onDecorationRemoved = this.register(initEvent()); public get decorations(): IterableIterator { return this._decorations.values(); } @@ -42,13 +40,13 @@ export class DecorationService extends Disposable implements IDecorationService decoration.onDispose(() => { if (decoration) { if (this._decorations.delete(decoration)) { - this._onDecorationRemoved.fire(decoration); + this.onDecorationRemoved.fire(decoration); } markerDispose.dispose(); } }); this._decorations.insert(decoration); - this._onDecorationRegistered.fire(decoration); + this.onDecorationRegistered.fire(decoration); } return decoration; } @@ -84,7 +82,7 @@ export class DecorationService extends Disposable implements IDecorationService public dispose(): void { for (const d of this._decorations.values()) { - this._onDecorationRemoved.fire(d); + this.onDecorationRemoved.fire(d); } this.reset(); } @@ -95,10 +93,8 @@ class Decoration extends Disposable implements IInternalDecoration { public element: HTMLElement | undefined; public isDisposed: boolean = false; - public readonly onRenderEmitter = this.register(new EventEmitter()); - public readonly onRender = this.onRenderEmitter.event; - private readonly _onDispose = this.register(new EventEmitter()); - public readonly onDispose = this._onDispose.event; + public readonly onRender = this.register(initEvent()); + public readonly onDispose = this.register(initEvent()); private _cachedBg: IColor | undefined | null = null; public get backgroundColorRGB(): IColor | undefined { @@ -139,7 +135,7 @@ class Decoration extends Disposable implements IInternalDecoration { return; } this._isDisposed = true; - this._onDispose.fire(); + this.onDispose.fire(); super.dispose(); } } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 33aa3ee2..16beddba 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -4,9 +4,9 @@ */ import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { CursorStyle } from 'common/Types'; +import { initEvent } from 'common/EventEmitter'; export const DEFAULT_OPTIONS: Readonly> = { cols: 80, @@ -57,8 +57,7 @@ export class OptionsService implements IOptionsService { public readonly rawOptions: Required; public options: Required; - private readonly _onOptionChange = new EventEmitter(); - public readonly onOptionChange = this._onOptionChange.event; + public readonly onOptionChange = initEvent(); constructor(options: Partial) { // set the default value of each option @@ -97,7 +96,7 @@ export class OptionsService implements IOptionsService { // Don't fire an option change event if they didn't change if (this.rawOptions[propName] !== value) { this.rawOptions[propName] = value; - this._onOptionChange.fire(propName); + this.onOptionChange.fire(propName); } }; diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e2b517cd..c47b1c2a 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent, IEventEmitter } from 'common/EventEmitter'; +import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, IOscLinkData } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; @@ -318,5 +318,5 @@ export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; readonly backgroundColorRGB: IColor | undefined; readonly foregroundColorRGB: IColor | undefined; - readonly onRenderEmitter: IEventEmitter; + readonly onRender: IEventWithEmitter; } diff --git a/src/common/services/UnicodeService.ts b/src/common/services/UnicodeService.ts index 239f4d62..7306db93 100644 --- a/src/common/services/UnicodeService.ts +++ b/src/common/services/UnicodeService.ts @@ -3,7 +3,7 @@ * @license MIT */ import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter'; import { UnicodeV6 } from 'common/input/UnicodeV6'; @@ -14,8 +14,7 @@ export class UnicodeService implements IUnicodeService { private _active: string = ''; private _activeProvider: IUnicodeVersionProvider; - private readonly _onChange = new EventEmitter(); - public readonly onChange = this._onChange.event; + public readonly onChange = initEvent(); constructor() { const defaultProvider = new UnicodeV6(); @@ -38,7 +37,7 @@ export class UnicodeService implements IUnicodeService { } this._active = version; this._activeProvider = this._providers[version]; - this._onChange.fire(version); + this.onChange.fire(version); } public register(provider: IUnicodeVersionProvider): void { diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 639988eb..45b8aa74 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -24,7 +24,7 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBuffer } from 'common/buffer/Types'; import { CoreTerminal } from 'common/CoreTerminal'; -import { EventEmitter, forwardEvent, IEvent } from 'common/EventEmitter'; +import { EventEmitter, forwardEvent, IEvent, initEvent } from 'common/EventEmitter'; import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; import { IMarker, ITerminalOptions, ScrollSource } from 'common/Types'; @@ -32,16 +32,11 @@ export class Terminal extends CoreTerminal { // TODO: We should remove options once components adopt optionsService public get options(): Required { return this.optionsService.options; } - private readonly _onBell = new EventEmitter(); - public readonly onBell = this._onBell.event; - private readonly _onCursorMove = new EventEmitter(); - public readonly onCursorMove = this._onCursorMove.event; - private readonly _onTitleChange = new EventEmitter(); - public readonly onTitleChange = this._onTitleChange.event; - private readonly _onA11yCharEmitter = new EventEmitter(); - public readonly onA11yChar = this._onA11yCharEmitter.event; - private readonly _onA11yTabEmitter = new EventEmitter(); - public readonly onA11yTab = this._onA11yTabEmitter.event; + public readonly onBell = initEvent(); + public readonly onCursorMove = initEvent(); + public readonly onTitleChange = initEvent(); + public readonly onA11yChar = initEvent(); + public readonly onA11yTab = initEvent(); /** * Creates a new `Terminal` object. @@ -65,10 +60,10 @@ export class Terminal extends CoreTerminal { // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestReset(() => this.reset())); - this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); - this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); - this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter)); - this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter)); + this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove)); + this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange)); + this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar)); + this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab)); } public dispose(): void { @@ -111,7 +106,7 @@ export class Terminal extends CoreTerminal { } public bell(): void { - this._onBell.fire(); + this.onBell.fire(); } /** From 03fcf60aaf71bb2c5c0f830d2cef3303f5617d5f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:55:01 -0700 Subject: [PATCH 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 c0074bba..979a6464 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -66,6 +66,19 @@ export class EventEmitter implements IEventEmitter { } } +/** + * Creates an object that implements both the {@link IEvent} and {@link IEmitter} interfaces. This + * allows more concise instantiation. The idea is to internally use the combined + * {@link IEventWithEmitter} interface and only expose {@link IEvent} externally. + * + * @example + * ```ts + * public readonly onFoo = initEvent(); + * // ... + * onFoo(e => handle(e)); + * onFoo.fire('bar'); + * ``` + */ export function initEvent(): IEventWithEmitter { const emitter = new EventEmitter(); const event = emitter.event; From 2e8b793ba0329dc84cda871155288a89fc4cb95d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 08:56:56 -0700 Subject: [PATCH 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 548be30c..b26c565c 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -20,7 +20,7 @@ export class WebglAddon implements ITerminalAddon { constructor( private _preserveDrawingBuffer?: boolean - ) {} + ) { } public activate(terminal: Terminal): void { if (!terminal.element) { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 7a487242..8d8b24f7 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -54,8 +54,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _contextRestorationTimeout: number | undefined; public readonly onChangeTextureAtlas = initEvent(); - public readonly onRequestRedraw = initEvent(); - public readonly onContextLoss = initEvent(); + public readonly onRequestRedraw = initEvent(); + public readonly onContextLoss = initEvent(); constructor( private _terminal: Terminal, @@ -419,9 +419,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg && - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg && + this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) { continue; } From 2a602ef231abcbb431d240679a6d02f44a99c83b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 10:20:37 -0700 Subject: [PATCH 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 b0151e30..fa4be1e5 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts @@ -45,8 +45,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.allowTransparency === b.allowTransparency && a.scaledCharWidth === b.scaledCharWidth && a.scaledCharHeight === b.scaledCharHeight && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground.rgba === b.colors.foreground.rgba && + a.colors.background.rgba === b.colors.background.rgba; } export function is256Color(colorCode: number): boolean { diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 83f82fa7..dc503d75 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -70,8 +70,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.scaledCharHeight === b.scaledCharHeight && a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors && a.minimumContrastRatio === b.minimumContrastRatio && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground.rgba === b.colors.foreground.rgba && + a.colors.background.rgba === b.colors.background.rgba; } export function is256Color(colorCode: number): boolean { From 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 3098538d..b809378e 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -96,7 +96,10 @@ export class DynamicCharAtlas extends BaseCharAtlas { const tmpCanvas = document.createElement('canvas'); tmpCanvas.width = this._config.scaledCharWidth; tmpCanvas.height = this._config.scaledCharHeight; - this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency })); + this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { + alpha: this._config.allowTransparency, + willReadFrequently: true + })); this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth); this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight); From 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 979a6464..29e931d2 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -22,7 +22,7 @@ export interface IEventWithEmitter extends IEventEmitter, IEv } export class EventEmitter implements IEventEmitter { - private _listeners: IListener[] = []; + private readonly _listeners: IListener[] = []; private _event?: IEvent; private _disposed: boolean = false; @@ -82,6 +82,9 @@ export class EventEmitter implements IEventEmitter { export function initEvent(): IEventWithEmitter { const emitter = new EventEmitter(); const event = emitter.event; + Object.defineProperty(event, '_listeners', { + value: (emitter as any)._listeners + }); Object.defineProperty(event, 'fire', { value: emitter.fire.bind(emitter) });