diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 215aa988..bbbb413f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,39 @@ jobs: - name: Lint API run: yarn lint-api + test-unit-coverage: + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: 'yarn' + - name: Install dependencies + run: | + yarn --frozen-lockfile + yarn install-addons + - uses: actions/download-artifact@v3 + with: + name: build-artifacts + - name: Unzip artifacts (Linux, macOS) + if: runner.os != 'Windows' + run: unzip -o compressed-build.zip + - name: Unzip artifacts (Windows) + if: runner.os == 'Windows' + run: 7z x compressed-build.zip -aoa -o${{ github.workspace }} + - name: Print directory structure + run: ls -R + - name: Unit test coverage + run: | + yarn test-unit-coverage --forbid-only + EXIT_CODE=$? + ./node_modules/.bin/nyc report --reporter=cobertura + exit $EXIT_CODE + test-unit-parallel: timeout-minutes: 20 strategy: diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ff29bbf1..8c199336 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,24 +3,24 @@ * @license MIT */ +import { ReadonlyColorSet } from 'browser/Types'; +import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; +import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { ReadonlyColorSet } from 'browser/Types'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; +import { isSafari } from 'common/Platform'; +import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ICellData, IDisposable } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; -import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; -import { Disposable, toDisposable } from 'common/Lifecycle'; -import { isSafari } from 'common/Platform'; -import { EventEmitter, forwardEvent } from 'common/EventEmitter'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -37,7 +37,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer private _bitmapGenerator: (BitmapGenerator | undefined)[] = []; protected _charAtlas!: ITextureAtlas; - private _charAtlasDisposable?: IDisposable; + protected _charAtlasDisposable = this.register(new MutableDisposable()); public get canvas(): HTMLCanvasElement { return this._canvas; } public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].canvas!; } @@ -74,7 +74,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this.register(toDisposable(() => { this._canvas.remove(); - this._charAtlas?.dispose(); })); } @@ -122,9 +121,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) { return; } - this._charAtlasDisposable?.dispose(); this._charAtlas = acquireTextureAtlas(this._terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr); - this._charAtlasDisposable = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); + this._charAtlasDisposable.value = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); this._charAtlas.warmUp(); for (let i = 0; i < this._charAtlas.pages.length; i++) { this._bitmapGenerator[i] = new BitmapGenerator(this._charAtlas.pages[i].canvas); diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 9f4ac896..c5174f86 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -7,7 +7,7 @@ import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStat import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; -import { toDisposable } from 'common/Lifecycle'; +import { MutableDisposable } from 'common/Lifecycle'; import { isFirefox } from 'common/Platform'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; @@ -26,7 +26,7 @@ interface ICursorState { export class CursorRenderLayer extends BaseRenderLayer { private _state: ICursorState; private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void}; - private _cursorBlinkStateManager: CursorBlinkStateManager | undefined; + private _cursorBlinkStateManager: MutableDisposable = this.register(new MutableDisposable()); private _cell: ICellData = new CellData(); constructor( @@ -57,10 +57,6 @@ export class CursorRenderLayer extends BaseRenderLayer { }; this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this._handleOptionsChanged(); - this.register(toDisposable(() => { - this._cursorBlinkStateManager?.dispose(); - this._cursorBlinkStateManager = undefined; - })); } public resize(dim: IRenderDimensions): void { @@ -77,28 +73,27 @@ export class CursorRenderLayer extends BaseRenderLayer { public reset(): void { this._clearCursor(); - this._cursorBlinkStateManager?.restartBlinkAnimation(); + this._cursorBlinkStateManager.value?.restartBlinkAnimation(); this._handleOptionsChanged(); } public handleBlur(): void { - this._cursorBlinkStateManager?.pause(); + this._cursorBlinkStateManager.value?.pause(); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } public handleFocus(): void { - this._cursorBlinkStateManager?.resume(); + this._cursorBlinkStateManager.value?.resume(); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } private _handleOptionsChanged(): void { if (this._optionsService.rawOptions.cursorBlink) { - if (!this._cursorBlinkStateManager) { - this._cursorBlinkStateManager = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService); + if (!this._cursorBlinkStateManager.value) { + this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService); } } else { - this._cursorBlinkStateManager?.dispose(); - this._cursorBlinkStateManager = undefined; + this._cursorBlinkStateManager.clear(); } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal @@ -106,14 +101,14 @@ export class CursorRenderLayer extends BaseRenderLayer { } public handleCursorMove(): void { - this._cursorBlinkStateManager?.restartBlinkAnimation(); + this._cursorBlinkStateManager.value?.restartBlinkAnimation(); } public handleGridChanged(startRow: number, endRow: number): void { - if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { + if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isPaused) { this._render(false); } else { - this._cursorBlinkStateManager.restartBlinkAnimation(); + this._cursorBlinkStateManager.value.restartBlinkAnimation(); } } @@ -159,7 +154,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } // Don't draw the cursor if it's blinking - if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + if (this._cursorBlinkStateManager.value && !this._cursorBlinkStateManager.value.isCursorVisible) { this._clearCursor(); return; } diff --git a/addons/xterm-addon-image/src/ImageRenderer.ts b/addons/xterm-addon-image/src/ImageRenderer.ts index d225315e..9b528efe 100644 --- a/addons/xterm-addon-image/src/ImageRenderer.ts +++ b/addons/xterm-addon-image/src/ImageRenderer.ts @@ -6,6 +6,7 @@ import { toRGBA8888 } from 'sixel/lib/Colors'; import { IDisposable } from 'xterm'; import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; const PLACEHOLDER_LENGTH = 4096; @@ -17,12 +18,12 @@ const PLACEHOLDER_HEIGHT = 24; * - add canvas layer to DOM (browser only for now) * - draw image tiles onRender */ -export class ImageRenderer implements IDisposable { +export class ImageRenderer extends Disposable implements IDisposable { public canvas: HTMLCanvasElement | undefined; private _ctx: CanvasRenderingContext2D | null | undefined; private _placeholder: HTMLCanvasElement | undefined; private _placeholderBitmap: ImageBitmap | undefined; - private _optionsRefresh: IDisposable | undefined; + private _optionsRefresh = this.register(new MutableDisposable()); private _oldOpen: ((parent: HTMLElement) => void) | undefined; private _renderService: IRenderService | undefined; private _oldSetRenderer: ((renderer: any) => void) | undefined; @@ -68,6 +69,7 @@ export class ImageRenderer implements IDisposable { constructor(private _terminal: ITerminalExt) { + super(); this._oldOpen = this._terminal._core.open; this._terminal._core.open = (parent: HTMLElement): void => { this._oldOpen?.call(this._terminal._core, parent); @@ -77,32 +79,29 @@ export class ImageRenderer implements IDisposable { this._open(); } // hack to spot fontSize changes - this._optionsRefresh = this._terminal._core.optionsService.onOptionChange(option => { + this._optionsRefresh.value = this._terminal._core.optionsService.onOptionChange(option => { if (option === 'fontSize') { this.rescaleCanvas(); this._renderService?.refreshRows(0, this._terminal.rows); } }); - } - - - public dispose(): void { - this._optionsRefresh?.dispose(); - this.removeLayerFromDom(); - if (this._terminal._core && this._oldOpen) { - this._terminal._core.open = this._oldOpen; - this._oldOpen = undefined; - } - if (this._renderService && this._oldSetRenderer) { - this._renderService.setRenderer = this._oldSetRenderer; - this._oldSetRenderer = undefined; - } - this._renderService = undefined; - this.canvas = undefined; - this._ctx = undefined; - this._placeholderBitmap?.close(); - this._placeholderBitmap = undefined; - this._placeholder = undefined; + this.register(toDisposable(() => { + this.removeLayerFromDom(); + if (this._terminal._core && this._oldOpen) { + this._terminal._core.open = this._oldOpen; + this._oldOpen = undefined; + } + if (this._renderService && this._oldSetRenderer) { + this._renderService.setRenderer = this._oldSetRenderer; + this._oldSetRenderer = undefined; + } + this._renderService = undefined; + this.canvas = undefined; + this._ctx = undefined; + this._placeholderBitmap?.close(); + this._placeholderBitmap = undefined; + this._placeholder = undefined; + })); } /** diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index cbde1a1b..10fc1ecc 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -5,7 +5,7 @@ import { Terminal, IDisposable, ITerminalAddon, IDecoration } from 'xterm'; import { EventEmitter } from 'common/EventEmitter'; -import { Disposable, toDisposable, disposeArray } from 'common/Lifecycle'; +import { Disposable, toDisposable, disposeArray, MutableDisposable } from 'common/Lifecycle'; export interface ISearchOptions { regex?: boolean; @@ -66,7 +66,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { private _cachedSearchTerm: string | undefined; private _highlightedLines: Set = new Set(); private _highlightDecorations: IHighlight[] = []; - private _selectedDecoration: IHighlight | undefined; + private _selectedDecoration: MutableDisposable = this.register(new MutableDisposable()); private _highlightLimit: number; private _lastSearchOptions: ISearchOptions | undefined; private _highlightTimeout: number | undefined; @@ -110,7 +110,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } public clearDecorations(retainCachedSearchTerm?: boolean): void { - this.clearActiveDecoration(); + this._selectedDecoration.clear(); disposeArray(this._highlightDecorations); this._highlightDecorations = []; this._highlightedLines.clear(); @@ -119,11 +119,6 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } } - public clearActiveDecoration(): void { - this._selectedDecoration?.dispose(); - this._selectedDecoration = undefined; - } - /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -320,8 +315,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon { private _fireResults(searchOptions?: ISearchOptions): void { if (searchOptions?.decorations) { let resultIndex = -1; - if (this._selectedDecoration) { - const selectedMatch = this._selectedDecoration.match; + if (this._selectedDecoration.value) { + const selectedMatch = this._selectedDecoration.value.match; for (let i = 0; i < this._highlightDecorations.length; i++) { const match = this._highlightDecorations[i].match; if (match.row === selectedMatch.row && match.col === selectedMatch.col && match.size === selectedMatch.size) { @@ -642,7 +637,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { */ private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean { const terminal = this._terminal!; - this.clearActiveDecoration(); + this._selectedDecoration.clear(); if (!result) { terminal.clearSelection(); return false; @@ -666,7 +661,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { disposables.push(marker); disposables.push(decoration.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true))); disposables.push(decoration.onDispose(() => disposeArray(disposables))); - this._selectedDecoration = { decoration, match: result, dispose() { decoration.dispose(); } }; + this._selectedDecoration.value = { decoration, match: result, dispose() { decoration.dispose(); } }; } } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 20db5faf..9a8f8ee3 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -20,7 +20,7 @@ import { CellData } from 'common/buffer/CellData'; import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { traceCall } from 'common/services/LogService'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { IDisposable, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; import { GlyphRenderer } from './GlyphRenderer'; import { RectangleRenderer } from './RectangleRenderer'; import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL, RenderModel } from './RenderModel'; @@ -31,7 +31,7 @@ import { IRenderLayer } from './renderLayer/Types'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; private _cursorBlinkStateManager: MutableDisposable = new MutableDisposable(); - private _charAtlasDisposable: IDisposable | undefined; + private _charAtlasDisposable = this.register(new MutableDisposable()); private _charAtlas: ITextureAtlas | undefined; private _devicePixelRatio: number; @@ -41,8 +41,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; - private _rectangleRenderer?: RectangleRenderer; - private _glyphRenderer?: GlyphRenderer; + private _rectangleRenderer: MutableDisposable = this.register(new MutableDisposable()); + private _glyphRenderer: MutableDisposable = this.register(new MutableDisposable()); public readonly dimensions: IRenderDimensions; @@ -128,7 +128,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.appendChild(this._canvas); - [this._rectangleRenderer, this._glyphRenderer] = this._initializeWebGLState(); + [this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState(); this._isAttached = this._coreBrowserService.window.document.body.contains(this._core.screenElement!); @@ -182,10 +182,10 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.style.width = `${this.dimensions.css.canvas.width}px`; this._core.screenElement!.style.height = `${this.dimensions.css.canvas.height}px`; - this._rectangleRenderer?.setDimensions(this.dimensions); - this._rectangleRenderer?.handleResize(); - this._glyphRenderer?.setDimensions(this.dimensions); - this._glyphRenderer?.handleResize(); + this._rectangleRenderer.value?.setDimensions(this.dimensions); + this._rectangleRenderer.value?.handleResize(); + this._glyphRenderer.value?.setDimensions(this.dimensions); + this._glyphRenderer.value?.handleResize(); this._refreshCharAtlas(); @@ -241,17 +241,13 @@ export class WebglRenderer extends Disposable implements IRenderer { * Initializes members dependent on WebGL context state. */ private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] { - // Dispose any previous rectangle and glyph renderers before creating new ones. - this._rectangleRenderer?.dispose(); - this._glyphRenderer?.dispose(); - - this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService)); - this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._gl, this.dimensions)); + this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService); + this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions); // Update dimensions and acquire char atlas this.handleCharSizeChanged(); - return [this._rectangleRenderer, this._glyphRenderer]; + return [this._rectangleRenderer.value, this._glyphRenderer.value]; } /** @@ -275,16 +271,15 @@ export class WebglRenderer extends Disposable implements IRenderer { this._coreBrowserService.dpr ); if (this._charAtlas !== atlas) { - this._charAtlasDisposable?.dispose(); this._onChangeTextureAtlas.fire(atlas.pages[0].canvas); - this._charAtlasDisposable = getDisposeArrayDisposable([ + this._charAtlasDisposable.value = getDisposeArrayDisposable([ forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas), forwardEvent(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas) ]); } this._charAtlas = atlas; this._charAtlas.warmUp(); - this._glyphRenderer?.setAtlas(this._charAtlas); + this._glyphRenderer.value?.setAtlas(this._charAtlas); } /** @@ -295,7 +290,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _clearModel(clearGlyphRenderer: boolean): void { this._model.clear(); if (clearGlyphRenderer) { - this._glyphRenderer?.clear(); + this._glyphRenderer.value?.clear(); } } @@ -340,14 +335,14 @@ export class WebglRenderer extends Disposable implements IRenderer { l.handleGridChanged(this._terminal, start, end); } - if (!this._glyphRenderer || !this._rectangleRenderer) { + if (!this._glyphRenderer.value || !this._rectangleRenderer.value) { return; } // Tell renderer the frame is beginning // upon a model clear also refresh the full viewport model // (also triggered by an atlas page merge, part of #4480) - if (this._glyphRenderer.beginFrame()) { + if (this._glyphRenderer.value.beginFrame()) { this._clearModel(true); this._updateModel(0, this._terminal.rows - 1); } else { @@ -356,10 +351,10 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Render - this._rectangleRenderer?.renderBackgrounds(); - this._glyphRenderer?.render(this._model); + this._rectangleRenderer.value.renderBackgrounds(); + this._glyphRenderer.value.render(this._model); if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isCursorVisible) { - this._rectangleRenderer?.renderCursor(); + this._rectangleRenderer.value.renderCursor(); } } @@ -502,7 +497,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); + this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); if (isJoined) { // Restore work cell @@ -511,7 +506,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Null out non-first cells for (x++; x < lastCharX; x++) { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); + this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); this._model.cells[j] = NULL_CELL_CODE; this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; @@ -521,9 +516,9 @@ export class WebglRenderer extends Disposable implements IRenderer { } } if (modelUpdated) { - this._rectangleRenderer!.updateBackgrounds(this._model); + this._rectangleRenderer.value!.updateBackgrounds(this._model); } - this._rectangleRenderer!.updateCursor(this._model); + this._rectangleRenderer.value!.updateCursor(this._model); } /** diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index f5449c4d..3aaac435 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -49,7 +49,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer })); this.register(toDisposable(() => { this._canvas.remove(); - this._charAtlas?.dispose(); })); } diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 7db2de26..69fd4ffd 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -1106,7 +1106,7 @@ describe('WebGL Renderer Integration Tests', async () => { async function getCellColor(col: number, row: number): Promise { await page.evaluate(` - window.gl = window.term._core._renderService._renderer._gl; + window.gl = window.term._core._renderService._renderer.value._gl; window.result = new Uint8Array(4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( @@ -1120,7 +1120,7 @@ async function getCellColor(col: number, row: number): Promise { async function getCellPixels(col: number, row: number): Promise { await page.evaluate(` - window.gl = window.term._core._renderService._renderer._gl; + window.gl = window.term._core._renderService._renderer.value._gl; window.result = new Uint8Array(window.d.device.cell.width * window.d.device.cell.height * 4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 24dace2f..a092e1bc 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -44,7 +44,7 @@ import { ThemeService } from 'browser/services/ThemeService'; import { color, rgba } from 'common/Color'; import { CoreTerminal } from 'common/CoreTerminal'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; -import { toDisposable } from 'common/Lifecycle'; +import { MutableDisposable, toDisposable } from 'common/Lifecycle'; import * as Browser from 'common/Platform'; import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, IColorEvent, ITerminalOptions, KeyboardResultType, ScrollSource, SpecialColorIndex } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -118,7 +118,7 @@ export class Terminal extends CoreTerminal implements ITerminal { public linkifier2: ILinkifier2; public viewport: IViewport | undefined; private _compositionHelper: ICompositionHelper | undefined; - private _accessibilityManager: AccessibilityManager | undefined; + private _accessibilityManager: MutableDisposable = this.register(new MutableDisposable()); private readonly _onCursorMove = this.register(new EventEmitter()); public readonly onCursorMove = this._onCursorMove.event; @@ -252,12 +252,11 @@ export class Terminal extends CoreTerminal implements ITerminal { private _handleScreenReaderModeOptionChange(value: boolean): void { if (value) { - if (!this._accessibilityManager && this._renderService) { - this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this); + if (!this._accessibilityManager.value && this._renderService) { + this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this); } } else { - this._accessibilityManager?.dispose(); - this._accessibilityManager = undefined; + this._accessibilityManager.clear(); } } @@ -535,7 +534,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.options.screenReaderMode) { // Note that this must be done *after* the renderer is created in order to // ensure the correct order of the dprchange event - this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this); + this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this); } this.register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e))); diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index cface515..c08bc4b1 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -366,11 +366,11 @@ export const powerlineDefinitions: { [index: string]: IVectorShape } = { // Right semi-circle solid '\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL, rightPadding: 1 }, // Right semi-circle line - '\u{E0B5}': { d: 'M0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.STROKE, rightPadding: 1 }, + '\u{E0B5}': { d: 'M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0', type: VectorType.STROKE, rightPadding: 1 }, // Left semi-circle solid '\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL, leftPadding: 1 }, // Left semi-circle line - '\u{E0B7}': { d: 'M1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.STROKE, leftPadding: 1 }, + '\u{E0B7}': { d: 'M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0', type: VectorType.STROKE, leftPadding: 1 }, // Lower left triangle '\u{E0B8}': { d: 'M-.5,-.5 L1.5,1.5 L-.5,1.5', type: VectorType.FILL }, // Backslash separator diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 41f0c9b5..0f18a233 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -10,7 +10,7 @@ import { IRenderDebouncerWithCallback } from 'browser/Types'; import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; import { EventEmitter } from 'common/EventEmitter'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, MutableDisposable } from 'common/Lifecycle'; import { DebouncedIdleTask } from 'common/TaskQueue'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -23,7 +23,7 @@ interface ISelectionState { export class RenderService extends Disposable implements IRenderService { public serviceBrand: undefined; - private _renderer: IRenderer | undefined; + private _renderer: MutableDisposable = this.register(new MutableDisposable()); private _renderDebouncer: IRenderDebouncerWithCallback; private _screenDprMonitor: ScreenDprMonitor; private _pausedResizeTask = new DebouncedIdleTask(); @@ -49,7 +49,7 @@ export class RenderService extends Disposable implements IRenderService { private readonly _onRefreshRequest = this.register(new EventEmitter<{ start: number, end: number }>()); public readonly onRefreshRequest = this._onRefreshRequest.event; - public get dimensions(): IRenderDimensions { return this._renderer!.dimensions; } + public get dimensions(): IRenderDimensions { return this._renderer.value!.dimensions; } constructor( private _rowCount: number, @@ -63,8 +63,6 @@ export class RenderService extends Disposable implements IRenderService { ) { super(); - this.register({ dispose: () => this._renderer?.dispose() }); - this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end)); this.register(this._renderDebouncer); @@ -73,7 +71,7 @@ export class RenderService extends Disposable implements IRenderService { this.register(this._screenDprMonitor); this.register(bufferService.onResize(() => this._fullRefresh())); - this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear())); + this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear())); this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this.register(this._charSizeService.onCharSizeChange(() => this.handleCharSizeChanged())); @@ -148,7 +146,7 @@ export class RenderService extends Disposable implements IRenderService { } private _renderRows(start: number, end: number): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } @@ -159,11 +157,11 @@ export class RenderService extends Disposable implements IRenderService { end = Math.min(end, this._rowCount - 1); // Render - this._renderer.renderRows(start, end); + this._renderer.value.renderRows(start, end); // Update selection if needed if (this._needsSelectionRefresh) { - this._renderer.handleSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); + this._renderer.value.handleSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); this._needsSelectionRefresh = false; } @@ -181,7 +179,7 @@ export class RenderService extends Disposable implements IRenderService { } private _handleOptionsChanged(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } this.refreshRows(0, this._rowCount - 1); @@ -189,25 +187,23 @@ export class RenderService extends Disposable implements IRenderService { } private _fireOnCanvasResize(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } // Don't fire the event if the dimensions haven't changed - if (this._renderer.dimensions.css.canvas.width === this._canvasWidth && this._renderer.dimensions.css.canvas.height === this._canvasHeight) { + if (this._renderer.value.dimensions.css.canvas.width === this._canvasWidth && this._renderer.value.dimensions.css.canvas.height === this._canvasHeight) { return; } - this._onDimensionsChange.fire(this._renderer.dimensions); + this._onDimensionsChange.fire(this._renderer.value.dimensions); } public hasRenderer(): boolean { - return !!this._renderer; + return !!this._renderer.value; } public setRenderer(renderer: IRenderer): void { - // TODO: RenderService should be the only one to dispose the renderer - this._renderer?.dispose(); - this._renderer = renderer; - this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); + this._renderer.value = renderer; + this._renderer.value.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); // Force a refresh this._needsSelectionRefresh = true; @@ -227,10 +223,10 @@ export class RenderService extends Disposable implements IRenderService { } public clearTextureAtlas(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } - this._renderer.clearTextureAtlas?.(); + this._renderer.value.clearTextureAtlas?.(); this._fullRefresh(); } @@ -239,50 +235,50 @@ export class RenderService extends Disposable implements IRenderService { // when devicePixelRatio changes this._charSizeService.measure(); - if (!this._renderer) { + if (!this._renderer.value) { return; } - this._renderer.handleDevicePixelRatioChange(); + this._renderer.value.handleDevicePixelRatioChange(); this.refreshRows(0, this._rowCount - 1); } public handleResize(cols: number, rows: number): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } if (this._isPaused) { - this._pausedResizeTask.set(() => this._renderer!.handleResize(cols, rows)); + this._pausedResizeTask.set(() => this._renderer.value!.handleResize(cols, rows)); } else { - this._renderer.handleResize(cols, rows); + this._renderer.value.handleResize(cols, rows); } this._fullRefresh(); } // TODO: Is this useful when we have onResize? public handleCharSizeChanged(): void { - this._renderer?.handleCharSizeChanged(); + this._renderer.value?.handleCharSizeChanged(); } public handleBlur(): void { - this._renderer?.handleBlur(); + this._renderer.value?.handleBlur(); } public handleFocus(): void { - this._renderer?.handleFocus(); + this._renderer.value?.handleFocus(); } public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { this._selectionState.start = start; this._selectionState.end = end; this._selectionState.columnSelectMode = columnSelectMode; - this._renderer?.handleSelectionChanged(start, end, columnSelectMode); + this._renderer.value?.handleSelectionChanged(start, end, columnSelectMode); } public handleCursorMove(): void { - this._renderer?.handleCursorMove(); + this._renderer.value?.handleCursorMove(); } public clear(): void { - this._renderer?.clear(); + this._renderer.value?.clear(); } } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 89d9f5e5..47f77406 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { Disposable, toDisposable } from 'common/Lifecycle'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; @@ -57,7 +57,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected _inputHandler: InputHandler; private _writeBuffer: WriteBuffer; - private _windowsWrappingHeuristics: IDisposable | undefined; + private _windowsWrappingHeuristics = this.register(new MutableDisposable()); private readonly _onBinary = this.register(new EventEmitter()); public readonly onBinary = this._onBinary.event; @@ -144,11 +144,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup WriteBuffer this._writeBuffer = this.register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult))); this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); - - this.register(toDisposable(() => { - this._windowsWrappingHeuristics?.dispose(); - this._windowsWrappingHeuristics = undefined; - })); } public write(data: string | Uint8Array, callback?: () => void): void { @@ -267,20 +262,19 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { if (value) { this._enableWindowsWrappingHeuristics(); } else { - this._windowsWrappingHeuristics?.dispose(); - this._windowsWrappingHeuristics = undefined; + this._windowsWrappingHeuristics.clear(); } } protected _enableWindowsWrappingHeuristics(): void { - if (!this._windowsWrappingHeuristics) { + if (!this._windowsWrappingHeuristics.value) { const disposables: IDisposable[] = []; disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); disposables.push(this.registerCsiHandler({ final: 'H' }, () => { updateWindowsModeWrappedState(this._bufferService); return false; })); - this._windowsWrappingHeuristics = toDisposable(() => { + this._windowsWrappingHeuristics.value = toDisposable(() => { for (const d of disposables) { d.dispose(); }