@@ -16,8 +16,6 @@
|
|||||||
"test/playwright/tsconfig.json",
|
"test/playwright/tsconfig.json",
|
||||||
"addons/addon-attach/src/tsconfig.json",
|
"addons/addon-attach/src/tsconfig.json",
|
||||||
"addons/addon-attach/test/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/src/tsconfig.json",
|
||||||
"addons/addon-clipboard/test/tsconfig.json",
|
"addons/addon-clipboard/test/tsconfig.json",
|
||||||
"addons/addon-fit/src/tsconfig.json",
|
"addons/addon-fit/src/tsconfig.json",
|
||||||
|
|||||||
@@ -32,9 +32,6 @@ jobs:
|
|||||||
./addons/addon-attach/lib/* \
|
./addons/addon-attach/lib/* \
|
||||||
./addons/addon-attach/out/* \
|
./addons/addon-attach/out/* \
|
||||||
./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/lib/* \
|
||||||
./addons/addon-clipboard/out/* \
|
./addons/addon-clipboard/out/* \
|
||||||
./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
|
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=core
|
||||||
- name: Integration tests (addon-attach)
|
- name: Integration tests (addon-attach)
|
||||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=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)
|
- name: Integration tests (addon-clipboard)
|
||||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-clipboard
|
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-clipboard
|
||||||
- name: Integration tests (addon-fit)
|
- name: Integration tests (addon-fit)
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ terminal.loadAddon(new WebLinksAddon());
|
|||||||
The xterm.js team maintains the following addons, but anyone can build them:
|
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-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-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-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
|
- [`@xterm/addon-image`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image): Adds image support
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
lib
|
|
||||||
node_modules
|
|
||||||
@@ -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
|
|
||||||
@@ -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.
|
|
||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<HTMLCanvasElement>());
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<HTMLCanvasElement>());
|
|
||||||
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
|
|
||||||
private readonly _onAddTextureAtlasCanvas = this.register(new Emitter<HTMLCanvasElement>());
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<IRequestRedrawEvent>());
|
|
||||||
public readonly onRequestRedraw = this._onRequestRedraw.event;
|
|
||||||
private readonly _onChangeTextureAtlas = this.register(new Emitter<HTMLCanvasElement>());
|
|
||||||
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
|
|
||||||
private readonly _onAddTextureAtlasCanvas = this.register(new Emitter<HTMLCanvasElement>());
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<CursorBlinkStateManager> = this.register(new MutableDisposable());
|
|
||||||
private _cell: ICellData = new CellData();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
terminal: Terminal,
|
|
||||||
container: HTMLElement,
|
|
||||||
zIndex: number,
|
|
||||||
private readonly _onRequestRedraw: Emitter<IRequestRedrawEvent>,
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<number>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
grid = new GridCache<number>();
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class GridCache<T> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<CharData>;
|
|
||||||
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<CharData>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<IRequestRedrawEvent>;
|
|
||||||
|
|
||||||
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<HTMLCanvasElement>;
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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;
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"files": [],
|
|
||||||
"include": [],
|
|
||||||
"references": [
|
|
||||||
{ "path": "./src" },
|
|
||||||
{ "path": "./test" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
-43
@@ -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<HTMLCanvasElement>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event that is fired when the a new page is added to the texture atlas.
|
|
||||||
*/
|
|
||||||
public readonly onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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'
|
|
||||||
};
|
|
||||||
@@ -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).
|
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
|
|
||||||
|
|||||||
@@ -133,7 +133,6 @@ if (config.addon) {
|
|||||||
// Library ESM imports
|
// Library ESM imports
|
||||||
"@xterm/xterm": ".",
|
"@xterm/xterm": ".",
|
||||||
"@xterm/addon-attach": "./addons/addon-attach/lib/xterm-addon-attach.mjs",
|
"@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-clipboard": "./addons/addon-clipboard/lib/xterm-addon-clipboard.mjs",
|
||||||
"@xterm/addon-fit": "./addons/addon-fit/lib/xterm-addon-fit.mjs",
|
"@xterm/addon-fit": "./addons/addon-fit/lib/xterm-addon-fit.mjs",
|
||||||
"@xterm/addon-image": "./addons/addon-image/lib/xterm-addon-image.mjs",
|
"@xterm/addon-image": "./addons/addon-image/lib/xterm-addon-image.mjs",
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
|
|||||||
// Publish addons if any files were changed inside of the addon
|
// Publish addons if any files were changed inside of the addon
|
||||||
const addonPackageDirs = [
|
const addonPackageDirs = [
|
||||||
path.resolve(__dirname, '../addons/addon-attach'),
|
path.resolve(__dirname, '../addons/addon-attach'),
|
||||||
path.resolve(__dirname, '../addons/addon-canvas'),
|
|
||||||
path.resolve(__dirname, '../addons/addon-clipboard'),
|
path.resolve(__dirname, '../addons/addon-clipboard'),
|
||||||
path.resolve(__dirname, '../addons/addon-fit'),
|
path.resolve(__dirname, '../addons/addon-fit'),
|
||||||
path.resolve(__dirname, '../addons/addon-image'),
|
path.resolve(__dirname, '../addons/addon-image'),
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ let configs = [
|
|||||||
];
|
];
|
||||||
const addons = [
|
const addons = [
|
||||||
'attach',
|
'attach',
|
||||||
'canvas',
|
|
||||||
'clipboard',
|
'clipboard',
|
||||||
'fit',
|
'fit',
|
||||||
'image',
|
'image',
|
||||||
|
|||||||
+25
-40
@@ -18,7 +18,6 @@ if ('WebAssembly' in window) {
|
|||||||
|
|
||||||
import { Terminal, ITerminalOptions, type IDisposable } from '@xterm/xterm';
|
import { Terminal, ITerminalOptions, type IDisposable } from '@xterm/xterm';
|
||||||
import { AttachAddon } from '@xterm/addon-attach';
|
import { AttachAddon } from '@xterm/addon-attach';
|
||||||
import { CanvasAddon } from '@xterm/addon-canvas';
|
|
||||||
import { ClipboardAddon } from '@xterm/addon-clipboard';
|
import { ClipboardAddon } from '@xterm/addon-clipboard';
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
import { LigaturesAddon } from '@xterm/addon-ligatures';
|
import { LigaturesAddon } from '@xterm/addon-ligatures';
|
||||||
@@ -33,7 +32,6 @@ export interface IWindowWithTerminal extends Window {
|
|||||||
term: typeof Terminal;
|
term: typeof Terminal;
|
||||||
Terminal: typeof Terminal;
|
Terminal: typeof Terminal;
|
||||||
AttachAddon?: typeof AttachAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
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
|
ClipboardAddon?: typeof ClipboardAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
FitAddon?: typeof FitAddon; // 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
|
ImageAddon?: typeof ImageAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
@@ -54,46 +52,43 @@ let socket;
|
|||||||
let pid;
|
let pid;
|
||||||
let autoResize: boolean = true;
|
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<T extends AddonType> {
|
interface IDemoAddon<T extends AddonType> {
|
||||||
name: T;
|
name: T;
|
||||||
canChange: boolean;
|
canChange: boolean;
|
||||||
ctor: (
|
ctor: (
|
||||||
T extends 'attach' ? typeof AttachAddon :
|
T extends 'attach' ? typeof AttachAddon :
|
||||||
T extends 'canvas' ? typeof CanvasAddon :
|
T extends 'clipboard' ? typeof ClipboardAddon :
|
||||||
T extends 'clipboard' ? typeof ClipboardAddon :
|
T extends 'fit' ? typeof FitAddon :
|
||||||
T extends 'fit' ? typeof FitAddon :
|
T extends 'image' ? typeof ImageAddonType :
|
||||||
T extends 'image' ? typeof ImageAddonType :
|
T extends 'ligatures' ? typeof LigaturesAddon :
|
||||||
T extends 'ligatures' ? typeof LigaturesAddon :
|
T extends 'search' ? typeof SearchAddon :
|
||||||
T extends 'search' ? typeof SearchAddon :
|
T extends 'serialize' ? typeof SerializeAddon :
|
||||||
T extends 'serialize' ? typeof SerializeAddon :
|
T extends 'webLinks' ? typeof WebLinksAddon :
|
||||||
T extends 'webLinks' ? typeof WebLinksAddon :
|
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :
|
||||||
T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :
|
T extends 'webgl' ? typeof WebglAddon :
|
||||||
T extends 'webgl' ? typeof WebglAddon :
|
never
|
||||||
never
|
|
||||||
);
|
);
|
||||||
instance?: (
|
instance?: (
|
||||||
T extends 'attach' ? AttachAddon :
|
T extends 'attach' ? AttachAddon :
|
||||||
T extends 'canvas' ? CanvasAddon :
|
T extends 'clipboard' ? ClipboardAddon :
|
||||||
T extends 'clipboard' ? ClipboardAddon :
|
T extends 'fit' ? FitAddon :
|
||||||
T extends 'fit' ? FitAddon :
|
T extends 'image' ? ImageAddonType :
|
||||||
T extends 'image' ? ImageAddonType :
|
T extends 'ligatures' ? LigaturesAddon :
|
||||||
T extends 'ligatures' ? LigaturesAddon :
|
T extends 'search' ? SearchAddon :
|
||||||
T extends 'search' ? SearchAddon :
|
T extends 'serialize' ? SerializeAddon :
|
||||||
T extends 'serialize' ? SerializeAddon :
|
T extends 'webLinks' ? WebLinksAddon :
|
||||||
T extends 'webLinks' ? WebLinksAddon :
|
T extends 'unicode11' ? Unicode11Addon :
|
||||||
T extends 'unicode11' ? Unicode11Addon :
|
T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :
|
||||||
T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :
|
T extends 'webgl' ? WebglAddon :
|
||||||
T extends 'webgl' ? WebglAddon :
|
never
|
||||||
never
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addons: { [T in AddonType]: IDemoAddon<T> } = {
|
const addons: { [T in AddonType]: IDemoAddon<T> } = {
|
||||||
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
|
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
|
||||||
canvas: { name: 'canvas', ctor: CanvasAddon, canChange: true },
|
|
||||||
clipboard: { name: 'clipboard', ctor: ClipboardAddon, canChange: true },
|
clipboard: { name: 'clipboard', ctor: ClipboardAddon, canChange: true },
|
||||||
fit: { name: 'fit', ctor: FitAddon, canChange: false },
|
fit: { name: 'fit', ctor: FitAddon, canChange: false },
|
||||||
image: { name: 'image', ctor: ImageAddon, canChange: true },
|
image: { name: 'image', ctor: ImageAddon, canChange: true },
|
||||||
@@ -166,7 +161,6 @@ const disposeRecreateButtonHandler: () => void = () => {
|
|||||||
window.term = null;
|
window.term = null;
|
||||||
socket = null;
|
socket = null;
|
||||||
addons.attach.instance = undefined;
|
addons.attach.instance = undefined;
|
||||||
addons.canvas.instance = undefined;
|
|
||||||
addons.clipboard.instance = undefined;
|
addons.clipboard.instance = undefined;
|
||||||
addons.fit.instance = undefined;
|
addons.fit.instance = undefined;
|
||||||
addons.image.instance = undefined;
|
addons.image.instance = undefined;
|
||||||
@@ -216,7 +210,6 @@ const createNewWindowButtonHandler: () => void = () => {
|
|||||||
if (document.location.pathname === '/test') {
|
if (document.location.pathname === '/test') {
|
||||||
window.Terminal = Terminal;
|
window.Terminal = Terminal;
|
||||||
window.AttachAddon = AttachAddon;
|
window.AttachAddon = AttachAddon;
|
||||||
window.CanvasAddon = CanvasAddon;
|
|
||||||
window.ClipboardAddon = ClipboardAddon;
|
window.ClipboardAddon = ClipboardAddon;
|
||||||
window.FitAddon = FitAddon;
|
window.FitAddon = FitAddon;
|
||||||
window.ImageAddon = ImageAddon;
|
window.ImageAddon = ImageAddon;
|
||||||
@@ -647,12 +640,6 @@ function initAddons(term: Terminal): void {
|
|||||||
addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e));
|
addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e));
|
||||||
addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e));
|
addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e));
|
||||||
}, 0);
|
}, 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') {
|
} else if (name === 'unicode11') {
|
||||||
term.unicode.activeVersion = '11';
|
term.unicode.activeVersion = '11';
|
||||||
} else if (name === 'unicodeGraphemes') {
|
} else if (name === 'unicodeGraphemes') {
|
||||||
@@ -669,8 +656,6 @@ function initAddons(term: Terminal): void {
|
|||||||
} else {
|
} else {
|
||||||
if (name === 'webgl') {
|
if (name === 'webgl') {
|
||||||
addons.webgl.instance.textureAtlas.remove();
|
addons.webgl.instance.textureAtlas.remove();
|
||||||
} else if (name === 'canvas') {
|
|
||||||
addons.canvas.instance.textureAtlas.remove();
|
|
||||||
} else if (name === 'unicode11' || name === 'unicodeGraphemes') {
|
} else if (name === 'unicode11' || name === 'unicodeGraphemes') {
|
||||||
term.unicode.activeVersion = '6';
|
term.unicode.activeVersion = '6';
|
||||||
}
|
}
|
||||||
@@ -809,7 +794,7 @@ function writeCustomGlyphHandler(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadTest(): void {
|
function loadTest(): void {
|
||||||
const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : 'dom';
|
const rendererName = addons.webgl.instance ? 'webgl' : 'dom';
|
||||||
const testData = [];
|
const testData = [];
|
||||||
let byteCount = 0;
|
let byteCount = 0;
|
||||||
for (let i = 0; i < 50; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
@@ -842,7 +827,7 @@ function loadTest(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadTestLongLines(): void {
|
function loadTestLongLines(): void {
|
||||||
const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : 'dom';
|
const rendererName = addons.webgl.instance ? 'webgl' : 'dom';
|
||||||
const testData = [];
|
const testData = [];
|
||||||
let byteCount = 0;
|
let byteCount = 0;
|
||||||
for (let i = 0; i < 50; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@xterm/addon-attach": ["../addons/addon-attach"],
|
"@xterm/addon-attach": ["../addons/addon-attach"],
|
||||||
"@xterm/addon-canvas": ["../addons/addon-canvas"],
|
|
||||||
"@xterm/addon-clipboard": ["../addons/addon-clipboard"],
|
"@xterm/addon-clipboard": ["../addons/addon-clipboard"],
|
||||||
"@xterm/addon-fit": ["../addons/addon-fit"],
|
"@xterm/addon-fit": ["../addons/addon-fit"],
|
||||||
"@xterm/addon-image": ["../addons/addon-image"],
|
"@xterm/addon-image": ["../addons/addon-image"],
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ const SELECTION_CLASS = 'xterm-selection';
|
|||||||
let nextTerminalId = 1;
|
let nextTerminalId = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fallback renderer for when canvas is slow. This is not meant to be
|
* The standard renderer and fallback for when the webgl addon is slow. This is not meant to be
|
||||||
* particularly fast or feature complete, more just stable and usable for when
|
* particularly fast and will even lack some features such as custom glyphs, hoever this is more
|
||||||
* canvas is not an option.
|
* reliable as webgl may not work on some machines.
|
||||||
*/
|
*/
|
||||||
export class DomRenderer extends Disposable implements IRenderer {
|
export class DomRenderer extends Disposable implements IRenderer {
|
||||||
private _rowFactory: DomRendererRowFactory;
|
private _rowFactory: DomRendererRowFactory;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
{ "path": "./test/benchmark" },
|
{ "path": "./test/benchmark" },
|
||||||
{ "path": "./test/playwright" },
|
{ "path": "./test/playwright" },
|
||||||
{ "path": "./addons/addon-attach" },
|
{ "path": "./addons/addon-attach" },
|
||||||
{ "path": "./addons/addon-canvas" },
|
|
||||||
{ "path": "./addons/addon-clipboard" },
|
{ "path": "./addons/addon-clipboard" },
|
||||||
{ "path": "./addons/addon-fit" },
|
{ "path": "./addons/addon-fit" },
|
||||||
{ "path": "./addons/addon-image" },
|
{ "path": "./addons/addon-image" },
|
||||||
|
|||||||
Vendored
+3
-6
@@ -612,9 +612,6 @@ declare module '@xterm/xterm' {
|
|||||||
* What layer to render the decoration at when {@link backgroundColor} or
|
* What layer to render the decoration at when {@link backgroundColor} or
|
||||||
* {@link foregroundColor} are used. `'bottom'` will render under the
|
* {@link foregroundColor} are used. `'bottom'` will render under the
|
||||||
* selection, `'top`' will render above the selection\*.
|
* 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';
|
readonly layer?: 'bottom' | 'top';
|
||||||
|
|
||||||
@@ -1116,7 +1113,7 @@ declare module '@xterm/xterm' {
|
|||||||
* render together, since they aren't drawn as optimally as individual
|
* render together, since they aren't drawn as optimally as individual
|
||||||
* characters.
|
* 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
|
* @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
|
* 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.
|
* (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)
|
* @param joinerId The character joiner's ID (returned after register)
|
||||||
*/
|
*/
|
||||||
deregisterCharacterJoiner(joinerId: number): void;
|
deregisterCharacterJoiner(joinerId: number): void;
|
||||||
@@ -1267,7 +1264,7 @@ declare module '@xterm/xterm' {
|
|||||||
refresh(start: number, end: number): void;
|
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
|
* this will force a redraw of all glyphs which can workaround issues
|
||||||
* causing the texture to become corrupt, for example Chromium/Nvidia has an
|
* 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.
|
* issue where the texture gets messed up when resuming the OS from sleep.
|
||||||
|
|||||||
Reference in New Issue
Block a user