diff --git a/.eslintrc.json b/.eslintrc.json index e9fdce694c..8c47598205 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,8 +16,6 @@ "test/playwright/tsconfig.json", "addons/addon-attach/src/tsconfig.json", "addons/addon-attach/test/tsconfig.json", - "addons/addon-canvas/src/tsconfig.json", - "addons/addon-canvas/test/tsconfig.json", "addons/addon-clipboard/src/tsconfig.json", "addons/addon-clipboard/test/tsconfig.json", "addons/addon-fit/src/tsconfig.json", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f03e49228..d2f39e6b88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,9 +32,6 @@ jobs: ./addons/addon-attach/lib/* \ ./addons/addon-attach/out/* \ ./addons/addon-attach/out-*/* \ - ./addons/addon-canvas/lib/* \ - ./addons/addon-canvas/out/* \ - ./addons/addon-canvas/out-*/* \ ./addons/addon-clipboard/lib/* \ ./addons/addon-clipboard/out/* \ ./addons/addon-clipboard/out-*/* \ @@ -209,8 +206,6 @@ jobs: run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=core - name: Integration tests (addon-attach) run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-attach - - name: Integration tests (addon-canvas) - run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas - name: Integration tests (addon-clipboard) run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-clipboard - name: Integration tests (addon-fit) diff --git a/README.md b/README.md index 4b98187fe2..544bde0794 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ terminal.loadAddon(new WebLinksAddon()); The xterm.js team maintains the following addons, but anyone can build them: - [`@xterm/addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach): Attaches to a server running a process via a websocket -- [`@xterm/addon-canvas`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas): Renders xterm.js using a `canvas` element's 2d context - [`@xterm/addon-clipboard`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-clipboard): Access the browser's clipboard - [`@xterm/addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit): Fits the terminal to the containing element - [`@xterm/addon-image`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image): Adds image support diff --git a/addons/addon-canvas/.gitignore b/addons/addon-canvas/.gitignore deleted file mode 100644 index a9f4ed5456..0000000000 --- a/addons/addon-canvas/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -lib -node_modules \ No newline at end of file diff --git a/addons/addon-canvas/.npmignore b/addons/addon-canvas/.npmignore deleted file mode 100644 index d2fb3bdcc4..0000000000 --- a/addons/addon-canvas/.npmignore +++ /dev/null @@ -1,32 +0,0 @@ -# Blacklist - exclude everything except npm defaults such as LICENSE, etc -* -!*/ - -# Whitelist - lib/ -!lib/**/*.d.ts - -!lib/**/*.js -!lib/**/*.js.map - -!lib/**/*.mjs -!lib/**/*.mjs.map - -!lib/**/*.css - -# Whitelist - src/ -!src/**/*.ts -!src/**/*.d.ts - -!src/**/*.js -!src/**/*.js.map - -!src/**/*.css - -# Blacklist - src/ test files -src/**/*.test.ts -src/**/*.test.d.ts -src/**/*.test.js -src/**/*.test.js.map - -# Whitelist - typings/ -!typings/*.d.ts diff --git a/addons/addon-canvas/LICENSE b/addons/addon-canvas/LICENSE deleted file mode 100644 index e597698cc6..0000000000 --- a/addons/addon-canvas/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/addons/addon-canvas/README.md b/addons/addon-canvas/README.md deleted file mode 100644 index 502ade258e..0000000000 --- a/addons/addon-canvas/README.md +++ /dev/null @@ -1,28 +0,0 @@ -## @xterm/addon-canvas - -An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a canvas-based renderer using a 2d context to draw. This addon requires xterm.js v5+. - -The purpose of this addon is to be used as a fallback for the [webgl addon](https://www.npmjs.com/package/@xterm/addon-webgl) when better performance is desired over the default DOM renderer, but WebGL2 isn't supported or performant for some reason. - -### Install - -```bash -npm install --save @xterm/addon-canvas -``` - -### Usage - -```ts -import { Terminal } from '@xterm/xterm'; -import { CanvasAddon } from '@xterm/addon-canvas'; - -const terminal = new Terminal(); -terminal.open(element); -terminal.loadAddon(new CanvasAddon()); -``` - -See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-canvas/typings/addon-canvas.d.ts) for more advanced usage. - -### See also - -- [@xterm/addon-webgl](https://www.npmjs.com/package/@xterm/addon-webgl) A renderer for xterm.js that uses WebGL diff --git a/addons/addon-canvas/package.json b/addons/addon-canvas/package.json deleted file mode 100644 index f61da6366c..0000000000 --- a/addons/addon-canvas/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@xterm/addon-canvas", - "version": "0.7.0", - "author": { - "name": "The xterm.js authors", - "url": "https://xtermjs.org/" - }, - "main": "lib/addon-canvas.js", - "module": "lib/addon-canvas.mjs", - "types": "typings/addon-canvas.d.ts", - "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas", - "license": "MIT", - "keywords": [ - "terminal", - "canvas", - "xterm", - "xterm.js" - ], - "scripts": { - "build": "../../node_modules/.bin/tsc -p .", - "prepackage": "npm run build", - "package": "../../node_modules/.bin/webpack", - "prepublishOnly": "npm run package", - "start": "node ../../demo/start" - }, - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } -} diff --git a/addons/addon-canvas/src/BaseRenderLayer.ts b/addons/addon-canvas/src/BaseRenderLayer.ts deleted file mode 100644 index 05a877ab94..0000000000 --- a/addons/addon-canvas/src/BaseRenderLayer.ts +++ /dev/null @@ -1,511 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @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 { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -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 { 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 { Terminal } from '@xterm/xterm'; -import { IRenderLayer } from './Types'; -import { Emitter, Event } from 'vs/base/common/event'; - -export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { - private _canvas: HTMLCanvasElement; - protected _ctx!: CanvasRenderingContext2D; - private _deviceCharWidth: number = 0; - private _deviceCharHeight: number = 0; - private _deviceCellWidth: number = 0; - private _deviceCellHeight: number = 0; - private _deviceCharLeft: number = 0; - private _deviceCharTop: number = 0; - - protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); - private _cellColorResolver: CellColorResolver; - private _bitmapGenerator: (BitmapGenerator | undefined)[] = []; - - protected _charAtlas!: ITextureAtlas; - protected _charAtlasDisposable = this.register(new MutableDisposable()); - - public get canvas(): HTMLCanvasElement { return this._canvas; } - public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].canvas!; } - - private readonly _onAddTextureAtlasCanvas = this.register(new Emitter()); - public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; - - constructor( - private readonly _terminal: Terminal, - private _container: HTMLElement, - id: string, - zIndex: number, - private _alpha: boolean, - protected readonly _themeService: IThemeService, - protected readonly _bufferService: IBufferService, - protected readonly _optionsService: IOptionsService, - protected readonly _decorationService: IDecorationService, - protected readonly _coreBrowserService: ICoreBrowserService - ) { - super(); - this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService); - this._canvas = this._coreBrowserService.mainDocument.createElement('canvas'); - this._canvas.classList.add(`xterm-${id}-layer`); - this._canvas.style.zIndex = zIndex.toString(); - this._initCanvas(); - this._container.appendChild(this._canvas); - this._refreshCharAtlas(this._themeService.colors); - this.register(this._themeService.onChangeColors(e => { - this._refreshCharAtlas(e); - this.reset(); - // Trigger selection changed as it's handled separately to regular rendering - this.handleSelectionChanged(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); - })); - - this.register(toDisposable(() => { - this._canvas.remove(); - })); - } - - private _initCanvas(): void { - this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha })); - // Draw the background if this is an opaque layer - if (!this._alpha) { - this._clearAll(); - } - } - - public handleBlur(): void {} - public handleFocus(): void {} - public handleCursorMove(): void {} - public handleGridChanged(startRow: number, endRow: number): void {} - - public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - this._selectionModel.update((this._terminal as any)._core, start, end, columnSelectMode); - } - - protected _setTransparency(alpha: boolean): void { - // Do nothing when alpha doesn't change - if (alpha === this._alpha) { - return; - } - - // Create new canvas and replace old one - const oldCanvas = this._canvas; - this._alpha = alpha; - // Cloning preserves properties - this._canvas = this._canvas.cloneNode() as HTMLCanvasElement; - this._initCanvas(); - this._container.replaceChild(this._canvas, oldCanvas); - - // Regenerate char atlas and force a full redraw - this._refreshCharAtlas(this._themeService.colors); - this.handleGridChanged(0, this._bufferService.rows - 1); - } - - /** - * Refreshes the char atlas, aquiring a new one if necessary. - * @param colorSet The color set to use for the char atlas. - */ - private _refreshCharAtlas(colorSet: ReadonlyColorSet): void { - if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) { - return; - } - this._charAtlas = acquireTextureAtlas(this._terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr); - this._charAtlasDisposable.value = Event.forward(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); - } - } - - public resize(dim: IRenderDimensions): void { - this._deviceCellWidth = dim.device.cell.width; - this._deviceCellHeight = dim.device.cell.height; - this._deviceCharWidth = dim.device.char.width; - this._deviceCharHeight = dim.device.char.height; - this._deviceCharLeft = dim.device.char.left; - this._deviceCharTop = dim.device.char.top; - this._canvas.width = dim.device.canvas.width; - this._canvas.height = dim.device.canvas.height; - this._canvas.style.width = `${dim.css.canvas.width}px`; - this._canvas.style.height = `${dim.css.canvas.height}px`; - - // Draw the background if this is an opaque layer - if (!this._alpha) { - this._clearAll(); - } - - this._refreshCharAtlas(this._themeService.colors); - } - - public abstract reset(): void; - - public clearTextureAtlas(): void { - this._charAtlas?.clearTexture(); - } - - /** - * Fills 1+ cells completely. This uses the existing fillStyle on the context. - * @param x The column to start at. - * @param y The row to start at - * @param width The number of columns to fill. - * @param height The number of rows to fill. - */ - protected _fillCells(x: number, y: number, width: number, height: number): void { - this._ctx.fillRect( - x * this._deviceCellWidth, - y * this._deviceCellHeight, - width * this._deviceCellWidth, - height * this._deviceCellHeight); - } - - /** - * Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the - * existing fillStyle on the context. - * @param x The column to fill. - * @param y The row to fill. - */ - protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void { - const cellOffset = Math.ceil(this._deviceCellHeight * 0.5); - this._ctx.fillRect( - x * this._deviceCellWidth, - (y + 1) * this._deviceCellHeight - cellOffset - this._coreBrowserService.dpr, - width * this._deviceCellWidth, - this._coreBrowserService.dpr); - } - - /** - * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the - * existing fillStyle on the context. - * @param x The column to fill. - * @param y The row to fill. - */ - protected _fillBottomLineAtCells(x: number, y: number, width: number = 1, pixelOffset: number = 0): void { - this._ctx.fillRect( - x * this._deviceCellWidth, - (y + 1) * this._deviceCellHeight + pixelOffset - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */, - width * this._deviceCellWidth, - this._coreBrowserService.dpr); - } - - protected _curlyUnderlineAtCell(x: number, y: number, width: number = 1): void { - this._ctx.save(); - this._ctx.beginPath(); - this._ctx.strokeStyle = this._ctx.fillStyle; - const lineWidth = this._coreBrowserService.dpr; - this._ctx.lineWidth = lineWidth; - for (let xOffset = 0; xOffset < width; xOffset++) { - const xLeft = (x + xOffset) * this._deviceCellWidth; - const xMid = (x + xOffset + 0.5) * this._deviceCellWidth; - const xRight = (x + xOffset + 1) * this._deviceCellWidth; - const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1; - const yMidBot = yMid - lineWidth; - const yMidTop = yMid + lineWidth; - this._ctx.moveTo(xLeft, yMid); - this._ctx.bezierCurveTo( - xLeft, yMidBot, - xMid, yMidBot, - xMid, yMid - ); - this._ctx.bezierCurveTo( - xMid, yMidTop, - xRight, yMidTop, - xRight, yMid - ); - } - this._ctx.stroke(); - this._ctx.restore(); - } - - protected _dottedUnderlineAtCell(x: number, y: number, width: number = 1): void { - this._ctx.save(); - this._ctx.beginPath(); - this._ctx.strokeStyle = this._ctx.fillStyle; - const lineWidth = this._coreBrowserService.dpr; - this._ctx.lineWidth = lineWidth; - this._ctx.setLineDash([lineWidth * 2, lineWidth]); - const xLeft = x * this._deviceCellWidth; - const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1; - this._ctx.moveTo(xLeft, yMid); - for (let xOffset = 0; xOffset < width; xOffset++) { - // const xLeft = x * this._deviceCellWidth; - const xRight = (x + width + xOffset) * this._deviceCellWidth; - this._ctx.lineTo(xRight, yMid); - } - this._ctx.stroke(); - this._ctx.closePath(); - this._ctx.restore(); - } - - protected _dashedUnderlineAtCell(x: number, y: number, width: number = 1): void { - this._ctx.save(); - this._ctx.beginPath(); - this._ctx.strokeStyle = this._ctx.fillStyle; - const lineWidth = this._coreBrowserService.dpr; - this._ctx.lineWidth = lineWidth; - this._ctx.setLineDash([lineWidth * 4, lineWidth * 3]); - const xLeft = x * this._deviceCellWidth; - const xRight = (x + width) * this._deviceCellWidth; - const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1; - this._ctx.moveTo(xLeft, yMid); - this._ctx.lineTo(xRight, yMid); - this._ctx.stroke(); - this._ctx.closePath(); - this._ctx.restore(); - } - - /** - * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the - * existing fillStyle on the context. - * @param x The column to fill. - * @param y The row to fill. - */ - protected _fillLeftLineAtCell(x: number, y: number, width: number): void { - this._ctx.fillRect( - x * this._deviceCellWidth, - y * this._deviceCellHeight, - this._coreBrowserService.dpr * width, - this._deviceCellHeight); - } - - /** - * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing - * strokeStyle on the context. - * @param x The column to fill. - * @param y The row to fill. - */ - protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void { - const lineWidth = this._coreBrowserService.dpr; - this._ctx.lineWidth = lineWidth; - this._ctx.strokeRect( - x * this._deviceCellWidth + lineWidth / 2, - y * this._deviceCellHeight + (lineWidth / 2), - width * this._deviceCellWidth - lineWidth, - (height * this._deviceCellHeight) - lineWidth); - } - - /** - * Clears the entire canvas. - */ - protected _clearAll(): void { - if (this._alpha) { - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); - } else { - this._ctx.fillStyle = this._themeService.colors.background.css; - this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); - } - } - - /** - * Clears 1+ cells completely. - * @param x The column to start at. - * @param y The row to start at. - * @param width The number of columns to clear. - * @param height The number of rows to clear. - */ - protected _clearCells(x: number, y: number, width: number, height: number): void { - if (this._alpha) { - this._ctx.clearRect( - x * this._deviceCellWidth, - y * this._deviceCellHeight, - width * this._deviceCellWidth, - height * this._deviceCellHeight); - } else { - this._ctx.fillStyle = this._themeService.colors.background.css; - this._ctx.fillRect( - x * this._deviceCellWidth, - y * this._deviceCellHeight, - width * this._deviceCellWidth, - height * this._deviceCellHeight); - } - } - - /** - * Draws a truecolor character at the cell. The character will be clipped to - * ensure that it fits with the cell, including the cell to the right if it's - * a wide character. This uses the existing fillStyle on the context. - * @param cell The cell data for the character to draw. - * @param x The column to draw at. - * @param y The row to draw at. - */ - protected _fillCharTrueColor(cell: CellData, x: number, y: number): void { - this._ctx.font = this._getFont(false, false); - this._ctx.textBaseline = TEXT_BASELINE; - this._clipRow(y); - - // Draw custom characters if applicable - let drawSuccess = false; - if (this._optionsService.rawOptions.customGlyphs !== false) { - drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._deviceCellWidth, y * this._deviceCellHeight, this._deviceCellWidth, this._deviceCellHeight, this._optionsService.rawOptions.fontSize, this._coreBrowserService.dpr); - } - - // Draw the character - if (!drawSuccess) { - this._ctx.fillText( - cell.getChars(), - x * this._deviceCellWidth + this._deviceCharLeft, - y * this._deviceCellHeight + this._deviceCharTop + this._deviceCharHeight); - } - } - - /** - * Draws one or more characters at a cell. If possible this will draw using - * the character atlas to reduce draw time. - */ - protected _drawChars(cell: ICellData, x: number, y: number): void { - const chars = cell.getChars(); - const code = cell.getCode(); - const width = cell.getWidth(); - this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth); - - if (!this._charAtlas) { - return; - } - - let glyph: IRasterizedGlyph; - if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true); - } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true); - } - if (!glyph.size.x || !glyph.size.y) { - return; - } - this._ctx.save(); - this._clipRow(y); - - // Draw the image, use the bitmap if it's available - - // HACK: If the canvas doesn't match, delete the generator. It's not clear how this happens but - // something is wrong with either the lifecycle of _bitmapGenerator or the page canvases are - // swapped out unexpectedly - if (this._bitmapGenerator[glyph.texturePage] && this._charAtlas.pages[glyph.texturePage].canvas !== this._bitmapGenerator[glyph.texturePage]!.canvas) { - this._bitmapGenerator[glyph.texturePage]?.bitmap?.close(); - delete this._bitmapGenerator[glyph.texturePage]; - } - - if (this._charAtlas.pages[glyph.texturePage].version !== this._bitmapGenerator[glyph.texturePage]?.version) { - if (!this._bitmapGenerator[glyph.texturePage]) { - this._bitmapGenerator[glyph.texturePage] = new BitmapGenerator(this._charAtlas.pages[glyph.texturePage].canvas); - } - this._bitmapGenerator[glyph.texturePage]!.refresh(); - this._bitmapGenerator[glyph.texturePage]!.version = this._charAtlas.pages[glyph.texturePage].version; - } - - // Reduce scale horizontally for wide glyphs printed in cells that would overlap with the - // following cell (ie. the width is not 2). - let renderWidth = glyph.size.x; - if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) { - if (allowRescaling(code, width, glyph.size.x, this._deviceCellWidth)) { - renderWidth = this._deviceCellWidth - 1; // - 1 to improve readability - } - } - - this._ctx.drawImage( - this._bitmapGenerator[glyph.texturePage]?.bitmap || this._charAtlas!.pages[glyph.texturePage].canvas, - glyph.texturePosition.x, - glyph.texturePosition.y, - glyph.size.x, - glyph.size.y, - x * this._deviceCellWidth + this._deviceCharLeft - glyph.offset.x, - y * this._deviceCellHeight + this._deviceCharTop - glyph.offset.y, - renderWidth, - glyph.size.y - ); - this._ctx.restore(); - } - - /** - * Clips a row to ensure no pixels will be drawn outside the cells in the row. - * @param y The row to clip. - */ - private _clipRow(y: number): void { - this._ctx.beginPath(); - this._ctx.rect( - 0, - y * this._deviceCellHeight, - this._bufferService.cols * this._deviceCellWidth, - this._deviceCellHeight); - this._ctx.clip(); - } - - /** - * Gets the current font. - * @param isBold If we should use the bold fontWeight. - */ - protected _getFont(isBold: boolean, isItalic: boolean): string { - const fontWeight = isBold ? this._optionsService.rawOptions.fontWeightBold : this._optionsService.rawOptions.fontWeight; - const fontStyle = isItalic ? 'italic' : ''; - - return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * this._coreBrowserService.dpr}px ${this._optionsService.rawOptions.fontFamily}`; - } -} - -/** - * The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch - * the operation as window.createImageBitmap is asynchronous. - */ -const GLYPH_BITMAP_COMMIT_DELAY = 100; - -const enum BitmapGeneratorState { - IDLE = 0, - GENERATING = 1, - GENERATING_INVALID = 2 -} - -class BitmapGenerator { - private _state: BitmapGeneratorState = BitmapGeneratorState.IDLE; - private _commitTimeout: number | undefined = undefined; - private _bitmap: ImageBitmap | undefined = undefined; - public get bitmap(): ImageBitmap | undefined { return this._bitmap; } - public version: number = -1; - - constructor(public readonly canvas: HTMLCanvasElement) { - } - - public refresh(): void { - // Clear the bitmap immediately as it's stale - this._bitmap?.close(); - this._bitmap = undefined; - // Disable ImageBitmaps on Safari because of https://bugs.webkit.org/show_bug.cgi?id=149990 - if (isSafari) { - return; - } - if (this._commitTimeout === undefined) { - this._commitTimeout = window.setTimeout(() => this._generate(), GLYPH_BITMAP_COMMIT_DELAY); - } - if (this._state === BitmapGeneratorState.GENERATING) { - this._state = BitmapGeneratorState.GENERATING_INVALID; - } - } - - private _generate(): void { - if (this._state === BitmapGeneratorState.IDLE) { - this._bitmap?.close(); - this._bitmap = undefined; - this._state = BitmapGeneratorState.GENERATING; - window.createImageBitmap(this.canvas).then(bitmap => { - if (this._state === BitmapGeneratorState.GENERATING_INVALID) { - this.refresh(); - } else { - this._bitmap = bitmap; - } - this._state = BitmapGeneratorState.IDLE; - }); - if (this._commitTimeout) { - this._commitTimeout = undefined; - } - } - } -} diff --git a/addons/addon-canvas/src/CanvasAddon.ts b/addons/addon-canvas/src/CanvasAddon.ts deleted file mode 100644 index 91d11f3f5b..0000000000 --- a/addons/addon-canvas/src/CanvasAddon.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2022 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import type { ITerminalAddon, Terminal } from '@xterm/xterm'; -import type { CanvasAddon as ICanvasApi } from '@xterm/addon-canvas'; -import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services'; -import { ITerminal } from 'browser/Types'; -import { Disposable, toDisposable } from 'common/Lifecycle'; -import { setTraceLogger } from 'common/services/LogService'; -import { IBufferService, IDecorationService, ILogService } from 'common/services/Services'; -import { CanvasRenderer } from './CanvasRenderer'; -import { Emitter, Event } from 'vs/base/common/event'; - -export class CanvasAddon extends Disposable implements ITerminalAddon , ICanvasApi { - private _terminal?: Terminal; - private _renderer?: CanvasRenderer; - - private readonly _onChangeTextureAtlas = this.register(new Emitter()); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onAddTextureAtlasCanvas = this.register(new Emitter()); - public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; - - public get textureAtlas(): HTMLCanvasElement | undefined { - return this._renderer?.textureAtlas; - } - - public activate(terminal: Terminal): void { - const core = (terminal as any)._core as ITerminal; - if (!terminal.element) { - this.register(core.onWillOpen(() => this.activate(terminal))); - return; - } - - this._terminal = terminal; - const coreService = core.coreService; - const optionsService = core.optionsService; - const screenElement = core.screenElement!; - const linkifier = core.linkifier!; - - const unsafeCore = core as any; - const bufferService: IBufferService = unsafeCore._bufferService; - const renderService: IRenderService = unsafeCore._renderService; - const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService; - const charSizeService: ICharSizeService = unsafeCore._charSizeService; - const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService; - const decorationService: IDecorationService = unsafeCore._decorationService; - const logService: ILogService = unsafeCore._logService; - const themeService: IThemeService = unsafeCore._themeService; - - // Set trace logger just in case it hasn't been yet which could happen when the addon is - // bundled separately to the core module - setTraceLogger(logService); - - this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService); - this.register(Event.forward(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas)); - this.register(Event.forward(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas)); - renderService.setRenderer(this._renderer); - renderService.handleResize(bufferService.cols, bufferService.rows); - - this.register(toDisposable(() => { - renderService.setRenderer((this._terminal as any)._core._createRenderer()); - renderService.handleResize(terminal.cols, terminal.rows); - this._renderer?.dispose(); - this._renderer = undefined; - })); - } - - public clearTextureAtlas(): void { - this._renderer?.clearTextureAtlas(); - } -} diff --git a/addons/addon-canvas/src/CanvasRenderer.ts b/addons/addon-canvas/src/CanvasRenderer.ts deleted file mode 100644 index 7d08c7b118..0000000000 --- a/addons/addon-canvas/src/CanvasRenderer.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ILinkifier2 } from 'browser/Types'; -import { removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; -import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; -import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; -import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; -import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from '@xterm/xterm'; -import { CursorRenderLayer } from './CursorRenderLayer'; -import { LinkRenderLayer } from './LinkRenderLayer'; -import { SelectionRenderLayer } from './SelectionRenderLayer'; -import { TextRenderLayer } from './TextRenderLayer'; -import { IRenderLayer } from './Types'; -import { Emitter, Event } from 'vs/base/common/event'; - -export class CanvasRenderer extends Disposable implements IRenderer { - private _renderLayers: IRenderLayer[]; - private _devicePixelRatio: number; - private _observerDisposable = this.register(new MutableDisposable()); - - public dimensions: IRenderDimensions; - - private readonly _onRequestRedraw = this.register(new Emitter()); - public readonly onRequestRedraw = this._onRequestRedraw.event; - private readonly _onChangeTextureAtlas = this.register(new Emitter()); - public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; - private readonly _onAddTextureAtlasCanvas = this.register(new Emitter()); - public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event; - - constructor( - private readonly _terminal: Terminal, - private readonly _screenElement: HTMLElement, - linkifier2: ILinkifier2, - private readonly _bufferService: IBufferService, - private readonly _charSizeService: ICharSizeService, - private readonly _optionsService: IOptionsService, - characterJoinerService: ICharacterJoinerService, - coreService: ICoreService, - private readonly _coreBrowserService: ICoreBrowserService, - decorationService: IDecorationService, - private readonly _themeService: IThemeService - ) { - super(); - const allowTransparency = this._optionsService.rawOptions.allowTransparency; - this._renderLayers = [ - new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService), - new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService), - new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService), - new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService) - ]; - for (const layer of this._renderLayers) { - Event.forward(layer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); - } - this.dimensions = createRenderDimensions(); - this._devicePixelRatio = this._coreBrowserService.dpr; - this._updateDimensions(); - - this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h)); - this.register(this._coreBrowserService.onWindowChange(w => { - this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h)); - })); - - this.register(toDisposable(() => { - for (const l of this._renderLayers) { - l.dispose(); - } - removeTerminalFromCache(this._terminal); - })); - } - - public get textureAtlas(): HTMLCanvasElement | undefined { - return this._renderLayers[0].cacheCanvas; - } - - public handleDevicePixelRatioChange(): void { - // If the device pixel ratio changed, the char atlas needs to be regenerated - // and the terminal needs to refreshed - if (this._devicePixelRatio !== this._coreBrowserService.dpr) { - this._devicePixelRatio = this._coreBrowserService.dpr; - this.handleResize(this._bufferService.cols, this._bufferService.rows); - } - } - - public handleResize(cols: number, rows: number): void { - // Update character and canvas dimensions - this._updateDimensions(); - - // Resize all render layers - for (const l of this._renderLayers) { - l.resize(this.dimensions); - } - - // Resize the screen - this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`; - this._screenElement.style.height = `${this.dimensions.css.canvas.height}px`; - } - - public handleCharSizeChanged(): void { - this.handleResize(this._bufferService.cols, this._bufferService.rows); - } - - public handleBlur(): void { - this._runOperation(l => l.handleBlur()); - } - - public handleFocus(): void { - this._runOperation(l => l.handleFocus()); - } - - public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - this._runOperation(l => l.handleSelectionChanged(start, end, columnSelectMode)); - // Selection foreground requires a full re-render - if (this._themeService.colors.selectionForeground) { - this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); - } - } - - public handleCursorMove(): void { - this._runOperation(l => l.handleCursorMove()); - } - - public clear(): void { - this._runOperation(l => l.reset()); - } - - private _runOperation(operation: (layer: IRenderLayer) => void): void { - for (const l of this._renderLayers) { - operation(l); - } - } - - /** - * Performs the refresh loop callback, calling refresh only if a refresh is - * necessary before queueing up the next one. - */ - public renderRows(start: number, end: number): void { - for (const l of this._renderLayers) { - l.handleGridChanged(start, end); - } - } - - public clearTextureAtlas(): void { - for (const layer of this._renderLayers) { - layer.clearTextureAtlas(); - } - } - - /** - * Recalculates the character and canvas dimensions. - */ - private _updateDimensions(): void { - if (!this._charSizeService.hasValidSize) { - return; - } - - // See the WebGL renderer for an explanation of this section. - const dpr = this._coreBrowserService.dpr; - this.dimensions.device.char.width = Math.floor(this._charSizeService.width * dpr); - this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr); - this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight); - this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2); - this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing); - this.dimensions.device.char.left = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); - this.dimensions.device.canvas.height = this._bufferService.rows * this.dimensions.device.cell.height; - this.dimensions.device.canvas.width = this._bufferService.cols * this.dimensions.device.cell.width; - this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr); - this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr); - this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows; - this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols; - } - - private _setCanvasDevicePixelDimensions(width: number, height: number): void { - this.dimensions.device.canvas.height = height; - this.dimensions.device.canvas.width = 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/addon-canvas/src/CursorRenderLayer.ts b/addons/addon-canvas/src/CursorRenderLayer.ts deleted file mode 100644 index d9dfe8ce18..0000000000 --- a/addons/addon-canvas/src/CursorRenderLayer.ts +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStateManager'; -import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; -import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { MutableDisposable } from 'common/Lifecycle'; -import { isFirefox } from 'common/Platform'; -import { ICellData } from 'common/Types'; -import { CellData } from 'common/buffer/CellData'; -import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from '@xterm/xterm'; -import { BaseRenderLayer } from './BaseRenderLayer'; -import type { Emitter } from 'vs/base/common/event'; - -interface ICursorState { - x: number; - y: number; - isFocused: boolean; - style: string; - width: number; -} - -export class CursorRenderLayer extends BaseRenderLayer { - private _state: ICursorState; - private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void}; - private _cursorBlinkStateManager: MutableDisposable = this.register(new MutableDisposable()); - private _cell: ICellData = new CellData(); - - constructor( - terminal: Terminal, - container: HTMLElement, - zIndex: number, - private readonly _onRequestRedraw: Emitter, - bufferService: IBufferService, - optionsService: IOptionsService, - private readonly _coreService: ICoreService, - coreBrowserService: ICoreBrowserService, - decorationService: IDecorationService, - themeService: IThemeService - ) { - super(terminal, container, 'cursor', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); - this._state = { - x: 0, - y: 0, - isFocused: false, - style: '', - width: 0 - }; - this._cursorRenderers = { - 'bar': this._renderBarCursor.bind(this), - 'block': this._renderBlockCursor.bind(this), - 'underline': this._renderUnderlineCursor.bind(this), - 'outline': this._renderOutlineCursor.bind(this) - }; - this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); - this._handleOptionsChanged(); - } - - public resize(dim: IRenderDimensions): void { - super.resize(dim); - // Resizing the canvas discards the contents of the canvas so clear state - this._state = { - x: 0, - y: 0, - isFocused: false, - style: '', - width: 0 - }; - } - - public reset(): void { - this._clearCursor(); - this._cursorBlinkStateManager.value?.restartBlinkAnimation(); - this._handleOptionsChanged(); - } - - public handleBlur(): void { - this._cursorBlinkStateManager.value?.pause(); - this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); - } - - public handleFocus(): void { - 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.value) { - this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService); - } - } else { - this._cursorBlinkStateManager.clear(); - } - // Request a refresh from the terminal as management of rendering is being - // moved back to the terminal - this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); - } - - public handleCursorMove(): void { - this._cursorBlinkStateManager.value?.restartBlinkAnimation(); - } - - public handleGridChanged(startRow: number, endRow: number): void { - if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isPaused) { - this._render(false); - } else { - this._cursorBlinkStateManager.value.restartBlinkAnimation(); - } - } - - private _render(triggeredByAnimationFrame: boolean): void { - // Don't draw the cursor if it's hidden - if (!this._coreService.isCursorInitialized || this._coreService.isCursorHidden) { - this._clearCursor(); - return; - } - - const cursorY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; - const viewportRelativeCursorY = cursorY - this._bufferService.buffer.ydisp; - - // Don't draw the cursor if it's off-screen - if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= this._bufferService.rows) { - this._clearCursor(); - return; - } - - // in case cursor.x == cols adjust visual cursor to cols - 1 - const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1); - this._bufferService.buffer.lines.get(cursorY)!.loadCell(cursorX, this._cell); - if (this._cell.content === undefined) { - return; - } - - if (!this._coreBrowserService.isFocused) { - this._clearCursor(); - this._ctx.save(); - this._ctx.fillStyle = this._themeService.colors.cursor.css; - const cursorStyle = this._optionsService.rawOptions.cursorStyle; - const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle; - if (cursorInactiveStyle && cursorInactiveStyle !== 'none') { - this._cursorRenderers[cursorInactiveStyle](cursorX, viewportRelativeCursorY, this._cell); - } - this._ctx.restore(); - this._state.x = cursorX; - this._state.y = viewportRelativeCursorY; - this._state.isFocused = false; - this._state.style = cursorStyle; - this._state.width = this._cell.getWidth(); - return; - } - - // Don't draw the cursor if it's blinking - if (this._cursorBlinkStateManager.value && !this._cursorBlinkStateManager.value.isCursorVisible) { - this._clearCursor(); - return; - } - - if (this._state) { - // The cursor is already in the correct spot, don't redraw - if (this._state.x === cursorX && - this._state.y === viewportRelativeCursorY && - this._state.isFocused === this._coreBrowserService.isFocused && - this._state.style === this._optionsService.rawOptions.cursorStyle && - this._state.width === this._cell.getWidth()) { - return; - } - this._clearCursor(); - } - - this._ctx.save(); - this._cursorRenderers[this._optionsService.rawOptions.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell); - this._ctx.restore(); - - this._state.x = cursorX; - this._state.y = viewportRelativeCursorY; - this._state.isFocused = false; - this._state.style = this._optionsService.rawOptions.cursorStyle; - this._state.width = this._cell.getWidth(); - } - - private _clearCursor(): void { - if (this._state) { - // Avoid potential rounding errors when browser is Firefox (#4487) or device pixel ratio is - // less than 1 - if (isFirefox || this._coreBrowserService.dpr < 1) { - this._clearAll(); - } else { - this._clearCells(this._state.x, this._state.y, this._state.width, 1); - } - this._state = { - x: 0, - y: 0, - isFocused: false, - style: '', - width: 0 - }; - } - } - - private _renderBarCursor(x: number, y: number, cell: ICellData): void { - this._ctx.save(); - this._ctx.fillStyle = this._themeService.colors.cursor.css; - this._fillLeftLineAtCell(x, y, this._optionsService.rawOptions.cursorWidth); - this._ctx.restore(); - } - - private _renderBlockCursor(x: number, y: number, cell: ICellData): void { - this._ctx.save(); - this._ctx.fillStyle = this._themeService.colors.cursor.css; - this._fillCells(x, y, cell.getWidth(), 1); - this._ctx.fillStyle = this._themeService.colors.cursorAccent.css; - this._fillCharTrueColor(cell, x, y); - this._ctx.restore(); - } - - private _renderUnderlineCursor(x: number, y: number, cell: ICellData): void { - this._ctx.save(); - this._ctx.fillStyle = this._themeService.colors.cursor.css; - this._fillBottomLineAtCells(x, y); - this._ctx.restore(); - } - - private _renderOutlineCursor(x: number, y: number, cell: ICellData): void { - this._ctx.save(); - this._ctx.strokeStyle = this._themeService.colors.cursor.css; - this._strokeRectAtCell(x, y, cell.getWidth(), 1); - this._ctx.restore(); - } -} diff --git a/addons/addon-canvas/src/GridCache.test.ts b/addons/addon-canvas/src/GridCache.test.ts deleted file mode 100644 index c4b1c220ee..0000000000 --- a/addons/addon-canvas/src/GridCache.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { GridCache } from './GridCache'; - -describe('GridCache', () => { - let grid: GridCache; - - beforeEach(() => { - grid = new GridCache(); - }); - - describe('constructor', () => { - it('should create an empty cache', () => { - assert.equal(grid.cache.length, 0); - }); - }); - - describe('resize', () => { - it('should fill all new elements with null', () => { - grid.resize(2, 2); - assert.equal(grid.cache.length, 2); - assert.equal(grid.cache[0].length, 2); - assert.equal(grid.cache[0][0], null); - assert.equal(grid.cache[0][1], null); - assert.equal(grid.cache[1].length, 2); - assert.equal(grid.cache[1][0], null); - assert.equal(grid.cache[1][1], null); - grid.resize(3, 2); - assert.equal(grid.cache.length, 3); - assert.equal(grid.cache[2][0], null); - assert.equal(grid.cache[2][1], null); - }); - - it('should remove rows/cols from the cache when reduced', () => { - grid.resize(2, 2); - grid.resize(1, 1); - assert.equal(grid.cache.length, 1); - assert.equal(grid.cache[0].length, 1); - }); - - it('should not touch existing cache entries if they fit in the new cache', () => { - grid.resize(1, 1); - assert.equal(grid.cache[0][0], null); - grid.cache[0][0] = 1; - grid.resize(2, 1); - assert.equal(grid.cache[0][0], 1); - }); - }); - - describe('clear', () => { - it('should make all entries null', () => { - grid.resize(1, 1); - grid.cache[0][0] = 1; - grid.clear(); - assert.equal(grid.cache[0][0], null); - }); - }); -}); diff --git a/addons/addon-canvas/src/GridCache.ts b/addons/addon-canvas/src/GridCache.ts deleted file mode 100644 index b48798d2ca..0000000000 --- a/addons/addon-canvas/src/GridCache.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -export class GridCache { - public cache: (T | undefined)[][]; - - public constructor() { - this.cache = []; - } - - public resize(width: number, height: number): void { - for (let x = 0; x < width; x++) { - if (this.cache.length <= x) { - this.cache.push([]); - } - for (let y = this.cache[x].length; y < height; y++) { - this.cache[x].push(undefined); - } - this.cache[x].length = height; - } - this.cache.length = width; - } - - public clear(): void { - for (let x = 0; x < this.cache.length; x++) { - for (let y = 0; y < this.cache[x].length; y++) { - this.cache[x][y] = undefined; - } - } - } -} diff --git a/addons/addon-canvas/src/LinkRenderLayer.ts b/addons/addon-canvas/src/LinkRenderLayer.ts deleted file mode 100644 index 1cdf71c2fc..0000000000 --- a/addons/addon-canvas/src/LinkRenderLayer.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ILinkifier2, ILinkifierEvent } from 'browser/Types'; -import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; -import { IRenderDimensions } from 'browser/renderer/shared/Types'; -import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from '@xterm/xterm'; -import { BaseRenderLayer } from './BaseRenderLayer'; - -export class LinkRenderLayer extends BaseRenderLayer { - private _state: ILinkifierEvent | undefined; - - constructor( - terminal: Terminal, - container: HTMLElement, - zIndex: number, - linkifier2: ILinkifier2, - bufferService: IBufferService, - optionsService: IOptionsService, - decorationService: IDecorationService, - coreBrowserService: ICoreBrowserService, - themeService: IThemeService - ) { - super(terminal, container, 'link', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); - - this.register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e))); - this.register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e))); - } - - public resize(dim: IRenderDimensions): void { - super.resize(dim); - // Resizing the canvas discards the contents of the canvas so clear state - this._state = undefined; - } - - public reset(): void { - this._clearCurrentLink(); - } - - private _clearCurrentLink(): void { - if (this._state) { - this._clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1); - const middleRowCount = this._state.y2 - this._state.y1 - 1; - if (middleRowCount > 0) { - this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount); - } - this._clearCells(0, this._state.y2, this._state.x2, 1); - this._state = undefined; - } - } - - private _handleShowLinkUnderline(e: ILinkifierEvent): void { - if (e.fg === INVERTED_DEFAULT_COLOR) { - this._ctx.fillStyle = this._themeService.colors.background.css; - } else if (e.fg && is256Color(e.fg)) { - // 256 color support - this._ctx.fillStyle = this._themeService.colors.ansi[e.fg].css; - } else { - this._ctx.fillStyle = this._themeService.colors.foreground.css; - } - - if (e.y1 === e.y2) { - // Single line link - this._fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1); - } else { - // Multi-line link - this._fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1); - for (let y = e.y1 + 1; y < e.y2; y++) { - this._fillBottomLineAtCells(0, y, e.cols); - } - this._fillBottomLineAtCells(0, e.y2, e.x2); - } - this._state = e; - } - - private _handleHideLinkUnderline(e: ILinkifierEvent): void { - this._clearCurrentLink(); - } -} diff --git a/addons/addon-canvas/src/SelectionRenderLayer.ts b/addons/addon-canvas/src/SelectionRenderLayer.ts deleted file mode 100644 index 01612a1d29..0000000000 --- a/addons/addon-canvas/src/SelectionRenderLayer.ts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IRenderDimensions } from 'browser/renderer/shared/Types'; -import { BaseRenderLayer } from './BaseRenderLayer'; -import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { Terminal } from '@xterm/xterm'; - -interface ISelectionState { - start?: [number, number]; - end?: [number, number]; - columnSelectMode?: boolean; - ydisp?: number; -} - -export class SelectionRenderLayer extends BaseRenderLayer { - private _state!: ISelectionState; - - constructor( - terminal: Terminal, - container: HTMLElement, - zIndex: number, - bufferService: IBufferService, - coreBrowserService: ICoreBrowserService, - decorationService: IDecorationService, - optionsService: IOptionsService, - themeService: IThemeService - ) { - super(terminal, container, 'selection', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService); - this._clearState(); - } - - private _clearState(): void { - this._state = { - start: undefined, - end: undefined, - columnSelectMode: undefined, - ydisp: undefined - }; - } - - public resize(dim: IRenderDimensions): void { - super.resize(dim); - // On resize use the base render layer's cached selection values since resize clears _state - // inside reset. - if (this._selectionModel.selectionStart && this._selectionModel.selectionEnd) { - this._clearState(); - this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); - } - } - - public reset(): void { - if (this._state.start && this._state.end) { - this._clearState(); - this._clearAll(); - } - } - - public handleBlur(): void { - this.reset(); - this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); - } - - public handleFocus(): void { - this.reset(); - this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); - } - - public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { - super.handleSelectionChanged(start, end, columnSelectMode); - this._redrawSelection(start, end, columnSelectMode); - } - - private _redrawSelection(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { - // Selection has not changed - if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) { - return; - } - - // Remove all selections - this._clearAll(); - - // Selection does not exist - if (!start || !end) { - this._clearState(); - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - this._bufferService.buffer.ydisp; - const viewportEndRow = end[1] - this._bufferService.buffer.ydisp; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) { - this._state.ydisp = this._bufferService.buffer.ydisp; - return; - } - - this._ctx.fillStyle = (this._coreBrowserService.isFocused - ? this._themeService.colors.selectionBackgroundTransparent - : this._themeService.colors.selectionInactiveBackgroundTransparent).css; - - if (columnSelectMode) { - const startCol = start[0]; - const width = end[0] - startCol; - const height = viewportCappedEndRow - viewportCappedStartRow + 1; - this._fillCells(startCol, viewportCappedStartRow, width, height); - } else { - // Draw first row - const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - const startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols; - this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); - - // Draw middle rows - const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); - this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount); - - // Draw final row - if (viewportCappedStartRow !== viewportCappedEndRow) { - // Only draw viewportEndRow if it's not the same as viewportStartRow - const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols; - this._fillCells(0, viewportCappedEndRow, endCol, 1); - } - } - - // Save state for next render - this._state.start = [start[0], start[1]]; - this._state.end = [end[0], end[1]]; - this._state.columnSelectMode = columnSelectMode; - this._state.ydisp = this._bufferService.buffer.ydisp; - } - - private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean { - return !this._areCoordinatesEqual(start, this._state.start) || - !this._areCoordinatesEqual(end, this._state.end) || - columnSelectMode !== this._state.columnSelectMode || - ydisp !== this._state.ydisp; - } - - private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean { - if (!coord1 || !coord2) { - return false; - } - - return coord1[0] === coord2[0] && coord1[1] === coord2[1]; - } -} diff --git a/addons/addon-canvas/src/TextRenderLayer.ts b/addons/addon-canvas/src/TextRenderLayer.ts deleted file mode 100644 index 43c33d64ae..0000000000 --- a/addons/addon-canvas/src/TextRenderLayer.ts +++ /dev/null @@ -1,292 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IRenderDimensions } from 'browser/renderer/shared/Types'; -import { JoinedCellData } from 'browser/services/CharacterJoinerService'; -import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { CharData, ICellData } from 'common/Types'; -import { AttributeData } from 'common/buffer/AttributeData'; -import { CellData } from 'common/buffer/CellData'; -import { Content, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { Terminal } from '@xterm/xterm'; -import { BaseRenderLayer } from './BaseRenderLayer'; -import { GridCache } from './GridCache'; - -/** - * This CharData looks like a null character, which will forc a clear and render - * when the character changes (a regular space ' ' character may not as it's - * drawn state is a cleared cell). - */ -// const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1]; - -export class TextRenderLayer extends BaseRenderLayer { - private _state: GridCache; - private _characterWidth: number = 0; - private _characterFont: string = ''; - private _characterOverlapCache: { [key: string]: boolean } = {}; - private _workCell = new CellData(); - - constructor( - terminal: Terminal, - container: HTMLElement, - zIndex: number, - alpha: boolean, - bufferService: IBufferService, - optionsService: IOptionsService, - private readonly _characterJoinerService: ICharacterJoinerService, - decorationService: IDecorationService, - coreBrowserService: ICoreBrowserService, - themeService: IThemeService - ) { - super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService); - this._state = new GridCache(); - this.register(optionsService.onSpecificOptionChange('allowTransparency', value => this._setTransparency(value))); - } - - public resize(dim: IRenderDimensions): void { - super.resize(dim); - - // Clear the character width cache if the font or width has changed - const terminalFont = this._getFont(false, false); - if (this._characterWidth !== dim.device.char.width || this._characterFont !== terminalFont) { - this._characterWidth = dim.device.char.width; - this._characterFont = terminalFont; - this._characterOverlapCache = {}; - } - // Resizing the canvas discards the contents of the canvas so clear state - this._state.clear(); - this._state.resize(this._bufferService.cols, this._bufferService.rows); - } - - public reset(): void { - this._state.clear(); - this._clearAll(); - } - - private _forEachCell( - firstRow: number, - lastRow: number, - callback: ( - cell: ICellData, - x: number, - y: number - ) => void - ): void { - for (let y = firstRow; y <= lastRow; y++) { - const row = y + this._bufferService.buffer.ydisp; - const line = this._bufferService.buffer.lines.get(row); - const joinedRanges = this._characterJoinerService.getJoinedCharacters(row); - for (let x = 0; x < this._bufferService.cols; x++) { - line!.loadCell(x, this._workCell); - let cell = this._workCell; - - // If true, indicates that the current character(s) to draw were joined. - let isJoined = false; - let lastCharX = x; - - // The character to the left is a wide character, drawing is owned by - // the char at x-1 - if (cell.getWidth() === 0) { - continue; - } - - // exit early for NULL and SP - // NOTE: commented out due to #4120 (needs a more clever patch to keep things performant) - // const code = cell.getCode(); - // if (code === 0 || code === 32) { - // continue; - // } - - // Process any joined character ranges as needed. Because of how the - // ranges are produced, we know that they are valid for the characters - // and attributes of our input. - if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { - isJoined = true; - const range = joinedRanges.shift()!; - - // We already know the exact start and end column of the joined range, - // so we get the string and width representing it directly - cell = new JoinedCellData( - this._workCell, - line!.translateToString(true, range[0], range[1]), - range[1] - range[0] - ); - - // Skip over the cells occupied by this range in the loop - lastCharX = range[1] - 1; - } - - // If the character is an overlapping char and the character to the - // right is a space, take ownership of the cell to the right. We skip - // this check for joined characters because their rendering likely won't - // yield the same result as rendering the last character individually. - if (!isJoined && this._isOverlapping(cell)) { - // If the character is overlapping, we want to force a re-render on every - // frame. This is specifically to work around the case where two - // overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a - // space is added. Without this, the first half of `b` would never - // get removed, and `a` would not re-render because it thinks it's - // already in the correct state. - // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; - if (lastCharX < line!.length - 1 && line!.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) { - // patch width to 2 - cell.content &= ~Content.WIDTH_MASK; - cell.content |= 2 << Content.WIDTH_SHIFT; - // this._clearChar(x + 1, y); - // The overlapping char's char data will force a clear and render when the - // overlapping char is no longer to the left of the character and also when - // the space changes to another character. - // this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA; - } - } - - callback( - cell, - x, - y - ); - - x = lastCharX; - } - } - } - - /** - * Draws the background for a specified range of columns. Tries to batch adjacent cells of the - * same color together to reduce draw calls. - */ - private _drawBackground(firstRow: number, lastRow: number): void { - const ctx = this._ctx; - const cols = this._bufferService.cols; - let startX: number = 0; - let startY: number = 0; - let prevFillStyle: string | null = null; - - ctx.save(); - - this._forEachCell(firstRow, lastRow, (cell, x, y) => { - // libvte and xterm both draw the background (but not foreground) of invisible characters, - // so we should too. - let nextFillStyle = null; // null represents default background color - - if (cell.isInverse()) { - if (cell.isFgDefault()) { - nextFillStyle = this._themeService.colors.foreground.css; - } else if (cell.isFgRGB()) { - nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else { - nextFillStyle = this._themeService.colors.ansi[cell.getFgColor()].css; - } - } else if (cell.isBgRGB()) { - nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; - } else if (cell.isBgPalette()) { - nextFillStyle = this._themeService.colors.ansi[cell.getBgColor()].css; - } - - // Get any decoration foreground/background overrides, this must be fetched before the early - // exist but applied after inverse - let isTop = false; - this._decorationService.forEachDecorationAtCell(x, this._bufferService.buffer.ydisp + y, undefined, d => { - if (d.options.layer !== 'top' && isTop) { - return; - } - if (d.backgroundColorRGB) { - nextFillStyle = d.backgroundColorRGB.css; - } - isTop = d.options.layer === 'top'; - }); - - if (prevFillStyle === null) { - // This is either the first iteration, or the default background was set. Either way, we - // don't need to draw anything. - startX = x; - startY = y; - } - - if (y !== startY) { - // our row changed, draw the previous row - ctx.fillStyle = prevFillStyle || ''; - this._fillCells(startX, startY, cols - startX, 1); - startX = x; - startY = y; - } else if (prevFillStyle !== nextFillStyle) { - // our color changed, draw the previous characters in this row - ctx.fillStyle = prevFillStyle || ''; - this._fillCells(startX, startY, x - startX, 1); - startX = x; - startY = y; - } - - prevFillStyle = nextFillStyle; - }); - - // flush the last color we encountered - if (prevFillStyle !== null) { - ctx.fillStyle = prevFillStyle; - this._fillCells(startX, startY, cols - startX, 1); - } - - ctx.restore(); - } - - private _drawForeground(firstRow: number, lastRow: number): void { - this._forEachCell(firstRow, lastRow, (cell, x, y) => this._drawChars(cell, x, y)); - } - - public handleGridChanged(firstRow: number, lastRow: number): void { - // Resize has not been called yet - if (this._state.cache.length === 0) { - return; - } - - if (this._charAtlas) { - this._charAtlas.beginFrame(); - } - - this._clearCells(0, firstRow, this._bufferService.cols, lastRow - firstRow + 1); - this._drawBackground(firstRow, lastRow); - this._drawForeground(firstRow, lastRow); - } - - /** - * Whether a character is overlapping to the next cell. - */ - private _isOverlapping(cell: ICellData): boolean { - // Only single cell characters can be overlapping, rendering issues can - // occur without this check - if (cell.getWidth() !== 1) { - return false; - } - - // We assume that any ascii character will not overlap - if (cell.getCode() < 256) { - return false; - } - - const chars = cell.getChars(); - - // Deliver from cache if available - if (this._characterOverlapCache.hasOwnProperty(chars)) { - return this._characterOverlapCache[chars]; - } - - // Setup the font - this._ctx.save(); - this._ctx.font = this._characterFont; - - // Measure the width of the character, but Math.floor it - // because that is what the renderer does when it calculates - // the character dimensions we are comparing against - const overlaps = Math.floor(this._ctx.measureText(chars).width) > this._characterWidth; - - // Restore the original context - this._ctx.restore(); - - // Cache and return - this._characterOverlapCache[chars] = overlaps; - return overlaps; - } -} diff --git a/addons/addon-canvas/src/Types.ts b/addons/addon-canvas/src/Types.ts deleted file mode 100644 index 9b06129e69..0000000000 --- a/addons/addon-canvas/src/Types.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from 'common/Types'; -import { IRenderDimensions } from 'browser/renderer/shared/Types'; -import type { Event } from 'vs/base/common/event'; - -export interface IRequestRedrawEvent { - start: number; - end: number; -} - -/** - * Note that IRenderer implementations should emit the refresh event after - * rendering rows to the screen. - */ -export interface IRenderer extends IDisposable { - readonly dimensions: IRenderDimensions; - - /** - * Fires when the renderer is requesting to be redrawn on the next animation - * frame but is _not_ a result of content changing (eg. selection changes). - */ - readonly onRequestRedraw: Event; - - handleDevicePixelRatioChange(): void; - handleResize(cols: number, rows: number): void; - handleCharSizeChanged(): void; - handleBlur(): void; - handleFocus(): void; - handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; - handleCursorMove(): void; - handleOptionsChanged(): void; - clear(): void; - renderRows(start: number, end: number): void; - clearTextureAtlas?(): void; -} - -export interface IRenderLayer extends IDisposable { - readonly canvas: HTMLCanvasElement; - readonly cacheCanvas: HTMLCanvasElement; - - readonly onAddTextureAtlasCanvas: Event; - /** - * Called when the terminal loses focus. - */ - handleBlur(): void; - - /** - * Called when the terminal gets focus. - */ - handleFocus(): void; - - /** - * Called when the cursor is moved. - */ - handleCursorMove(): void; - - /** - * Called when the data in the grid has changed (or needs to be rendered - * again). - */ - handleGridChanged(startRow: number, endRow: number): void; - - /** - * Calls when the selection changes. - */ - handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; - - /** - * Resize the render layer. - */ - resize(dim: IRenderDimensions): void; - - /** - * Clear the state of the render layer. - */ - reset(): void; - - /** - * Clears the texture atlas. - */ - clearTextureAtlas(): void; -} diff --git a/addons/addon-canvas/src/tsconfig.json b/addons/addon-canvas/src/tsconfig.json deleted file mode 100644 index 093acefbc0..0000000000 --- a/addons/addon-canvas/src/tsconfig.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es2021", - "lib": [ - "dom", - "es2021" - ], - "rootDir": ".", - "outDir": "../out", - "sourceMap": true, - "removeComments": true, - "baseUrl": ".", - "paths": { - "common/*": [ - "../../../src/common/*" - ], - "browser/*": [ - "../../../src/browser/*" - ], - "vs/*": [ - "../../../src/vs/*" - ], - "@xterm/addon-canvas": [ - "../typings/addon-canvas.d.ts" - ] - }, - "strict": true, - "downlevelIteration": true, - "types": [ - "../../../node_modules/@types/mocha" - ] - }, - "include": [ - "./**/*", - "../../../typings/xterm.d.ts" - ], - "references": [ - { - "path": "../../../src/common" - }, - { - "path": "../../../src/browser" - }, - { - "path": "../../../src/vs" - } - ] -} diff --git a/addons/addon-canvas/test/CanvasRenderer.test.ts b/addons/addon-canvas/test/CanvasRenderer.test.ts deleted file mode 100644 index 099b62ec47..0000000000 --- a/addons/addon-canvas/test/CanvasRenderer.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import test from '@playwright/test'; -import { ISharedRendererTestContext, injectSharedRendererTests, injectSharedRendererTestsStandalone } from '../../../test/playwright/SharedRendererTests'; -import { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils'; - -let ctx: ITestContext; -const ctxWrapper: ISharedRendererTestContext = { - value: undefined, - skipCanvasExceptions: true -} as any; -test.beforeAll(async ({ browser }) => { - ctx = await createTestContext(browser); - await openTerminal(ctx); - ctxWrapper.value = ctx; - await ctx.page.evaluate(` - window.addon = new window.CanvasAddon(true); - window.term.loadAddon(window.addon); - `); -}); -test.afterAll(async () => await ctx.page.close()); - -test.describe('Canvas Renderer Integration Tests', () => { - // HACK: The tests fail for an unknown reason - test.skip(({ browserName }) => browserName === 'webkit'); - - injectSharedRendererTests(ctxWrapper); - injectSharedRendererTestsStandalone(ctxWrapper, async () => { - await ctx.page.evaluate(` - window.addon = new window.CanvasAddon(true); - window.term.loadAddon(window.addon); - `); - }); -}); diff --git a/addons/addon-canvas/test/playwright.config.ts b/addons/addon-canvas/test/playwright.config.ts deleted file mode 100644 index 22834be116..0000000000 --- a/addons/addon-canvas/test/playwright.config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { PlaywrightTestConfig } from '@playwright/test'; - -const config: PlaywrightTestConfig = { - testDir: '.', - timeout: 10000, - projects: [ - { - name: 'ChromeStable', - use: { - browserName: 'chromium', - channel: 'chrome' - } - }, - { - name: 'FirefoxStable', - use: { - browserName: 'firefox' - } - }, - { - name: 'WebKit', - use: { - browserName: 'webkit' - } - } - ], - reporter: 'list', - webServer: { - command: 'npm run start', - port: 3000, - timeout: 120000, - reuseExistingServer: !process.env.CI - } -}; -export default config; diff --git a/addons/addon-canvas/test/tsconfig.json b/addons/addon-canvas/test/tsconfig.json deleted file mode 100644 index 6efb7073dc..0000000000 --- a/addons/addon-canvas/test/tsconfig.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "ESNext", - "lib": [ - "es6", - ], - "rootDir": ".", - "outDir": "../out-test", - "sourceMap": true, - "removeComments": true, - "baseUrl": ".", - "paths": { - "common/*": [ - "../../../src/common/*" - ], - "browser/*": [ - "../../../src/browser/*" - ] - }, - "strict": true, - "types": [ - "../../../node_modules/@types/node", - "../../../node_modules/@lunapaint/png-codec" - ] - }, - "include": [ - "./**/*", - "../../../typings/xterm.d.ts" - ], - "references": [ - { - "path": "../../../src/common" - }, - { - "path": "../../../src/browser" - }, - { - "path": "../../../test/playwright" - } - ] -} diff --git a/addons/addon-canvas/tsconfig.json b/addons/addon-canvas/tsconfig.json deleted file mode 100644 index 2d820dd1a6..0000000000 --- a/addons/addon-canvas/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "files": [], - "include": [], - "references": [ - { "path": "./src" }, - { "path": "./test" } - ] -} diff --git a/addons/addon-canvas/typings/addon-canvas.d.ts b/addons/addon-canvas/typings/addon-canvas.d.ts deleted file mode 100644 index a679f0a5ca..0000000000 --- a/addons/addon-canvas/typings/addon-canvas.d.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm'; - -declare module '@xterm/addon-canvas' { - /** - * An xterm.js addon that provides search functionality. - */ - export class CanvasAddon implements ITerminalAddon { - public textureAtlas?: HTMLCanvasElement; - - /** - * An event that is fired when the texture atlas of the renderer changes. - */ - public readonly onChangeTextureAtlas: IEvent; - - /** - * An event that is fired when the a new page is added to the texture atlas. - */ - public readonly onAddTextureAtlasCanvas: IEvent; - - constructor(); - - /** - * Activates the addon. - * @param terminal The terminal the addon is being loaded in. - */ - public activate(terminal: Terminal): void; - - /** - * Disposes the addon. - */ - public dispose(): void; - - /** - * Clears the terminal's texture atlas and triggers a redraw. - */ - public clearTextureAtlas(): void; - } -} diff --git a/addons/addon-canvas/webpack.config.js b/addons/addon-canvas/webpack.config.js deleted file mode 100644 index c38d928604..0000000000 --- a/addons/addon-canvas/webpack.config.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -const path = require('path'); - -const addonName = 'CanvasAddon'; -const mainFile = 'addon-canvas.js'; - -module.exports = { - entry: `./out/${addonName}.js`, - devtool: 'source-map', - module: { - rules: [ - { - test: /\.js$/, - use: ["source-map-loader"], - enforce: "pre", - exclude: /node_modules/ - } - ] - }, - resolve: { - modules: ['./node_modules'], - extensions: [ '.js' ], - alias: { - common: path.resolve('../../out/common'), - browser: path.resolve('../../out/browser'), - vs: path.resolve('../../out/vs') - } - }, - output: { - filename: mainFile, - path: path.resolve('./lib'), - library: addonName, - libraryTarget: 'umd', - // Force usage of globalThis instead of global / self. (This is cross-env compatible) - globalObject: 'globalThis', - }, - mode: 'production' -}; diff --git a/addons/addon-webgl/README.md b/addons/addon-webgl/README.md index 05d753e86e..9d89863f31 100644 --- a/addons/addon-webgl/README.md +++ b/addons/addon-webgl/README.md @@ -35,7 +35,3 @@ terminal.loadAddon(addon); ``` Read more about handling WebGL context losses on the [Khronos wiki](https://www.khronos.org/webgl/wiki/HandlingContextLost). - -### See also - -- [@xterm/addon-canvas](https://www.npmjs.com/package/@xterm/addon-canvas) A renderer for xterm.js that uses a 2d canvas that can be used as a fallback when WebGL is not available diff --git a/bin/esbuild.mjs b/bin/esbuild.mjs index a8ccf48f01..71bee9aa53 100644 --- a/bin/esbuild.mjs +++ b/bin/esbuild.mjs @@ -133,7 +133,6 @@ if (config.addon) { // Library ESM imports "@xterm/xterm": ".", "@xterm/addon-attach": "./addons/addon-attach/lib/xterm-addon-attach.mjs", - "@xterm/addon-canvas": "./addons/addon-canvas/lib/xterm-addon-canvas.mjs", "@xterm/addon-clipboard": "./addons/addon-clipboard/lib/xterm-addon-clipboard.mjs", "@xterm/addon-fit": "./addons/addon-fit/lib/xterm-addon-fit.mjs", "@xterm/addon-image": "./addons/addon-image/lib/xterm-addon-image.mjs", diff --git a/bin/publish.js b/bin/publish.js index 4b945d7569..1f47a82bb2 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -28,7 +28,6 @@ if (changedFiles.some(e => e.search(/^addons\//) === -1)) { // Publish addons if any files were changed inside of the addon const addonPackageDirs = [ path.resolve(__dirname, '../addons/addon-attach'), - path.resolve(__dirname, '../addons/addon-canvas'), path.resolve(__dirname, '../addons/addon-clipboard'), path.resolve(__dirname, '../addons/addon-fit'), path.resolve(__dirname, '../addons/addon-image'), diff --git a/bin/test_integration.js b/bin/test_integration.js index 2964693c36..936467cc17 100644 --- a/bin/test_integration.js +++ b/bin/test_integration.js @@ -22,7 +22,6 @@ let configs = [ ]; const addons = [ 'attach', - 'canvas', 'clipboard', 'fit', 'image', diff --git a/demo/client.ts b/demo/client.ts index c2b5ee3c11..152b94ccd3 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -18,7 +18,6 @@ if ('WebAssembly' in window) { import { Terminal, ITerminalOptions, type IDisposable } from '@xterm/xterm'; import { AttachAddon } from '@xterm/addon-attach'; -import { CanvasAddon } from '@xterm/addon-canvas'; import { ClipboardAddon } from '@xterm/addon-clipboard'; import { FitAddon } from '@xterm/addon-fit'; import { LigaturesAddon } from '@xterm/addon-ligatures'; @@ -33,7 +32,6 @@ export interface IWindowWithTerminal extends Window { term: typeof Terminal; Terminal: typeof Terminal; AttachAddon?: typeof AttachAddon; // eslint-disable-line @typescript-eslint/naming-convention - CanvasAddon?: typeof CanvasAddon; // eslint-disable-line @typescript-eslint/naming-convention ClipboardAddon?: typeof ClipboardAddon; // eslint-disable-line @typescript-eslint/naming-convention FitAddon?: typeof FitAddon; // eslint-disable-line @typescript-eslint/naming-convention ImageAddon?: typeof ImageAddon; // eslint-disable-line @typescript-eslint/naming-convention @@ -54,46 +52,43 @@ let socket; let pid; let autoResize: boolean = true; -type AddonType = 'attach' | 'canvas' | 'clipboard' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures'; +type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures'; interface IDemoAddon { name: T; canChange: boolean; ctor: ( T extends 'attach' ? typeof AttachAddon : - T extends 'canvas' ? typeof CanvasAddon : - T extends 'clipboard' ? typeof ClipboardAddon : - T extends 'fit' ? typeof FitAddon : - T extends 'image' ? typeof ImageAddonType : - T extends 'ligatures' ? typeof LigaturesAddon : - T extends 'search' ? typeof SearchAddon : - T extends 'serialize' ? typeof SerializeAddon : - T extends 'webLinks' ? typeof WebLinksAddon : - T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon : - T extends 'webgl' ? typeof WebglAddon : - never + T extends 'clipboard' ? typeof ClipboardAddon : + T extends 'fit' ? typeof FitAddon : + T extends 'image' ? typeof ImageAddonType : + T extends 'ligatures' ? typeof LigaturesAddon : + T extends 'search' ? typeof SearchAddon : + T extends 'serialize' ? typeof SerializeAddon : + T extends 'webLinks' ? typeof WebLinksAddon : + T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon : + T extends 'webgl' ? typeof WebglAddon : + never ); instance?: ( T extends 'attach' ? AttachAddon : - T extends 'canvas' ? CanvasAddon : - T extends 'clipboard' ? ClipboardAddon : - T extends 'fit' ? FitAddon : - T extends 'image' ? ImageAddonType : - T extends 'ligatures' ? LigaturesAddon : - T extends 'search' ? SearchAddon : - T extends 'serialize' ? SerializeAddon : - T extends 'webLinks' ? WebLinksAddon : - T extends 'unicode11' ? Unicode11Addon : - T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon : - T extends 'webgl' ? WebglAddon : - never + T extends 'clipboard' ? ClipboardAddon : + T extends 'fit' ? FitAddon : + T extends 'image' ? ImageAddonType : + T extends 'ligatures' ? LigaturesAddon : + T extends 'search' ? SearchAddon : + T extends 'serialize' ? SerializeAddon : + T extends 'webLinks' ? WebLinksAddon : + T extends 'unicode11' ? Unicode11Addon : + T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon : + T extends 'webgl' ? WebglAddon : + never ); } const addons: { [T in AddonType]: IDemoAddon } = { attach: { name: 'attach', ctor: AttachAddon, canChange: false }, - canvas: { name: 'canvas', ctor: CanvasAddon, canChange: true }, clipboard: { name: 'clipboard', ctor: ClipboardAddon, canChange: true }, fit: { name: 'fit', ctor: FitAddon, canChange: false }, image: { name: 'image', ctor: ImageAddon, canChange: true }, @@ -166,7 +161,6 @@ const disposeRecreateButtonHandler: () => void = () => { window.term = null; socket = null; addons.attach.instance = undefined; - addons.canvas.instance = undefined; addons.clipboard.instance = undefined; addons.fit.instance = undefined; addons.image.instance = undefined; @@ -216,7 +210,6 @@ const createNewWindowButtonHandler: () => void = () => { if (document.location.pathname === '/test') { window.Terminal = Terminal; window.AttachAddon = AttachAddon; - window.CanvasAddon = CanvasAddon; window.ClipboardAddon = ClipboardAddon; window.FitAddon = FitAddon; window.ImageAddon = ImageAddon; @@ -647,12 +640,6 @@ function initAddons(term: Terminal): void { addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e)); }, 0); - } else if (name === 'canvas') { - setTimeout(() => { - setTextureAtlas(addons.canvas.instance.textureAtlas); - addons.canvas.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); - addons.canvas.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e)); - }, 0); } else if (name === 'unicode11') { term.unicode.activeVersion = '11'; } else if (name === 'unicodeGraphemes') { @@ -669,8 +656,6 @@ function initAddons(term: Terminal): void { } else { if (name === 'webgl') { addons.webgl.instance.textureAtlas.remove(); - } else if (name === 'canvas') { - addons.canvas.instance.textureAtlas.remove(); } else if (name === 'unicode11' || name === 'unicodeGraphemes') { term.unicode.activeVersion = '6'; } @@ -809,7 +794,7 @@ function writeCustomGlyphHandler(): void { } function loadTest(): void { - const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : 'dom'; + const rendererName = addons.webgl.instance ? 'webgl' : 'dom'; const testData = []; let byteCount = 0; for (let i = 0; i < 50; i++) { @@ -842,7 +827,7 @@ function loadTest(): void { } function loadTestLongLines(): void { - const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : 'dom'; + const rendererName = addons.webgl.instance ? 'webgl' : 'dom'; const testData = []; let byteCount = 0; for (let i = 0; i < 50; i++) { diff --git a/demo/tsconfig.json b/demo/tsconfig.json index 1d632e8f26..5569bd1d46 100644 --- a/demo/tsconfig.json +++ b/demo/tsconfig.json @@ -7,7 +7,6 @@ "baseUrl": ".", "paths": { "@xterm/addon-attach": ["../addons/addon-attach"], - "@xterm/addon-canvas": ["../addons/addon-canvas"], "@xterm/addon-clipboard": ["../addons/addon-clipboard"], "@xterm/addon-fit": ["../addons/addon-fit"], "@xterm/addon-image": ["../addons/addon-image"], diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index ce8721c03a..c25b7d524e 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -27,9 +27,9 @@ const SELECTION_CLASS = 'xterm-selection'; let nextTerminalId = 1; /** - * A fallback renderer for when canvas is slow. This is not meant to be - * particularly fast or feature complete, more just stable and usable for when - * canvas is not an option. + * The standard renderer and fallback for when the webgl addon is slow. This is not meant to be + * particularly fast and will even lack some features such as custom glyphs, hoever this is more + * reliable as webgl may not work on some machines. */ export class DomRenderer extends Disposable implements IRenderer { private _rowFactory: DomRendererRowFactory; diff --git a/tsconfig.all.json b/tsconfig.all.json index 0311a65eda..7ca3b7a791 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -7,7 +7,6 @@ { "path": "./test/benchmark" }, { "path": "./test/playwright" }, { "path": "./addons/addon-attach" }, - { "path": "./addons/addon-canvas" }, { "path": "./addons/addon-clipboard" }, { "path": "./addons/addon-fit" }, { "path": "./addons/addon-image" }, diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3aa4b1910d..af73230ab7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -612,9 +612,6 @@ declare module '@xterm/xterm' { * What layer to render the decoration at when {@link backgroundColor} or * {@link foregroundColor} are used. `'bottom'` will render under the * selection, `'top`' will render above the selection\*. - * - * *\* The selection will render on top regardless of layer on the canvas - * renderer due to how it renders selection separately.* */ readonly layer?: 'bottom' | 'top'; @@ -1116,7 +1113,7 @@ declare module '@xterm/xterm' { * render together, since they aren't drawn as optimally as individual * characters. * - * NOTE: character joiners are only used by the canvas renderer. + * NOTE: character joiners are only used by the webgl renderer. * * @param handler The function that determines character joins. It is called * with a string of text that is eligible for joining and returns an array @@ -1128,7 +1125,7 @@ declare module '@xterm/xterm' { /** * (EXPERIMENTAL) Deregisters the character joiner if one was registered. - * NOTE: character joiners are only used by the canvas renderer. + * NOTE: character joiners are only used by the webgl renderer. * @param joinerId The character joiner's ID (returned after register) */ deregisterCharacterJoiner(joinerId: number): void; @@ -1267,7 +1264,7 @@ declare module '@xterm/xterm' { refresh(start: number, end: number): void; /** - * Clears the texture atlas of the canvas renderer if it's active. Doing + * Clears the texture atlas of the webgl renderer if it's active. Doing * this will force a redraw of all glyphs which can workaround issues * causing the texture to become corrupt, for example Chromium/Nvidia has an * issue where the texture gets messed up when resuming the OS from sleep.