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/" diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 9a6464c1..14dce8d6 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -30,9 +30,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; @@ -49,6 +49,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/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 8b8dcee6..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 { 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 } from 'common/services/Services'; +import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; export class CanvasAddon implements ITerminalAddon { @@ -18,13 +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._renderService; + 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 = instantiationService.createInstance(CanvasRenderer, colors, screenElement, linkifier); + 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 88c81ea7..c0c332ef 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -11,10 +11,11 @@ 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'; let nextRendererId = 1; @@ -33,18 +34,21 @@ 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 + private readonly _bufferService: IBufferService, + private readonly _charSizeService: ICharSizeService, + 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, @@ -62,6 +66,9 @@ export class CanvasRenderer extends Disposable implements IRenderer { }; this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); + + this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); + this.onOptionsChanged(); } @@ -167,53 +174,32 @@ export class CanvasRenderer 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 = height; + this.dimensions.scaledCanvasWidth = width; + // 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/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 2fa82c0d..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(); @@ -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 { diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index faeee19f..625bcce9 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -36,10 +36,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/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index 6f5aff85..dda6052a 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -57,6 +57,8 @@ export interface IRenderer extends IDisposable { } export interface IRenderLayer extends IDisposable { + readonly canvas: HTMLCanvasElement; + /** * Called when the terminal loses focus. */ 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/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() { diff --git a/addons/xterm-addon-ligatures/src/font.ts b/addons/xterm-addon-ligatures/src/font.ts index 4075cc6c..5c5f0a6c 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 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(); + 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 { @@ -90,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 }); } 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/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 85d32870..01c6799c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -11,11 +11,12 @@ 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, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; @@ -96,6 +97,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); })); + this.register(observeDevicePixelDimensions(this._canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this._core.screenElement!.appendChild(this._canvas); @@ -518,67 +520,63 @@ 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 - // 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 + // 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 { + 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(); + } + private _requestRedrawViewport(): void { this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index f01fadd8..f5b0dc1c 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -416,15 +416,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; } @@ -460,7 +460,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) { @@ -604,7 +609,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; @@ -624,15 +629,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; @@ -645,6 +649,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; diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index c80b4c56..fd686cc7 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -54,6 +54,12 @@ export class CursorRenderLayer extends BaseRenderLayer { this.onOptionsChanged(terminal); } + public override dispose(): void { + 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 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`; diff --git a/package.json b/package.json index cb0c1919..a270a52f 100644 --- a/package.json +++ b/package.json @@ -79,8 +79,8 @@ "playwright": "^1.22.1", "source-map-loader": "^3.0.0", "source-map-support": "^0.5.20", - "ts-loader": "^9.1.2", - "typescript": "^4.4.4", + "ts-loader": "^9.3.1", + "typescript": "4.7", "utf8": "^3.0.0", "webpack": "^5.61.0", "webpack-cli": "^4.9.1", diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index dae9acfa..9c978949 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -38,6 +38,11 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { this.register(getDisposeArrayDisposable(this._linkCacheDisposables)); } + public dispose(): void { + super.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 ac6f887e..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); @@ -586,11 +587,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 f7011200..f0e70437 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -168,7 +168,7 @@ export interface ILinkWithState { state?: ILinkState; } -export interface ILinkifier2 { +export interface ILinkifier2 extends IDisposable { onShowLinkUnderline: IEvent; onHideLinkUnderline: IEvent; readonly currentLink: ILinkWithState | undefined; diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 1eb9dc4e..6cff1f98 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,6 +42,11 @@ export class Viewport extends Disposable implements IViewport { private _refreshAnimationFrame: number | null = null; private _ignoreNextScrollEvent: boolean = false; + private _smoothScrollState: ISmoothScrollState = { + startTime: 0, + origin: -1, + target: -1 + }; constructor( private readonly _scrollLines: (amount: number) => void, @@ -168,6 +179,37 @@ export class Viewport extends Disposable implements IViewport { this._scrollLines(diff); } + private _smoothScroll(): void { + // Check valid state + if (this._isDisposed || this._smoothScrollState.origin === -1 || this._smoothScrollState.target === -1) { + return; + } + + // Calculate position complete + const percent = this._smoothScrollPercent(); + 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._clearSmoothScrollState(); + } + } + + private _smoothScrollPercent(): number { + if (!this._optionsService.rawOptions.smoothScrollDuration || !this._smoothScrollState.startTime) { + return 1; + } + return Math.max(Math.min((Date.now() - this._smoothScrollState.startTime) / this._optionsService.rawOptions.smoothScrollDuration, 1), 0); + } + + private _clearSmoothScrollState(): void { + this._smoothScrollState.startTime = 0; + this._smoothScrollState.origin = -1; + this._smoothScrollState.target = -1; + } + /** * Handles bubbling of scroll event in case the viewport has reached top or bottom * @param ev The scroll event. @@ -196,7 +238,23 @@ export class Viewport extends Disposable implements IViewport { if (amount === 0) { return false; } - this._viewportElement.scrollTop += amount; + if (!this._optionsService.rawOptions.smoothScrollDuration) { + this._viewportElement.scrollTop += amount; + } else { + this._smoothScrollState.startTime = Date.now(); + if (this._smoothScrollPercent() < 1) { + this._smoothScrollState.origin = this._viewportElement.scrollTop; + if (this._smoothScrollState.target === -1) { + this._smoothScrollState.target = this._viewportElement.scrollTop + amount; + } else { + this._smoothScrollState.target += amount; + } + this._smoothScrollState.target = Math.max(Math.min(this._smoothScrollState.target, this._viewportElement.scrollHeight), 0); + this._smoothScroll(); + } else { + this._clearSmoothScrollState(); + } + } return this._bubbleScroll(ev, amount); } 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/common/Color.ts b/src/common/Color.ts index f564d66d..a66e39c1 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -200,7 +200,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; } @@ -209,7 +209,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; } 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; } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 550adb31..ab9edfbf 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -28,6 +28,7 @@ export const DEFAULT_OPTIONS: Readonly = { scrollback: 1000, scrollSensitivity: 1, screenReaderMode: false, + smoothScrollDuration: 0, macOptionIsMeta: false, macOptionClickForcesSelection: false, minimumContrastRatio: 1, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 709e171f..248d5644 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -232,6 +232,7 @@ export interface ITerminalOptions { screenReaderMode: boolean; scrollback: number; scrollSensitivity: number; + smoothScrollDuration: number; tabStopWidth: number; theme: ITheme; windowsMode: boolean; 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-headless.d.ts b/typings/xterm-headless.d.ts index 76527e37..d39a7098 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -169,6 +169,12 @@ declare module 'xterm-headless' { */ 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. + */ + smoothScrollDuration?: number; + /** * The size of tab stops in the terminal. */ @@ -631,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 43e52ee6..6619d4e6 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 @@ -201,6 +191,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. + */ + smoothScrollDuration?: number; + /** * The size of tab stops in the terminal. */ @@ -242,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. */ @@ -708,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. @@ -946,7 +958,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; diff --git a/yarn.lock b/yarn.lock index 01fb6061..315dddab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3760,10 +3760,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" @@ -3831,16 +3831,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"