From 9df996cb3db67f89392a54206237dcc565579f15 Mon Sep 17 00:00:00 2001 From: Svante Boberg Date: Sat, 23 Jul 2022 13:21:13 +0200 Subject: [PATCH 01/30] Dispose events https://github.com/microsoft/vscode/issues/155232 The events that is setup for needs to be disposed for js to be able to garbage collect the terminals. --- src/browser/Linkifier.ts | 6 ++++++ src/browser/Linkifier2.ts | 7 +++++++ src/browser/Terminal.ts | 4 ++++ src/browser/Types.d.ts | 2 ++ 4 files changed, 19 insertions(+) diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index b17d66a8..25f07104 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -53,6 +53,12 @@ export class Linkifier implements ILinkifier { }; } + public dispose(): void { + this._onShowLinkUnderline.dispose(); + this._onHideLinkUnderline.dispose(); + this._onLinkTooltip.dispose(); + } + /** * Attaches the linkifier to the DOM, enabling linkification. * @param mouseZoneManager The mouse zone manager to register link zones with. diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index dae9acfa..8d7cbee6 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -38,6 +38,13 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { this.register(getDisposeArrayDisposable(this._linkCacheDisposables)); } + public dispose(): void { + super.dispose(); + this._onShowLinkUnderline.dispose(); + this._onHideLinkUnderline.dispose(); + this._lastMouseEvent = undefined; + } + public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { this._linkProviders.push(linkProvider); return { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index bd82325d..732c7d49 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -247,8 +247,12 @@ export class Terminal extends CoreTerminal implements ITerminal { super.dispose(); this._renderService?.dispose(); this._customKeyEventHandler = undefined; + this._overviewRulerRenderer?.dispose(); + this._overviewRulerRenderer = undefined; this.write = () => { }; this.element?.parentNode?.removeChild(this.element); + this.linkifier.dispose(); + this.linkifier2.dispose(); } protected _setup(): void { diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 41992a8b..8408b331 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -201,6 +201,7 @@ export interface ILinkifier { linkifyRows(start: number, end: number): void; registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): boolean; + dispose(): void; } interface ILinkState { @@ -219,6 +220,7 @@ export interface ILinkifier2 { attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void; registerLinkProvider(linkProvider: ILinkProvider): IDisposable; + dispose(): void; } export interface ILinkMatcherOptions { From cb10aadb0623c998d7d0a36e7b4026cd1080be34 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 23 Jul 2022 19:18:26 -0700 Subject: [PATCH 02/30] Fix device pixel rounding errors in webgl renderer This uses the ResizeObserver devicePixelContentBoxSize API in order to fetch the exact device pixel dimensions from the browser. The old possibly blurry behavior is used as a fallback if that API is not available. Part of #2662 Part of microsoft/vscode#85154 --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 95 +++++++++++-------- 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1e385b33..2c885ef6 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -11,7 +11,7 @@ import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; @@ -96,6 +96,31 @@ export class WebglRenderer extends Disposable implements IRenderer { this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); })); + // Observe any resizes to the canvas and extract the actual pixel size of the canvas if the + // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when + // converting between CSS pixels and device pixels which causes blurry rendering when device + // pixel ratio is not a round number. + let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { + const entry = entries.find((entry) => entry.target === this._canvas); + if (!entry) { + return; + } + + // Disconnect if devicePixelContentBoxSize isn't supported by the browser + if (!('devicePixelContentBoxSize' in entry)) { + observer?.disconnect(); + observer = undefined; + return; + } + + this._setCanvasDevicePixelDimensions( + entry.devicePixelContentBoxSize[0].inlineSize, + entry.devicePixelContentBoxSize[0].blockSize + ); + }); + observer.observe(this._canvas, { box: ['device-pixel-content-box'] } as any); + this.register(toDisposable(() => observer?.disconnect())); + this._core.screenElement!.appendChild(this._canvas); this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions)); @@ -516,67 +541,61 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - // Calculate the scaled character width. Width is floored as it must be - // drawn to an integer grid in order for the CharAtlas "stamps" to not be - // blurry. When text is drawn to the grid not using the CharAtlas, it is - // clipped to ensure there is no overlap with the next cell. - - // NOTE: ceil fixes sometime, floor does others :s - + // Calculate the scaled character width. Width is floored as it must be drawn to an integer grid + // in order for the char atlas glyphs to not be blurry. this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); - // Calculate the scaled character height. Height is ceiled in case - // devicePixelRatio is a floating point number in order to ensure there is - // enough space to draw the character to the cell. + // Calculate the scaled character height. Height is ceiled in case devicePixelRatio is a + // floating point number in order to ensure there is enough space to draw the character to the + // cell. this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); - // Calculate the scaled cell height, if lineHeight is not 1 then the value - // will be floored because since lineHeight can never be lower then 1, there - // is a guarentee that the scaled line height will always be larger than - // scaled char height. + // Calculate the scaled cell height, if lineHeight is _not_ 1, the resulting value will be + // floored since lineHeight can never be lower then 1, this guarentees the scaled cell height + // will always be larger than scaled char height. this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight!); - // Calculate the y coordinate within a cell that text should draw from in - // order to draw in the center of a cell. + // Calculate the y offset within a cell that glyph should draw at in order for it to be centered + // correctly within the cell. this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); // Calculate the scaled cell width, taking the letterSpacing into account. this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing!); - // Calculate the x coordinate with a cell that text should draw from in - // order to draw in the center of a cell. + // Calculate the x offset with a cell that text should draw from in order for it to be centered + // correctly within the cell. this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing! / 2); - // Recalculate the canvas dimensions; scaled* define the actual number of - // pixel in the canvas + // Recalculate the canvas dimensions, the scaled dimensions define the actual number of pixel in + // the canvas this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; - // The the size of the canvas on the page. It's very important that this - // rounds to nearest integer and not ceils as browsers often set - // window.devicePixelRatio as something like 1.100000023841858, when it's - // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 - // pixel too large for the canvas element size. + // The the size of the canvas on the page. It's important that this rounds to nearest integer + // and not ceils as browsers often have floating point precision issues where + // `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when + // it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too + // large for the canvas element size. this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); - // this.dimensions.scaledCanvasHeight = this.dimensions.canvasHeight * devicePixelRatio; - // this.dimensions.scaledCanvasWidth = this.dimensions.canvasWidth * devicePixelRatio; - - // Get the _actual_ dimensions of an individual cell. This needs to be - // derived from the canvasWidth/Height calculated above which takes into - // account window.devicePixelRatio. CharMeasure.width/height by itself is - // insufficient when the page is not at 100% zoom level as CharMeasure is - // measured in CSS pixels, but the actual char size on the canvas can + // Get the _actual_ dimensions of an individual cell. This needs to be derived from the + // canvas CSS dimensions calculated above which takes into account device pixel ratio. + // `CharMeasure.width`/`height` by itself is insufficient when device pixel ratio is not a round + // number as CharMeasure is measured in CSS pixels, since the actual device pixel char size will // differ. - // this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; - // this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; - - // This fixes 110% and 125%, not 150% or 175% though this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } + private _setCanvasDevicePixelDimensions(width: number, height: number): void { + this.dimensions.scaledCanvasHeight = width; + this.dimensions.scaledCanvasWidth = height; + this._canvas.width = width; + this._canvas.height = height; + this._requestRedrawViewport(); + } + private _requestRedrawViewport(): void { this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } From e25471361f127f534551f6ec78ebb16a71e6ca32 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 03:41:25 -0700 Subject: [PATCH 03/30] Fix device pixel value in canvas renderer --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 10 +-- src/browser/renderer/BaseRenderLayer.ts | 2 + src/browser/renderer/Renderer.ts | 80 ++++++++++--------- src/browser/renderer/Types.d.ts | 2 + 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 2c885ef6..b081fb1f 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -579,13 +579,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); - // Get the _actual_ dimensions of an individual cell. This needs to be derived from the - // canvas CSS dimensions calculated above which takes into account device pixel ratio. - // `CharMeasure.width`/`height` by itself is insufficient when device pixel ratio is not a round - // number as CharMeasure is measured in CSS pixels, since the actual device pixel char size will - // differ. - this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; - this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; + // Get the CSS dimensions of an individual cell. + this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._devicePixelRatio; + this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._devicePixelRatio; } private _setCanvasDevicePixelDimensions(width: number, height: number): void { diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 0a9b8057..af3aabb4 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -48,6 +48,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { italic: false }; + public get canvas(): HTMLCanvasElement { return this._canvas; } + constructor( private _container: HTMLElement, id: string, diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 8bc32278..04fdec77 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -8,7 +8,7 @@ import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer'; import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer'; import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; @@ -62,6 +62,33 @@ export class Renderer extends Disposable implements IRenderer { }; this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); + + // Observe any resizes to the canvas and extract the actual pixel size of the canvas if the + // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when + // converting between CSS pixels and device pixels which causes blurry rendering when device + // pixel ratio is not a round number. + const observedCanvas = this._renderLayers[0].canvas; + let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { + const entry = entries.find((entry) => entry.target === observedCanvas); + if (!entry) { + return; + } + + // Disconnect if devicePixelContentBoxSize isn't supported by the browser + if (!('devicePixelContentBoxSize' in entry)) { + observer?.disconnect(); + observer = undefined; + return; + } + + this._setCanvasDevicePixelDimensions( + entry.devicePixelContentBoxSize[0].inlineSize, + entry.devicePixelContentBoxSize[0].blockSize + ); + }); + observer.observe(observedCanvas, { box: ['device-pixel-content-box'] } as any); + this.register(toDisposable(() => observer?.disconnect())); + this.onOptionsChanged(); } @@ -167,53 +194,32 @@ export class Renderer extends Disposable implements IRenderer { return; } - // Calculate the scaled character width. Width is floored as it must be - // drawn to an integer grid in order for the CharAtlas "stamps" to not be - // blurry. When text is drawn to the grid not using the CharAtlas, it is - // clipped to ensure there is no overlap with the next cell. + // See the WebGL renderer for an explanation of this section. this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * window.devicePixelRatio); - - // Calculate the scaled character height. Height is ceiled in case - // devicePixelRatio is a floating point number in order to ensure there is - // enough space to draw the character to the cell. this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio); - - // Calculate the scaled cell height, if lineHeight is not 1 then the value - // will be floored because since lineHeight can never be lower then 1, there - // is a guarentee that the scaled line height will always be larger than - // scaled char height. this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); - - // Calculate the y coordinate within a cell that text should draw from in - // order to draw in the center of a cell. this.dimensions.scaledCharTop = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); - - // Calculate the scaled cell width, taking the letterSpacing into account. this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); - - // Calculate the x coordinate with a cell that text should draw from in - // order to draw in the center of a cell. this.dimensions.scaledCharLeft = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); - - // Recalculate the canvas dimensions; scaled* define the actual number of - // pixel in the canvas this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight; this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth; - - // The the size of the canvas on the page. It's very important that this - // rounds to nearest integer and not ceils as browsers often set - // window.devicePixelRatio as something like 1.100000023841858, when it's - // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 - // pixel too large for the canvas element size. this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); - - // Get the _actual_ dimensions of an individual cell. This needs to be - // derived from the canvasWidth/Height calculated above which takes into - // account window.devicePixelRatio. ICharSizeService.width/height by itself - // is insufficient when the page is not at 100% zoom level as it's measured - // in CSS pixels, but the actual char size on the canvas can differ. this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; } + + private _setCanvasDevicePixelDimensions(width: number, height: number): void { + this.dimensions.scaledCanvasHeight = width; + this.dimensions.scaledCanvasWidth = height; + // Resize all render layers + for (const l of this._renderLayers) { + l.resize(this.dimensions); + } + this._requestRedrawViewport(); + } + + private _requestRedrawViewport(): void { + this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + } } diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 6818a926..7410d481 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -56,6 +56,8 @@ export interface IRenderer extends IDisposable { } export interface IRenderLayer extends IDisposable { + readonly canvas: HTMLCanvasElement; + /** * Called when the terminal loses focus. */ From 3d6c25bf2de0a9ee625e61350ccb432c1a98f269 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 03:43:28 -0700 Subject: [PATCH 04/30] typescript@4.7 Required for devicePixelContentBoxSize ResizeObserver API --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cb0c1919..9db4d16f 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "source-map-loader": "^3.0.0", "source-map-support": "^0.5.20", "ts-loader": "^9.1.2", - "typescript": "^4.4.4", + "typescript": "4.7", "utf8": "^3.0.0", "webpack": "^5.61.0", "webpack-cli": "^4.9.1", diff --git a/yarn.lock b/yarn.lock index 7cb970bb..2a5c02bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3817,16 +3817,16 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@4.7: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + typescript@^4.2.3: version "4.6.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== -typescript@^4.4.4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" - integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== - unbox-primitive@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" From ad8404417edb072db61f0a064e4a66ec0ae24614 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 03:57:22 -0700 Subject: [PATCH 05/30] ts-loader update, correct css cell dimension error --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 16 +++++++++++----- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b081fb1f..8d6a4f49 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -579,14 +579,20 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); - // Get the CSS dimensions of an individual cell. - this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._devicePixelRatio; - this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._devicePixelRatio; + // Get the CSS dimensions of an individual cell. This needs to be derived from the calculated + // device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the + // page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char + // size on the canvas can differ. + this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; + this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } private _setCanvasDevicePixelDimensions(width: number, height: number): void { - this.dimensions.scaledCanvasHeight = width; - this.dimensions.scaledCanvasWidth = height; + if (this.dimensions.scaledCanvasWidth === width && this.dimensions.scaledCanvasHeight === height) { + return; + } + this.dimensions.scaledCanvasWidth = width; + this.dimensions.scaledCanvasHeight = height; this._canvas.width = width; this._canvas.height = height; this._requestRedrawViewport(); diff --git a/package.json b/package.json index 9db4d16f..a270a52f 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "playwright": "^1.22.1", "source-map-loader": "^3.0.0", "source-map-support": "^0.5.20", - "ts-loader": "^9.1.2", + "ts-loader": "^9.3.1", "typescript": "4.7", "utf8": "^3.0.0", "webpack": "^5.61.0", diff --git a/yarn.lock b/yarn.lock index 2a5c02bb..a1e6391c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3746,10 +3746,10 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" -ts-loader@^9.1.2: - version "9.2.6" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74" - integrity sha512-QMTC4UFzHmu9wU2VHZEmWWE9cUajjfcdcws+Gh7FhiO+Dy0RnR1bNz0YCHqhI0yRowCE9arVnNxYHqELOy9Hjw== +ts-loader@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.1.tgz#fe25cca56e3e71c1087fe48dc67f4df8c59b22d4" + integrity sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw== dependencies: chalk "^4.1.0" enhanced-resolve "^5.0.0" From 47b1fdd14725811c85a9e52f67c9dd6aba362093 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 04:09:34 -0700 Subject: [PATCH 06/30] Refactor to share observeDevicePixelDimensions --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 27 ++------------- src/browser/renderer/DevicePixelObserver.ts | 34 +++++++++++++++++++ src/browser/renderer/Renderer.ts | 27 ++------------- 3 files changed, 38 insertions(+), 50 deletions(-) create mode 100644 src/browser/renderer/DevicePixelObserver.ts diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 8d6a4f49..b21f8c2f 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -16,6 +16,7 @@ import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'co import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; @@ -95,31 +96,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); })); - - // Observe any resizes to the canvas and extract the actual pixel size of the canvas if the - // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when - // converting between CSS pixels and device pixels which causes blurry rendering when device - // pixel ratio is not a round number. - let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { - const entry = entries.find((entry) => entry.target === this._canvas); - if (!entry) { - return; - } - - // Disconnect if devicePixelContentBoxSize isn't supported by the browser - if (!('devicePixelContentBoxSize' in entry)) { - observer?.disconnect(); - observer = undefined; - return; - } - - this._setCanvasDevicePixelDimensions( - entry.devicePixelContentBoxSize[0].inlineSize, - entry.devicePixelContentBoxSize[0].blockSize - ); - }); - observer.observe(this._canvas, { box: ['device-pixel-content-box'] } as any); - this.register(toDisposable(() => observer?.disconnect())); + this.register(observeDevicePixelDimensions(this._canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this._core.screenElement!.appendChild(this._canvas); diff --git a/src/browser/renderer/DevicePixelObserver.ts b/src/browser/renderer/DevicePixelObserver.ts new file mode 100644 index 00000000..611eb910 --- /dev/null +++ b/src/browser/renderer/DevicePixelObserver.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { toDisposable } from 'common/Lifecycle'; +import { IDisposable } from 'common/Types'; + +export function observeDevicePixelDimensions(element: HTMLElement, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable { + // Observe any resizes to the element and extract the actual pixel size of the element if the + // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when + // converting between CSS pixels and device pixels which causes blurry rendering when device + // pixel ratio is not a round number. + let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { + const entry = entries.find((entry) => entry.target === element); + if (!entry) { + return; + } + + // Disconnect if devicePixelContentBoxSize isn't supported by the browser + if (!('devicePixelContentBoxSize' in entry)) { + observer?.disconnect(); + observer = undefined; + return; + } + + callback( + entry.devicePixelContentBoxSize[0].inlineSize, + entry.devicePixelContentBoxSize[0].blockSize + ); + }); + observer.observe(element, { box: ['device-pixel-content-box'] } as any); + return toDisposable(() => observer?.disconnect()); +} diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 04fdec77..45b7f1c1 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -14,6 +14,7 @@ import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; let nextRendererId = 1; @@ -63,31 +64,7 @@ export class Renderer extends Disposable implements IRenderer { this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); - // Observe any resizes to the canvas and extract the actual pixel size of the canvas if the - // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when - // converting between CSS pixels and device pixels which causes blurry rendering when device - // pixel ratio is not a round number. - const observedCanvas = this._renderLayers[0].canvas; - let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { - const entry = entries.find((entry) => entry.target === observedCanvas); - if (!entry) { - return; - } - - // Disconnect if devicePixelContentBoxSize isn't supported by the browser - if (!('devicePixelContentBoxSize' in entry)) { - observer?.disconnect(); - observer = undefined; - return; - } - - this._setCanvasDevicePixelDimensions( - entry.devicePixelContentBoxSize[0].inlineSize, - entry.devicePixelContentBoxSize[0].blockSize - ); - }); - observer.observe(observedCanvas, { box: ['device-pixel-content-box'] } as any); - this.register(toDisposable(() => observer?.disconnect())); + this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this.onOptionsChanged(); } From 0165c3612dac9426607a1a70ab3899245dc65fc5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 07:11:56 -0700 Subject: [PATCH 07/30] Fix canvas device pixel dimension setting --- src/browser/renderer/Renderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 45b7f1c1..fe3c4c58 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -187,8 +187,8 @@ export class Renderer extends Disposable implements IRenderer { } private _setCanvasDevicePixelDimensions(width: number, height: number): void { - this.dimensions.scaledCanvasHeight = width; - this.dimensions.scaledCanvasWidth = height; + this.dimensions.scaledCanvasHeight = height; + this.dimensions.scaledCanvasWidth = width; // Resize all render layers for (const l of this._renderLayers) { l.resize(this.dimensions); From 0ccf3beec7cfddc1fdce5a6ca50ffd64f733e39c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 08:03:27 -0700 Subject: [PATCH 08/30] Retain hue when flipping luminance Fixes #3845 --- src/common/Color.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/Color.ts b/src/common/Color.ts index e5c7e3eb..f62dd6cb 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -195,7 +195,7 @@ export namespace rgba { const resultA = reduceLuminance(bgRgba, fgRgba, ratio); const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8)); if (resultARatio < ratio) { - const resultB = increaseLuminance(bgRgba, bgRgba, ratio); + const resultB = increaseLuminance(bgRgba, fgRgba, ratio); const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8)); return resultARatio > resultBRatio ? resultA : resultB; } @@ -204,7 +204,7 @@ export namespace rgba { const resultA = increaseLuminance(bgRgba, fgRgba, ratio); const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8)); if (resultARatio < ratio) { - const resultB = reduceLuminance(bgRgba, bgRgba, ratio); + const resultB = reduceLuminance(bgRgba, fgRgba, ratio); const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8)); return resultARatio > resultBRatio ? resultA : resultB; } From a39c7fb6368ad4908fd3b6b003a0991517a13dd6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 08:30:35 -0700 Subject: [PATCH 09/30] Never clear color when allowTransparency is on It's redundant and wasn't working as expected anyway. Fixes #3930 --- .../src/atlas/WebglCharAtlas.ts | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 05751c42..67ddb581 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -395,15 +395,15 @@ export class WebglCharAtlas implements IDisposable { this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight); } - // If this charcater is underscore and beyond the cell bounds, shift it up until it is visible, - // try for a maximum of 5 pixels. + // If this charcater is underscore and beyond the cell bounds, shift it up until it is visible + // even on the bottom row, try for a maximum of 5 pixels. if (chars === '_' && !this._config.allowTransparency) { - let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor, this._config.allowTransparency); + let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor); if (isBeyondCellBounds) { for (let offset = 1; offset <= 5; offset++) { this._tmpCtx.clearRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height); this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight - offset); - isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor, this._config.allowTransparency); + isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor, foregroundColor); if (!isBeyondCellBounds) { break; } @@ -439,7 +439,12 @@ export class WebglCharAtlas implements IDisposable { ); // Clear out the background color and determine if the glyph is empty. - const isEmpty = clearColor(imageData, backgroundColor, foregroundColor, this._config.allowTransparency); + let isEmpty: boolean; + if (!this._config.allowTransparency) { + isEmpty = clearColor(imageData, backgroundColor, foregroundColor); + } else { + isEmpty = checkCompletelyTransparent(imageData); + } // Handle empty glyphs if (isEmpty) { @@ -583,7 +588,7 @@ export class WebglCharAtlas implements IDisposable { * transparent. * @returns True if the result is "empty", meaning all pixels are fully transparent. */ -function clearColor(imageData: ImageData, bg: IColor, fg: IColor, allowTransparency: boolean): boolean { +function clearColor(imageData: ImageData, bg: IColor, fg: IColor): boolean { // Get color channels const r = bg.rgba >>> 24; const g = bg.rgba >>> 16 & 0xFF; @@ -603,15 +608,14 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, allowTranspare // Set alpha channel of relevent pixels to 0 let isEmpty = true; for (let offset = 0; offset < imageData.data.length; offset += 4) { + // Check exact match if (imageData.data[offset] === r && imageData.data[offset + 1] === g && imageData.data[offset + 2] === b) { imageData.data[offset + 3] = 0; } else { - // Check the threshold only when transparency is not allowed only as overlapping isn't an - // issue for transparency glyphs. - if (!allowTransparency && - (Math.abs(imageData.data[offset] - r) + + // Check the threshold based difference + if ((Math.abs(imageData.data[offset] - r) + Math.abs(imageData.data[offset + 1] - g) + Math.abs(imageData.data[offset + 2] - b)) < threshold) { imageData.data[offset + 3] = 0; @@ -624,6 +628,15 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, allowTranspare return isEmpty; } +function checkCompletelyTransparent(imageData: ImageData): boolean { + for (let offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset + 3] > 0) { + return false; + } + } + return true; +} + function toPaddedHex(c: number): string { const s = c.toString(16); return s.length < 2 ? '0' + s : s; From 9acc36f8c034b996052d525e47de96212fc70c01 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 16:52:11 -0700 Subject: [PATCH 10/30] Warn when ligature fonts fail to download Fixes #3784 --- addons/xterm-addon-ligatures/bin/download-fonts.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-ligatures/bin/download-fonts.js b/addons/xterm-addon-ligatures/bin/download-fonts.js index ec4e5fea..27e75c09 100644 --- a/addons/xterm-addon-ligatures/bin/download-fonts.js +++ b/addons/xterm-addon-ligatures/bin/download-fonts.js @@ -22,10 +22,13 @@ const fontsFolder = path.join(__dirname, '../fonts'); async function download() { await mkdirp(fontsFolder); - await downloadFiraCode(); - await downloadIosevka(); - - console.log('Loaded all fonts for testing') + try { + await downloadFiraCode(); + await downloadIosevka(); + console.log('Loaded all fonts for testing') + } catch (e) { + console.warn('Fonts failed to download, ligature tests will not work', e); + } } async function downloadFiraCode() { From 3322418728faaeb2070053ea37595d5aad8f37ba Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 17:45:14 -0700 Subject: [PATCH 11/30] Remove shift+backspace -> ^H binding This has been there since the first commit of term.js, it's not clear why it's there but Terminal.app, iTerm2 and kitty seem to just do a regular backspace Fixes #3759 --- src/common/input/Keyboard.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/common/input/Keyboard.ts b/src/common/input/Keyboard.ts index 0481ad99..013a7711 100644 --- a/src/common/input/Keyboard.ts +++ b/src/common/input/Keyboard.ts @@ -83,10 +83,7 @@ export function evaluateKeyboardEvent( break; case 8: // backspace - if (ev.shiftKey) { - result.key = C0.BS; // ^H - break; - } else if (ev.altKey) { + if (ev.altKey) { result.key = C0.ESC + C0.DEL; // \e ^? break; } From f4bc9f1ecd72449dfdaf09ebbe6082833218504c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 19:47:18 -0700 Subject: [PATCH 12/30] Pull fonts using latest font access API in Chrome Part of #958 --- addons/xterm-addon-ligatures/src/font.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/xterm-addon-ligatures/src/font.ts index 4075cc6c..3a3c799a 100644 --- a/addons/xterm-addon-ligatures/src/font.ts +++ b/addons/xterm-addon-ligatures/src/font.ts @@ -65,6 +65,22 @@ export default async function load(fontFamily: string, cacheSize: number): Promi console.error(err.name, err.message); } } + // Latest proposal + else if ('queryLocalFonts' in window) { + const fonts: Record = {}; + try { + const fontsIterator = await (window as any).queryLocalFonts(); // await (navigator as unknown as IFontAccessNavigator).fonts.query(); + for (const metadata of fontsIterator) { + if (!fonts.hasOwnProperty(metadata.family)) { + fonts[metadata.family] = []; + } + fonts[metadata.family].push(metadata); + } + fontsPromise = Promise.resolve(fonts); + } catch (err: any) { + console.error(err.name, err.message); + } + } // Node environment or no font access API else { try { From 4744ef34b2029415605448fa38c89f07bc0095bd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 20:44:06 -0700 Subject: [PATCH 13/30] Remove unneeded comment --- addons/xterm-addon-ligatures/src/font.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/xterm-addon-ligatures/src/font.ts index 3a3c799a..e2e40d98 100644 --- a/addons/xterm-addon-ligatures/src/font.ts +++ b/addons/xterm-addon-ligatures/src/font.ts @@ -69,7 +69,7 @@ export default async function load(fontFamily: string, cacheSize: number): Promi else if ('queryLocalFonts' in window) { const fonts: Record = {}; try { - const fontsIterator = await (window as any).queryLocalFonts(); // await (navigator as unknown as IFontAccessNavigator).fonts.query(); + const fontsIterator = await (window as any).queryLocalFonts(); for (const metadata of fontsIterator) { if (!fonts.hasOwnProperty(metadata.family)) { fonts[metadata.family] = []; From 78ddb4b14a72136113dacc2b1f2d04d1a7e79285 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 25 Jul 2022 07:00:58 -0700 Subject: [PATCH 14/30] Add link to font access api change --- addons/xterm-addon-ligatures/src/font.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/xterm-addon-ligatures/src/font.ts index e2e40d98..8336cefc 100644 --- a/addons/xterm-addon-ligatures/src/font.ts +++ b/addons/xterm-addon-ligatures/src/font.ts @@ -106,7 +106,9 @@ export default async function load(fontFamily: string, cacheSize: number): Promi if (fonts.hasOwnProperty(family) && fonts[family].length > 0) { const font = fonts[family][0]; if ('blob' in font) { - return loadBuffer(await (await font.blob()).arrayBuffer(), { cacheSize }); + const bytes = await font.blob(); + const buffer = await bytes.arrayBuffer(); + return loadBuffer(buffer, { cacheSize }); } return await loadFile(font.path, { cacheSize }); } From 5664ff310109658f61554612cb9a9e3da4081833 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 25 Jul 2022 07:42:38 -0700 Subject: [PATCH 15/30] Fix ligature tests in node --- addons/xterm-addon-ligatures/src/font.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/xterm-addon-ligatures/src/font.ts index 8336cefc..5c5f0a6c 100644 --- a/addons/xterm-addon-ligatures/src/font.ts +++ b/addons/xterm-addon-ligatures/src/font.ts @@ -65,8 +65,8 @@ export default async function load(fontFamily: string, cacheSize: number): Promi console.error(err.name, err.message); } } - // Latest proposal - else if ('queryLocalFonts' in window) { + // Latest proposal https://bugs.chromium.org/p/chromium/issues/detail?id=1312603 + else if (typeof process !== 'object' && 'queryLocalFonts' in window) { const fonts: Record = {}; try { const fontsIterator = await (window as any).queryLocalFonts(); From 9159fc68e163f902f9993d6c8bf97a39d39cf1e8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 25 Jul 2022 09:44:54 -0700 Subject: [PATCH 16/30] Redraw canvas renderer selection on resize Fixes #1963 --- src/browser/renderer/BaseRenderLayer.ts | 6 +++--- src/browser/renderer/SelectionRenderLayer.ts | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 0a9b8057..a44253a0 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -29,9 +29,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - private _selectionStart: [number, number] | undefined; - private _selectionEnd: [number, number] | undefined; - private _columnSelectMode: boolean = false; + protected _selectionStart: [number, number] | undefined; + protected _selectionEnd: [number, number] | undefined; + protected _columnSelectMode: boolean = false; protected _charAtlas: BaseCharAtlas | undefined; diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts index ce4fe071..f8bdc234 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -42,8 +42,11 @@ export class SelectionRenderLayer extends BaseRenderLayer { public resize(dim: IRenderDimensions): void { super.resize(dim); - // Resizing the canvas discards the contents of the canvas so clear state - this._clearState(); + // On resize use the base render layer's cached selection values since resize clears _state + // inside reset. + if (this._selectionStart && this._selectionEnd) { + this.onSelectionChanged(this._selectionStart, this._selectionEnd, this._columnSelectMode); + } } public reset(): void { From c895f67fd8d21861f49be1df198437935b8c2831 Mon Sep 17 00:00:00 2001 From: Svante Boberg Date: Wed, 27 Jul 2022 00:00:41 +0200 Subject: [PATCH 17/30] Add missing webgl cursor blink disposal Same disposal code as the non-webgl cursor blink layer --- .../src/renderLayer/CursorRenderLayer.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index c80b4c56..f7dcef28 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -54,6 +54,14 @@ export class CursorRenderLayer extends BaseRenderLayer { this.onOptionsChanged(terminal); } + public override dispose(): void { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = undefined; + } + super.dispose(); + } + public resize(terminal: Terminal, dim: IRenderDimensions): void { super.resize(terminal, dim); // Resizing the canvas discards the contents of the canvas so clear state From 078b83fcaf9abbf309ef17515d1f12eb59deddc2 Mon Sep 17 00:00:00 2001 From: Svante Boberg Date: Wed, 27 Jul 2022 14:24:42 +0200 Subject: [PATCH 18/30] Use disposal registration --- .../src/renderLayer/CursorRenderLayer.ts | 6 ++---- src/browser/Linkifier.ts | 16 ++++++---------- src/browser/Terminal.ts | 10 +++------- src/browser/Types.d.ts | 6 ++---- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index f7dcef28..fd686cc7 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -55,10 +55,8 @@ export class CursorRenderLayer extends BaseRenderLayer { } public override dispose(): void { - if (this._cursorBlinkStateManager) { - this._cursorBlinkStateManager.dispose(); - this._cursorBlinkStateManager = undefined; - } + this._cursorBlinkStateManager?.dispose(); + this._cursorBlinkStateManager = undefined; super.dispose(); } diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts index 25f07104..b9e3c4ca 100644 --- a/src/browser/Linkifier.ts +++ b/src/browser/Linkifier.ts @@ -6,6 +6,7 @@ import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; import { IBufferStringIteratorResult } from 'common/buffer/Types'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; import { ILogService, IBufferService, IOptionsService, IUnicodeService } from 'common/services/Services'; /** @@ -18,7 +19,7 @@ const OVERSCAN_CHAR_LIMIT = 2000; /** * The Linkifier applies links to rows shortly after they have been refreshed. */ -export class Linkifier implements ILinkifier { +export class Linkifier extends Disposable implements ILinkifier { /** * The time to wait after a row is changed before it is linkified. This prevents * the costly operation of searching every row multiple times, potentially a @@ -35,11 +36,11 @@ export class Linkifier implements ILinkifier { private _nextLinkMatcherId = 0; private _rowsToLinkify: { start: number | undefined, end: number | undefined }; - private _onShowLinkUnderline = new EventEmitter(); + private _onShowLinkUnderline = this.register(new EventEmitter()); public get onShowLinkUnderline(): IEvent { return this._onShowLinkUnderline.event; } - private _onHideLinkUnderline = new EventEmitter(); + private _onHideLinkUnderline = this.register(new EventEmitter()); public get onHideLinkUnderline(): IEvent { return this._onHideLinkUnderline.event; } - private _onLinkTooltip = new EventEmitter(); + private _onLinkTooltip = this.register(new EventEmitter()); public get onLinkTooltip(): IEvent { return this._onLinkTooltip.event; } constructor( @@ -47,18 +48,13 @@ export class Linkifier implements ILinkifier { @ILogService private readonly _logService: ILogService, @IUnicodeService private readonly _unicodeService: IUnicodeService ) { + super(); this._rowsToLinkify = { start: undefined, end: undefined }; } - public dispose(): void { - this._onShowLinkUnderline.dispose(); - this._onHideLinkUnderline.dispose(); - this._onLinkTooltip.dispose(); - } - /** * Attaches the linkifier to the DOM, enabling linkification. * @param mouseZoneManager The mouse zone manager to register link zones with. diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 732c7d49..8f881177 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -168,7 +168,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._setup(); - this.linkifier = this._instantiationService.createInstance(Linkifier); + this.linkifier = this.register(this._instantiationService.createInstance(Linkifier)); this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2)); this._decorationService = this._instantiationService.createInstance(DecorationService); this._instantiationService.setService(IDecorationService, this._decorationService); @@ -247,12 +247,8 @@ export class Terminal extends CoreTerminal implements ITerminal { super.dispose(); this._renderService?.dispose(); this._customKeyEventHandler = undefined; - this._overviewRulerRenderer?.dispose(); - this._overviewRulerRenderer = undefined; this.write = () => { }; this.element?.parentNode?.removeChild(this.element); - this.linkifier.dispose(); - this.linkifier2.dispose(); } protected _setup(): void { @@ -612,11 +608,11 @@ export class Terminal extends CoreTerminal implements ITerminal { } if (this.options.overviewRulerWidth) { - this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); + this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement)); } this.optionsService.onOptionChange(() => { if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) { - this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); + this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement)); } }); // Measure the character size diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 8408b331..db19e0e0 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -192,7 +192,7 @@ export interface ILinkifierEvent { fg: number | undefined; } -export interface ILinkifier { +export interface ILinkifier extends IDisposable { onShowLinkUnderline: IEvent; onHideLinkUnderline: IEvent; onLinkTooltip: IEvent; @@ -201,7 +201,6 @@ export interface ILinkifier { linkifyRows(start: number, end: number): void; registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): boolean; - dispose(): void; } interface ILinkState { @@ -213,14 +212,13 @@ export interface ILinkWithState { state?: ILinkState; } -export interface ILinkifier2 { +export interface ILinkifier2 extends IDisposable { onShowLinkUnderline: IEvent; onHideLinkUnderline: IEvent; readonly currentLink: ILinkWithState | undefined; attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void; registerLinkProvider(linkProvider: ILinkProvider): IDisposable; - dispose(): void; } export interface ILinkMatcherOptions { From 52dc6a456c692b0fd23538a81750927b1be653f5 Mon Sep 17 00:00:00 2001 From: Svante Boberg Date: Wed, 27 Jul 2022 14:34:00 +0200 Subject: [PATCH 19/30] Remove redundant disposal --- src/browser/Linkifier2.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 8d7cbee6..9c978949 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -40,8 +40,6 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { public dispose(): void { super.dispose(); - this._onShowLinkUnderline.dispose(); - this._onHideLinkUnderline.dispose(); this._lastMouseEvent = undefined; } From a55728dd28f45f3da643aa38196a616e1334fcb1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 05:41:00 -0700 Subject: [PATCH 20/30] Add duration-based smooth scroll Fixes #1140 --- src/browser/Viewport.ts | 50 ++++++++++++++++++++++++++- src/common/services/OptionsService.ts | 1 + src/common/services/Services.ts | 1 + typings/xterm-headless.d.ts | 9 ++++- typings/xterm.d.ts | 6 ++++ 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 1eb9dc4e..65ccc333 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -36,6 +36,9 @@ export class Viewport extends Disposable implements IViewport { private _refreshAnimationFrame: number | null = null; private _ignoreNextScrollEvent: boolean = false; + private _lastSmoothScrollOrigin?: number = undefined; + private _lastSmoothScrollTarget?: number = undefined; + private _lastSmoothScrollStartTime?: number = undefined; constructor( private readonly _scrollLines: (amount: number) => void, @@ -168,6 +171,33 @@ export class Viewport extends Disposable implements IViewport { this._scrollLines(diff); } + private _smoothScroll(): void { + // Check valid state + if (this._isDisposed || this._lastSmoothScrollOrigin === undefined || this._lastSmoothScrollTarget === undefined) { + return; + } + + // Calculate position complete + const percent = this._smoothScrollPercent(); + this._viewportElement.scrollTop = this._lastSmoothScrollOrigin + Math.round(percent * (this._lastSmoothScrollTarget - this._lastSmoothScrollOrigin)); + + // Continue or finish smooth scroll + if (percent < 1) { + window.requestAnimationFrame(() => this._smoothScroll()); + } else { + this._lastSmoothScrollStartTime = undefined; + this._lastSmoothScrollOrigin = undefined; + this._lastSmoothScrollTarget = undefined; + } + } + + private _smoothScrollPercent(): number { + if (!this._optionsService.rawOptions.smoothScrollingDuration || !this._lastSmoothScrollStartTime) { + return 1; + } + return Math.max(Math.min((Date.now() - this._lastSmoothScrollStartTime) / this._optionsService.rawOptions.smoothScrollingDuration, 1), 0); + } + /** * Handles bubbling of scroll event in case the viewport has reached top or bottom * @param ev The scroll event. @@ -196,7 +226,25 @@ export class Viewport extends Disposable implements IViewport { if (amount === 0) { return false; } - this._viewportElement.scrollTop += amount; + if (!this._optionsService.rawOptions.smoothScrollingDuration) { + this._viewportElement.scrollTop += amount; + } else { + this._lastSmoothScrollStartTime = Date.now(); + if (this._smoothScrollPercent() < 1) { + this._lastSmoothScrollOrigin = this._viewportElement.scrollTop; + if (this._lastSmoothScrollTarget === undefined) { + this._lastSmoothScrollTarget = this._viewportElement.scrollTop + amount; + } else { + this._lastSmoothScrollTarget += amount; + } + this._lastSmoothScrollTarget = Math.max(Math.min(this._lastSmoothScrollTarget, this._viewportElement.scrollHeight), 0); + this._smoothScroll(); + } else { + this._lastSmoothScrollStartTime = undefined; + this._lastSmoothScrollOrigin = undefined; + this._lastSmoothScrollTarget = undefined; + } + } return this._bubbleScroll(ev, amount); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 4f9600a4..32358bdd 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -37,6 +37,7 @@ export const DEFAULT_OPTIONS: Readonly = { scrollback: 1000, scrollSensitivity: 1, screenReaderMode: false, + smoothScrollingDuration: 125, macOptionIsMeta: false, macOptionClickForcesSelection: false, minimumContrastRatio: 1, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index fab8435a..72418ee2 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -241,6 +241,7 @@ export interface ITerminalOptions { screenReaderMode: boolean; scrollback: number; scrollSensitivity: number; + smoothScrollingDuration: number; tabStopWidth: number; theme: ITheme; windowsMode: boolean; diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 26a01e4c..cc08039e 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -182,10 +182,17 @@ declare module 'xterm-headless' { scrollback?: number; /** - * The scrolling speed multiplier used for adjusting normal scrolling speed. + * The duration to smoothly scroll between the origin and the target in + * milliseconds. Set to 0 to disable smooth scrolling and scroll instantly. */ scrollSensitivity?: number; + /** + * The duration to smoothly scroll between the origin and the target. Set + * this to 0 to disable smooth scrolling and scroll instantly. + */ + smoothScrollingDuration?: number; + /** * The size of tab stops in the terminal. */ diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a3f47300..afa138d7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -233,6 +233,12 @@ declare module 'xterm' { */ scrollSensitivity?: number; + /** + * The duration to smoothly scroll between the origin and the target in + * milliseconds. Set to 0 to disable smooth scrolling and scroll instantly. + */ + smoothScrollingDuration?: number; + /** * The size of tab stops in the terminal. */ From fb4cb0f140a42d61359048eded4b74aa14645c84 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 05:44:34 -0700 Subject: [PATCH 21/30] Reduce smooth scroll GC --- src/browser/Viewport.ts | 48 +++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 65ccc333..c1afabd3 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -13,6 +13,12 @@ import { IRenderDimensions } from 'browser/renderer/Types'; const FALLBACK_SCROLL_BAR_WIDTH = 15; +interface ISmoothScrollState { + startTime: number; + origin: number; + target: number; +} + /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. * Logic for the virtual scroll bar is included in this object. @@ -36,9 +42,11 @@ export class Viewport extends Disposable implements IViewport { private _refreshAnimationFrame: number | null = null; private _ignoreNextScrollEvent: boolean = false; - private _lastSmoothScrollOrigin?: number = undefined; - private _lastSmoothScrollTarget?: number = undefined; - private _lastSmoothScrollStartTime?: number = undefined; + private _smoothScrollState: ISmoothScrollState = { + startTime: 0, + origin: -1, + target: -1 + }; constructor( private readonly _scrollLines: (amount: number) => void, @@ -173,29 +181,33 @@ export class Viewport extends Disposable implements IViewport { private _smoothScroll(): void { // Check valid state - if (this._isDisposed || this._lastSmoothScrollOrigin === undefined || this._lastSmoothScrollTarget === undefined) { + if (this._isDisposed || this._smoothScrollState.origin === -1 || this._smoothScrollState.target === -1) { return; } // Calculate position complete const percent = this._smoothScrollPercent(); - this._viewportElement.scrollTop = this._lastSmoothScrollOrigin + Math.round(percent * (this._lastSmoothScrollTarget - this._lastSmoothScrollOrigin)); + this._viewportElement.scrollTop = this._smoothScrollState.origin + Math.round(percent * (this._smoothScrollState.target - this._smoothScrollState.origin)); // Continue or finish smooth scroll if (percent < 1) { window.requestAnimationFrame(() => this._smoothScroll()); } else { - this._lastSmoothScrollStartTime = undefined; - this._lastSmoothScrollOrigin = undefined; - this._lastSmoothScrollTarget = undefined; + this._clearSmoothScrollState(); } } private _smoothScrollPercent(): number { - if (!this._optionsService.rawOptions.smoothScrollingDuration || !this._lastSmoothScrollStartTime) { + if (!this._optionsService.rawOptions.smoothScrollingDuration || !this._smoothScrollState.startTime) { return 1; } - return Math.max(Math.min((Date.now() - this._lastSmoothScrollStartTime) / this._optionsService.rawOptions.smoothScrollingDuration, 1), 0); + return Math.max(Math.min((Date.now() - this._smoothScrollState.startTime) / this._optionsService.rawOptions.smoothScrollingDuration, 1), 0); + } + + private _clearSmoothScrollState(): void { + this._smoothScrollState.startTime = 0; + this._smoothScrollState.origin = -1; + this._smoothScrollState.target = -1; } /** @@ -229,20 +241,18 @@ export class Viewport extends Disposable implements IViewport { if (!this._optionsService.rawOptions.smoothScrollingDuration) { this._viewportElement.scrollTop += amount; } else { - this._lastSmoothScrollStartTime = Date.now(); + this._smoothScrollState.startTime = Date.now(); if (this._smoothScrollPercent() < 1) { - this._lastSmoothScrollOrigin = this._viewportElement.scrollTop; - if (this._lastSmoothScrollTarget === undefined) { - this._lastSmoothScrollTarget = this._viewportElement.scrollTop + amount; + this._smoothScrollState.origin = this._viewportElement.scrollTop; + if (this._smoothScrollState.target === -1) { + this._smoothScrollState.target = this._viewportElement.scrollTop + amount; } else { - this._lastSmoothScrollTarget += amount; + this._smoothScrollState.target += amount; } - this._lastSmoothScrollTarget = Math.max(Math.min(this._lastSmoothScrollTarget, this._viewportElement.scrollHeight), 0); + this._smoothScrollState.target = Math.max(Math.min(this._smoothScrollState.target, this._viewportElement.scrollHeight), 0); this._smoothScroll(); } else { - this._lastSmoothScrollStartTime = undefined; - this._lastSmoothScrollOrigin = undefined; - this._lastSmoothScrollTarget = undefined; + this._clearSmoothScrollState(); } } return this._bubbleScroll(ev, amount); From 4b8e7a57683a2c39e1b838ed59697850593185c0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 05:45:56 -0700 Subject: [PATCH 22/30] Change default to 0 --- src/browser/Viewport.ts | 6 +++--- src/common/services/OptionsService.ts | 2 +- src/common/services/Services.ts | 2 +- typings/xterm-headless.d.ts | 2 +- typings/xterm.d.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index c1afabd3..6cff1f98 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -198,10 +198,10 @@ export class Viewport extends Disposable implements IViewport { } private _smoothScrollPercent(): number { - if (!this._optionsService.rawOptions.smoothScrollingDuration || !this._smoothScrollState.startTime) { + if (!this._optionsService.rawOptions.smoothScrollDuration || !this._smoothScrollState.startTime) { return 1; } - return Math.max(Math.min((Date.now() - this._smoothScrollState.startTime) / this._optionsService.rawOptions.smoothScrollingDuration, 1), 0); + return Math.max(Math.min((Date.now() - this._smoothScrollState.startTime) / this._optionsService.rawOptions.smoothScrollDuration, 1), 0); } private _clearSmoothScrollState(): void { @@ -238,7 +238,7 @@ export class Viewport extends Disposable implements IViewport { if (amount === 0) { return false; } - if (!this._optionsService.rawOptions.smoothScrollingDuration) { + if (!this._optionsService.rawOptions.smoothScrollDuration) { this._viewportElement.scrollTop += amount; } else { this._smoothScrollState.startTime = Date.now(); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 32358bdd..87d04ff9 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -37,7 +37,7 @@ export const DEFAULT_OPTIONS: Readonly = { scrollback: 1000, scrollSensitivity: 1, screenReaderMode: false, - smoothScrollingDuration: 125, + smoothScrollDuration: 0, macOptionIsMeta: false, macOptionClickForcesSelection: false, minimumContrastRatio: 1, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 72418ee2..15cfd8ea 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -241,7 +241,7 @@ export interface ITerminalOptions { screenReaderMode: boolean; scrollback: number; scrollSensitivity: number; - smoothScrollingDuration: number; + smoothScrollDuration: number; tabStopWidth: number; theme: ITheme; windowsMode: boolean; diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index cc08039e..85433829 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -191,7 +191,7 @@ declare module 'xterm-headless' { * The duration to smoothly scroll between the origin and the target. Set * this to 0 to disable smooth scrolling and scroll instantly. */ - smoothScrollingDuration?: number; + smoothScrollDuration?: number; /** * The size of tab stops in the terminal. diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index afa138d7..1413f66b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -237,7 +237,7 @@ declare module 'xterm' { * The duration to smoothly scroll between the origin and the target in * milliseconds. Set to 0 to disable smooth scrolling and scroll instantly. */ - smoothScrollingDuration?: number; + smoothScrollDuration?: number; /** * The size of tab stops in the terminal. From 78bfe0b89d5a1556f693b42257536c9d9dac6da6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 05:47:39 -0700 Subject: [PATCH 23/30] Revert accidental setting change --- typings/xterm-headless.d.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 85433829..07c1749d 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -182,16 +182,15 @@ declare module 'xterm-headless' { scrollback?: number; /** - * The duration to smoothly scroll between the origin and the target in - * milliseconds. Set to 0 to disable smooth scrolling and scroll instantly. + * The scrolling speed multiplier used for adjusting normal scrolling speed. */ scrollSensitivity?: number; /** - * The duration to smoothly scroll between the origin and the target. Set - * this to 0 to disable smooth scrolling and scroll instantly. + * The duration to smoothly scroll between the origin and the target in + * milliseconds. Set to 0 to disable smooth scrolling and scroll instantly. */ - smoothScrollDuration?: number; + smoothScrollDuration?: number; /** * The size of tab stops in the terminal. From 0b0662c4d9d5bb0cd60f84dfab0c3e697003e8e6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 07:27:27 -0700 Subject: [PATCH 24/30] xterm-addon-canvas@0.1.0 --- addons/xterm-addon-canvas/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-canvas/package.json b/addons/xterm-addon-canvas/package.json index 3d08e9db..ba19e674 100644 --- a/addons/xterm-addon-canvas/package.json +++ b/addons/xterm-addon-canvas/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-canvas", - "version": "0.12.0", + "version": "0.1.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" From 1be14e4338cf174740414752ad1d7e9770f143c9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 09:09:44 -0700 Subject: [PATCH 25/30] Fix version of published addons --- bin/publish.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index cb6c836a..78ebde13 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -98,9 +98,13 @@ function getNextBetaVersion(packageJson) { process.exit(1); } const tag = 'beta'; - // const stableVersion = packageJson.version.split('.'); - // const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; - const nextStableVersion = `5.0.0`; + let nextStableVersion; + if (packageJson.name = 'xterm') { + nextStableVersion = `5.0.0`; + } else { + const stableVersion = packageJson.version.split('.'); + nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; + } const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag); if (publishedVersions.length === 0) { return `${nextStableVersion}-${tag}.1`; From d9880c0270363d58a50a1b1f95a27571dfea19f4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 10:20:57 -0700 Subject: [PATCH 26/30] Fix publishing addons as xterm --- bin/publish.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index cb6c836a..6b5abcd9 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -98,9 +98,13 @@ function getNextBetaVersion(packageJson) { process.exit(1); } const tag = 'beta'; - // const stableVersion = packageJson.version.split('.'); - // const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; - const nextStableVersion = `5.0.0`; + let nextStableVersion; + if (packageJson.name === 'xterm') { + nextStableVersion = `5.0.0`; + } else { + const stableVersion = packageJson.version.split('.'); + nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; + } const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag); if (publishedVersions.length === 0) { return `${nextStableVersion}-${tag}.1`; From 8df878b8a9c9d9ffa4627fb5397e1c91f35f377d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 10:45:43 -0700 Subject: [PATCH 27/30] Clarify you shouldn't use the object after Terminal.dispose Fixes #3939 --- typings/xterm-headless.d.ts | 7 ++++--- typings/xterm.d.ts | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 666d45c3..d39a7098 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -637,9 +637,10 @@ declare module 'xterm-headless' { registerMarker(cursorYOffset?: number): IMarker | undefined; /* - * Disposes of the terminal, detaching it from the DOM and removing any - * active listeners. - */ + * Disposes of the terminal, detaching it from the DOM and removing any + * active listeners. Once the terminal is disposed it should not be used + * again. + */ dispose(): void; /** diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 4890b4dd..8c4e7735 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -952,7 +952,8 @@ declare module 'xterm' { /* * Disposes of the terminal, detaching it from the DOM and removing any - * active listeners. + * active listeners. Once the terminal is disposed it should not be used + * again. */ dispose(): void; From 9e650cea9ff221fee7275be65443eb2905cd7fff Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 11:00:08 -0700 Subject: [PATCH 28/30] Fix service access in canvas addon --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 10 ++++++---- addons/xterm-addon-canvas/src/CanvasRenderer.ts | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 8b8dcee6..26fad8bd 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { IRenderService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; -import { IBufferService, IInstantiationService } from 'common/services/Services'; +import { IBufferService, IInstantiationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; export class CanvasAddon implements ITerminalAddon { @@ -19,12 +19,14 @@ export class CanvasAddon implements ITerminalAddon { } this._terminal = terminal; const instantiationService: IInstantiationService = (terminal as any)._core._instantiationService; - const bufferService: IBufferService = (terminal as any)._core._renderService; + const bufferService: IBufferService = (terminal as any)._core._bufferService; const renderService: IRenderService = (terminal as any)._core._renderService; + const charSizeService: ICharSizeService = (terminal as any)._core._charSizeService; + const optionsService: IOptionsService = (terminal as any)._core.optionsService; const colors: IColorSet = (terminal as any)._core._colorManager.colors; const screenElement: HTMLElement = (terminal as any)._core.screenElement; const linkifier = (terminal as any)._core.linkifier2; - this._renderer = instantiationService.createInstance(CanvasRenderer, colors, screenElement, linkifier); + this._renderer = new CanvasRenderer(colors, screenElement, linkifier, instantiationService, bufferService, charSizeService, optionsService); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 238d9bbb..7239005a 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -34,10 +34,10 @@ export class CanvasRenderer extends Disposable implements IRenderer { private _colors: IColorSet, private readonly _screenElement: HTMLElement, linkifier2: ILinkifier2, - @IInstantiationService instantiationService: IInstantiationService, - @IBufferService private readonly _bufferService: IBufferService, - @ICharSizeService private readonly _charSizeService: ICharSizeService, - @IOptionsService private readonly _optionsService: IOptionsService + instantiationService: IInstantiationService, + private readonly _bufferService: IBufferService, + private readonly _charSizeService: ICharSizeService, + private readonly _optionsService: IOptionsService ) { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; From 006249ce65152708b86d04643f77817b9704e249 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 11:16:12 -0700 Subject: [PATCH 29/30] Make cols/rows only usable in the ctor Fixes #3825 --- addons/xterm-addon-ligatures/src/index.ts | 2 +- test/api/TestUtils.ts | 4 +-- typings/xterm.d.ts | 30 ++++++++++++++--------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/addons/xterm-addon-ligatures/src/index.ts b/addons/xterm-addon-ligatures/src/index.ts index c54f3b50..058d9454 100644 --- a/addons/xterm-addon-ligatures/src/index.ts +++ b/addons/xterm-addon-ligatures/src/index.ts @@ -54,7 +54,7 @@ export function enableLigatures(term: Terminal): void { // Only refresh things if we actually found a font if (f) { - term.refresh(0, term.options.rows! - 1); + term.refresh(0, term.rows - 1); } } }) diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index 5731009b..3fd0648e 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -5,7 +5,7 @@ import * as playwright from 'playwright'; import deepEqual = require('deep-equal'); -import { ITerminalOptions } from 'xterm'; +import { ITerminalInitOnlyOptions, ITerminalOptions } from 'xterm'; import { deepStrictEqual, fail } from 'assert'; export async function pollFor(page: playwright.Page, evalOrFn: string | (() => Promise), val: T, preFn?: () => Promise, maxDuration?: number): Promise { @@ -43,7 +43,7 @@ export async function timeout(ms: number): Promise { return new Promise(r => setTimeout(r, ms)); } -export async function openTerminal(page: playwright.Page, options: ITerminalOptions = {}): Promise { +export async function openTerminal(page: playwright.Page, options: ITerminalOptions & ITerminalInitOnlyOptions = {}): Promise { await page.evaluate(`window.term = new Terminal(${JSON.stringify({ allowProposedApi: true, ...options })})`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.waitForSelector('.xterm-rows'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 4890b4dd..8e9b69c2 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -21,7 +21,7 @@ declare module 'xterm' { export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off'; /** - * An object containing start up options for the terminal. + * An object containing options for the terminal. */ export interface ITerminalOptions { /** @@ -55,11 +55,6 @@ declare module 'xterm' { */ convertEol?: boolean; - /** - * The number of columns in the terminal. - */ - cols?: number; - /** * Whether the cursor blinks. */ @@ -177,11 +172,6 @@ declare module 'xterm' { */ rightClickSelectsWord?: boolean; - /** - * The number of rows in the terminal. - */ - rows?: number; - /** * Whether screen reader support is enabled. When on this will expose * supporting elements in the DOM to support NVDA on Windows and VoiceOver @@ -248,6 +238,22 @@ declare module 'xterm' { overviewRulerWidth?: number; } + /** + * An object containing additional options for the terminal that can only be + * set on start up. + */ + export interface ITerminalInitOnlyOptions { + /** + * The number of columns in the terminal. + */ + cols?: number; + + /** + * The number of rows in the terminal. + */ + rows?: number; + } + /** * Contains colors to theme the terminal with. */ @@ -714,7 +720,7 @@ declare module 'xterm' { * * @param options An object containing a set of options. */ - constructor(options?: ITerminalOptions); + constructor(options?: ITerminalOptions & ITerminalInitOnlyOptions); /** * Adds an event listener for when the bell is triggered. From 055bcce18de91f329f61261aa1c81c1da231f7a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 11:27:36 -0700 Subject: [PATCH 30/30] Remove instantiation service from canvas addon completely --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 11 +++++++---- .../xterm-addon-canvas/src/CanvasRenderer.ts | 19 +++++++++++-------- .../src/CursorRenderLayer.ts | 10 +++++----- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 6 +++--- .../src/SelectionRenderLayer.ts | 6 +++--- .../xterm-addon-canvas/src/TextRenderLayer.ts | 8 ++++---- addons/xterm-addon-canvas/src/tsconfig.json | 1 - src/browser/Terminal.ts | 5 +++-- 8 files changed, 36 insertions(+), 30 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 26fad8bd..9fca00a6 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { ICharSizeService, IRenderService } from 'browser/services/Services'; +import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; -import { IBufferService, IInstantiationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; export class CanvasAddon implements ITerminalAddon { @@ -18,15 +18,18 @@ export class CanvasAddon implements ITerminalAddon { throw new Error('Cannot activate CanvasAddon before Terminal.open'); } this._terminal = terminal; - const instantiationService: IInstantiationService = (terminal as any)._core._instantiationService; const bufferService: IBufferService = (terminal as any)._core._bufferService; const renderService: IRenderService = (terminal as any)._core._renderService; + const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService; const charSizeService: ICharSizeService = (terminal as any)._core._charSizeService; + const coreService: ICoreService = (terminal as any)._core.coreService; + const coreBrowserService: ICoreBrowserService = (terminal as any)._core._coreBrowserService; + const decorationService: IDecorationService = (terminal as any)._core._decorationService; const optionsService: IOptionsService = (terminal as any)._core.optionsService; const colors: IColorSet = (terminal as any)._core._colorManager.colors; const screenElement: HTMLElement = (terminal as any)._core.screenElement; const linkifier = (terminal as any)._core.linkifier2; - this._renderer = new CanvasRenderer(colors, screenElement, linkifier, instantiationService, bufferService, charSizeService, optionsService); + this._renderer = new CanvasRenderer(colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 7239005a..c0c332ef 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -11,8 +11,8 @@ import { IRenderLayer } from './Types'; import { LinkRenderLayer } from './LinkRenderLayer'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifier2 } from 'browser/Types'; -import { ICharSizeService } from 'browser/services/Services'; -import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; +import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; +import { IBufferService, IOptionsService, IInstantiationService, IDecorationService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from './atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; @@ -34,18 +34,21 @@ export class CanvasRenderer extends Disposable implements IRenderer { private _colors: IColorSet, private readonly _screenElement: HTMLElement, linkifier2: ILinkifier2, - instantiationService: IInstantiationService, private readonly _bufferService: IBufferService, private readonly _charSizeService: ICharSizeService, - private readonly _optionsService: IOptionsService + private readonly _optionsService: IOptionsService, + characterJoinerService: ICharacterJoinerService, + coreService: ICoreService, + coreBrowserService: ICoreBrowserService, + decorationService: IDecorationService ) { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ - instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id), - instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id), - instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier2), - instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw) + new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService), + new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, this._optionsService, decorationService), + new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService), + new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, coreBrowserService, decorationService) ]; this.dimensions = { scaledCharWidth: 0, diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 60c1301d..4d7e0570 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -37,11 +37,11 @@ export class CursorRenderLayer extends BaseRenderLayer { colors: IColorSet, rendererId: number, private _onRequestRedraw: IEventEmitter, - @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService, - @ICoreService private readonly _coreService: ICoreService, - @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, - @IDecorationService decorationService: IDecorationService + bufferService: IBufferService, + optionsService: IOptionsService, + private readonly _coreService: ICoreService, + private readonly _coreBrowserService: ICoreBrowserService, + decorationService: IDecorationService ) { super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); this._state = { diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index f92f6d54..e514e3d0 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -19,9 +19,9 @@ export class LinkRenderLayer extends BaseRenderLayer { colors: IColorSet, rendererId: number, linkifier2: ILinkifier2, - @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService, - @IDecorationService decorationService: IDecorationService + bufferService: IBufferService, + optionsService: IOptionsService, + decorationService: IDecorationService ) { super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 3738591e..3b25ec8f 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -23,9 +23,9 @@ export class SelectionRenderLayer extends BaseRenderLayer { zIndex: number, colors: IColorSet, rendererId: number, - @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService, - @IDecorationService decorationService: IDecorationService + bufferService: IBufferService, + optionsService: IOptionsService, + decorationService: IDecorationService ) { super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); this._clearState(); diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index ea5fea0b..d44e4b81 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -35,10 +35,10 @@ export class TextRenderLayer extends BaseRenderLayer { colors: IColorSet, alpha: boolean, rendererId: number, - @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService, - @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, - @IDecorationService decorationService: IDecorationService + bufferService: IBufferService, + optionsService: IOptionsService, + private readonly _characterJoinerService: ICharacterJoinerService, + decorationService: IDecorationService ) { super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService); this._state = new GridCache(); diff --git a/addons/xterm-addon-canvas/src/tsconfig.json b/addons/xterm-addon-canvas/src/tsconfig.json index d954ec49..206d52ae 100644 --- a/addons/xterm-addon-canvas/src/tsconfig.json +++ b/addons/xterm-addon-canvas/src/tsconfig.json @@ -21,7 +21,6 @@ }, "strict": true, "downlevelIteration": true, - "experimentalDecorators": true, "types": [ "../../../node_modules/@types/mocha" ] diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 3fd421ad..b072c92c 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -81,6 +81,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // browser services private _decorationService: DecorationService; private _charSizeService: ICharSizeService | undefined; + private _coreBrowserService: ICoreBrowserService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; @@ -494,8 +495,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur())); this._helperContainer.appendChild(this.textarea); - const coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea); - this._instantiationService.setService(ICoreBrowserService, coreBrowserService); + this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea); + this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService); this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer); this._instantiationService.setService(ICharSizeService, this._charSizeService);