Merge pull request #3949 from Tyriar/v5_canvas

Move the canvas renderer into an addon
This commit is contained in:
Daniel Imms
2022-07-28 05:07:31 -07:00
committed by GitHub
49 changed files with 455 additions and 166 deletions
+1
View File
@@ -14,6 +14,7 @@
"test/benchmark/tsconfig.json",
"addons/xterm-addon-attach/src/tsconfig.json",
"addons/xterm-addon-attach/test/tsconfig.json",
"addons/xterm-addon-canvas/src/tsconfig.json",
"addons/xterm-addon-fit/src/tsconfig.json",
"addons/xterm-addon-fit/test/tsconfig.json",
"addons/xterm-addon-ligatures/src/tsconfig.json",
@@ -28,7 +28,7 @@ describe('AttachAddon', () => {
beforeEach(async () => await page.goto(APP));
it('string', async function(): Promise<any> {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
const port = 8080;
const server = new WebSocket.Server({ port });
server.on('connection', socket => socket.send('foo'));
@@ -38,7 +38,7 @@ describe('AttachAddon', () => {
});
it('utf8', async function(): Promise<any> {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
const port = 8080;
const server = new WebSocket.Server({ port });
const data = new Uint8Array([102, 111, 111]);
+2
View File
@@ -0,0 +1,2 @@
lib
node_modules
+29
View File
@@ -0,0 +1,29 @@
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.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
+19
View File
@@ -0,0 +1,19 @@
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.
+23
View File
@@ -0,0 +1,23 @@
## 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+.
### Install
```bash
npm install --save xterm-addon-canvas
```
### Usage
```ts
import { Terminal } from '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/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts) for more advanced usage.
+27
View File
@@ -0,0 +1,27 @@
{
"name": "xterm-addon-canvas",
"version": "0.12.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/xterm-addon-canvas.js",
"types": "typings/xterm-addon-canvas.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"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"
},
"peerDependencies": {
"xterm": "^4.0.0"
}
}
@@ -3,13 +3,14 @@
* @license MIT
*/
import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { IRenderLayer } from './Types';
import { ICellData, IColor } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
import { IGlyphIdentifier } from './atlas/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants';
import { BaseCharAtlas } from './atlas/BaseCharAtlas';
import { acquireCharAtlas } from './atlas/CharAtlasCache';
import { AttributeData } from 'common/buffer/AttributeData';
import { IColorSet } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IRenderService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { CanvasRenderer } from './CanvasRenderer';
import { IBufferService, IInstantiationService } from 'common/services/Services';
import { ITerminalAddon, Terminal } from 'xterm';
export class CanvasAddon implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: CanvasRenderer;
public activate(terminal: Terminal): void {
if (!terminal.element) {
throw new Error('Cannot activate CanvasAddon before Terminal.open');
}
this._terminal = terminal;
const instantiationService: IInstantiationService = (terminal as any)._core._instantiationService;
const bufferService: IBufferService = (terminal as any)._core._renderService;
const renderService: IRenderService = (terminal as any)._core._renderService;
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
const screenElement: HTMLElement = (terminal as any)._core.screenElement;
const linkifier = (terminal as any)._core.linkifier2;
this._renderer = instantiationService.createInstance(CanvasRenderer, colors, screenElement, linkifier);
renderService.setRenderer(this._renderer);
renderService.onResize(bufferService.cols, bufferService.rows);
}
public dispose(): void {
if (!this._terminal) {
throw new Error('Cannot dispose CanvasAddon because it is activated');
}
const renderService: IRenderService = (this._terminal as any)._core._renderService;
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(this._terminal.cols, this._terminal.rows);
this._renderer?.dispose();
this._renderer = undefined;
}
}
@@ -3,21 +3,22 @@
* @license MIT
*/
import { TextRenderLayer } from 'browser/renderer/TextRenderLayer';
import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer';
import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer';
import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
import { TextRenderLayer } from './TextRenderLayer';
import { SelectionRenderLayer } from './SelectionRenderLayer';
import { CursorRenderLayer } from './CursorRenderLayer';
import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IRenderLayer } from './Types';
import { LinkRenderLayer } from './LinkRenderLayer';
import { Disposable } from 'common/Lifecycle';
import { IColorSet, ILinkifier2 } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services';
import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services';
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
import { removeTerminalFromCache } from './atlas/CharAtlasCache';
import { EventEmitter, IEvent } from 'common/EventEmitter';
let nextRendererId = 1;
export class Renderer extends Disposable implements IRenderer {
export class CanvasRenderer extends Disposable implements IRenderer {
private _id = nextRendererId++;
private _renderLayers: IRenderLayer[];
@@ -4,7 +4,7 @@
*/
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { BaseRenderLayer } from './BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
import { GridCache } from 'browser/renderer/GridCache';
import { GridCache } from './GridCache';
describe('GridCache', () => {
let grid: GridCache<number>;
@@ -5,8 +5,8 @@
import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from './BaseRenderLayer';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants';
import { is256Color } from './atlas/CharAtlasUtils';
import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
@@ -4,7 +4,7 @@
*/
import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { BaseRenderLayer } from './BaseRenderLayer';
import { IColorSet } from 'browser/Types';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
@@ -5,8 +5,8 @@
import { IRenderDimensions } from 'browser/renderer/Types';
import { CharData, ICellData } from 'common/Types';
import { GridCache } from 'browser/renderer/GridCache';
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { GridCache } from './GridCache';
import { BaseRenderLayer } from './BaseRenderLayer';
import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, Content } from 'common/buffer/Constants';
import { IColorSet } from 'browser/Types';
+110
View File
@@ -0,0 +1,110 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDisposable } from 'common/Types';
import { IColorSet } from 'browser/Types';
import { IEvent } from 'common/EventEmitter';
// TODO: Use core interfaces
export interface IRenderDimensions {
scaledCharWidth: number;
scaledCharHeight: number;
scaledCellWidth: number;
scaledCellHeight: number;
scaledCharLeft: number;
scaledCharTop: number;
scaledCanvasWidth: number;
scaledCanvasHeight: number;
canvasWidth: number;
canvasHeight: number;
actualCellWidth: number;
actualCellHeight: number;
}
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: IEvent<IRequestRedrawEvent>;
dispose(): void;
setColors(colors: IColorSet): void;
onDevicePixelRatioChange(): void;
onResize(cols: number, rows: number): void;
onCharSizeChanged(): void;
onBlur(): void;
onFocus(): void;
onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
onCursorMove(): void;
onOptionsChanged(): void;
clear(): void;
renderRows(start: number, end: number): void;
clearTextureAtlas?(): void;
}
export interface IRenderLayer extends IDisposable {
/**
* Called when the terminal loses focus.
*/
onBlur(): void;
/**
* * Called when the terminal gets focus.
*/
onFocus(): void;
/**
* Called when the cursor is moved.
*/
onCursorMove(): void;
/**
* Called when options change.
*/
onOptionsChanged(): void;
/**
* Called when the theme changes.
*/
setColors(colorSet: IColorSet): void;
/**
* Called when the data in the grid has changed (or needs to be rendered
* again).
*/
onGridChanged(startRow: number, endRow: number): void;
/**
* Calls when the selection changes.
*/
onSelectionChanged(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;
}
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
import { IGlyphIdentifier } from './Types';
import { IDisposable } from 'common/Types';
export abstract class BaseCharAtlas implements IDisposable {
@@ -3,10 +3,10 @@
* @license MIT
*/
import { generateConfig, configEquals } from 'browser/renderer/atlas/CharAtlasUtils';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { DynamicCharAtlas } from 'browser/renderer/atlas/DynamicCharAtlas';
import { ICharAtlasConfig } from 'browser/renderer/atlas/Types';
import { generateConfig, configEquals } from './CharAtlasUtils';
import { BaseCharAtlas } from './BaseCharAtlas';
import { DynamicCharAtlas } from './DynamicCharAtlas';
import { ICharAtlasConfig } from './Types';
import { IColorSet } from 'browser/Types';
import { ITerminalOptions } from 'common/services/Services';
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ICharAtlasConfig } from 'browser/renderer/atlas/Types';
import { ICharAtlasConfig } from './Types';
import { DEFAULT_COLOR } from 'common/buffer/Constants';
import { IColorSet, IPartialColorSet } from 'browser/Types';
import { ITerminalOptions } from 'common/services/Services';
@@ -3,11 +3,11 @@
* @license MIT
*/
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IGlyphIdentifier, ICharAtlasConfig } from 'browser/renderer/atlas/Types';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants';
import { IGlyphIdentifier, ICharAtlasConfig } from './Types';
import { BaseCharAtlas } from './BaseCharAtlas';
import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager';
import { LRUMap } from 'browser/renderer/atlas/LRUMap';
import { LRUMap } from './LRUMap';
import { isFirefox, isSafari } from 'common/Platform';
import { IColor } from 'common/Types';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
import { LRUMap } from 'browser/renderer/atlas/LRUMap';
import { LRUMap } from './LRUMap';
describe('LRUMap', () => {
it('can be used to store and retrieve values', () => {
@@ -0,0 +1,41 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es6"
],
"rootDir": ".",
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"baseUrl": ".",
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"types": [
"../../../node_modules/@types/mocha"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
}
]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"include": [],
"references": [
{ "path": "./src" }
]
}
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
declare module 'xterm-addon-canvas' {
/**
* An xterm.js addon that provides search functionality.
*/
export class CanvasAddon implements ITerminalAddon {
public textureAtlas?: 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;
}
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
const path = require('path');
const addonName = 'CanvasAddon';
const mainFile = 'xterm-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')
}
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
},
mode: 'production'
};
+8 -4
View File
@@ -48,7 +48,8 @@ describe('FitAddon', () => {
it('default', async function(): Promise<any> {
await loadFit();
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
assert.equal(dimensions.cols, 87);
assert.isAbove(dimensions.cols, 85);
assert.isBelow(dimensions.cols, 88);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
});
@@ -56,7 +57,8 @@ describe('FitAddon', () => {
it('width', async function(): Promise<any> {
await loadFit(1008);
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
assert.equal(dimensions.cols, 110);
assert.isAbove(dimensions.cols, 108);
assert.isBelow(dimensions.cols, 111);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
});
@@ -89,7 +91,8 @@ describe('FitAddon', () => {
await page.evaluate(`window.fit.fit()`);
const cols: number = await page.evaluate(`window.term.cols`);
const rows: number = await page.evaluate(`window.term.rows`);
assert.equal(cols, 87);
assert.isAbove(cols, 85);
assert.isBelow(cols, 88);
assert.isAbove(rows, 24);
assert.isBelow(rows, 29);
});
@@ -99,7 +102,8 @@ describe('FitAddon', () => {
await page.evaluate(`window.fit.fit()`);
const cols: number = await page.evaluate(`window.term.cols`);
const rows: number = await page.evaluate(`window.term.rows`);
assert.equal(cols, 110);
assert.isAbove(cols, 108);
assert.isBelow(cols, 111);
assert.isAbove(rows, 24);
assert.isBelow(rows, 29);
});
@@ -42,7 +42,7 @@ describe('SerializeAddon', () => {
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal(page, { rows: 10, cols: 10, rendererType: 'dom' });
await openTerminal(page, { rows: 10, cols: 10 });
await page.evaluate(`
window.serializeAddon = new SerializeAddon();
window.term.loadAddon(window.serializeAddon);
@@ -38,7 +38,7 @@ describe('WebLinksAddon', () => {
});
async function testHostName(hostname: string): Promise<void> {
await openTerminal(page, { rendererType: 'dom', cols: 40 });
await openTerminal(page, { cols: 40 });
await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
const data = ` http://${hostname} \\r\\n` +
` http://${hostname}/a~b#c~d?e~f \\r\\n` +
+1
View File
@@ -16,6 +16,7 @@ import { Terminal } from 'xterm';
import { WebglAddon } from 'xterm-addon-webgl';
const terminal = new Terminal();
terminal.open(element);
terminal.loadAddon(new WebglAddon());
```
@@ -4,14 +4,14 @@
*/
import { ICharAtlasConfig } from './Types';
import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/Constants';
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
import { throwIfFalsy } from '../WebglUtils';
import { IColor } from 'common/Types';
import { IDisposable } from 'xterm';
import { AttributeData } from 'common/buffer/AttributeData';
import { channels, color, rgba } from 'common/Color';
import { color, rgba } from 'common/Color';
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
import { excludeFromContrastRatioDemands, isPowerlineGlyph } from 'browser/renderer/RendererUtils';
@@ -7,7 +7,7 @@ import { IRenderLayer } from './Types';
import { acquireCharAtlas } from '../atlas/CharAtlasCache';
import { Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
import { TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { TEXT_BASELINE } from 'browser/renderer/Constants';
import { IRenderDimensions } from 'browser/renderer/Types';
import { CellData } from 'common/buffer/CellData';
import { WebglCharAtlas } from 'atlas/WebglCharAtlas';
@@ -5,7 +5,7 @@
import { Terminal } from 'xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants';
import { is256Color } from '../atlas/CharAtlasUtils';
import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
+1 -1
View File
@@ -4,7 +4,7 @@
"target": "es5",
"lib": [
"dom",
"es6",
"es6"
],
"rootDir": ".",
"outDir": "../out",
@@ -860,7 +860,7 @@ describe('WebGL Renderer Integration Tests', async () => {
describe('allowTransparency', async () => {
if (areTestsEnabled) {
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true }));
before(async () => setupBrowser({ allowTransparency: true }));
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
@@ -879,7 +879,7 @@ describe('WebGL Renderer Integration Tests', async () => {
describe('selectionForeground', () => {
if (areTestsEnabled) {
before(async () => setupBrowser({ rendererType: 'dom' }));
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
@@ -898,7 +898,7 @@ describe('WebGL Renderer Integration Tests', async () => {
describe('decoration color overrides', async () => {
if (areTestsEnabled) {
before(async () => setupBrowser({ rendererType: 'dom' }));
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
@@ -1014,7 +1014,7 @@ async function getCellPixels(col: number, row: number): Promise<number[]> {
return await page.evaluate(`Array.from(window.result)`);
}
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
async function setupBrowser(options: ITerminalOptions = {}): Promise<void> {
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
+8 -4
View File
@@ -10,6 +10,7 @@
// Use tsc version (yarn watch)
import { Terminal } from '../out/browser/public/Terminal';
import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon';
import { CanvasAddon } from '../addons/xterm-addon-canvas/out/CanvasAddon';
import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon';
import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon';
import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAddon';
@@ -53,13 +54,14 @@ let socketURL;
let socket;
let pid;
type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures';
type AddonType = 'attach' | 'canvas' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures';
interface IDemoAddon<T extends AddonType> {
name: T;
canChange: boolean;
ctor:
T extends 'attach' ? typeof AttachAddon :
T extends 'canvas' ? typeof CanvasAddon :
T extends 'fit' ? typeof FitAddon :
T extends 'search' ? typeof SearchAddon :
T extends 'serialize' ? typeof SerializeAddon :
@@ -69,6 +71,7 @@ interface IDemoAddon<T extends AddonType> {
typeof WebglAddon;
instance?:
T extends 'attach' ? AttachAddon :
T extends 'canvas' ? CanvasAddon :
T extends 'fit' ? FitAddon :
T extends 'search' ? SearchAddon :
T extends 'serialize' ? SerializeAddon :
@@ -81,6 +84,7 @@ interface IDemoAddon<T extends AddonType> {
const addons: { [T in AddonType]: IDemoAddon<T>} = {
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
canvas: { name: 'canvas', ctor: CanvasAddon, canChange: true },
fit: { name: 'fit', ctor: FitAddon, canChange: false },
search: { name: 'search', ctor: SearchAddon, canChange: true },
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
@@ -150,6 +154,7 @@ const disposeRecreateButtonHandler = () => {
window.term = null;
socket = null;
addons.attach.instance = undefined;
addons.canvas.instance = undefined;
addons.fit.instance = undefined;
addons.search.instance = undefined;
addons.serialize.instance = undefined;
@@ -331,7 +336,6 @@ function initOptions(term: TerminalType): void {
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
logLevel: ['debug', 'info', 'warn', 'error', 'off'],
rendererType: ['dom', 'canvas'],
theme: ['default', 'xtermjs', 'sapphire', 'light'],
wordSeparator: null
};
@@ -628,7 +632,7 @@ function writeCustomGlyphHandler() {
}
function loadTest() {
const isWebglEnabled = !!addons.webgl.instance;
const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : 'dom';
const testData = [];
let byteCount = 0;
for (let i = 0; i < 50; i++) {
@@ -654,7 +658,7 @@ function loadTest() {
term.write('', () => {
const time = Math.round(performance.now() - start);
const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2);
term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${isWebglEnabled ? 'webgl' : 'canvas'} renderer)`);
term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${rendererName} renderer)`);
// Send ^C to get a new prompt
term._core._onData.fire('\x03');
});
+1 -12
View File
@@ -28,7 +28,6 @@ import { Viewport } from 'browser/Viewport';
import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard';
import { C0, C1_ESCAPED } from 'common/data/EscapeSequences';
import { WindowsOptionsReportType } from '../common/InputHandler';
import { Renderer } from 'browser/renderer/Renderer';
import { SelectionService } from 'browser/services/SelectionService';
import * as Browser from 'common/Platform';
import { addDisposableDomListener } from 'browser/Lifecycle';
@@ -296,12 +295,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.refresh(0, this.rows - 1);
}
break;
case 'rendererType':
if (this._renderService) {
this._renderService.setRenderer(this._createRenderer());
this._renderService.onResize(this.cols, this.rows);
}
break;
case 'scrollback':
this.viewport?.syncScrollArea();
break;
@@ -615,11 +608,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
private _createRenderer(): IRenderer {
switch (this.options.rendererType) {
case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier2);
case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2);
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
}
return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2);
}
/**
@@ -6,10 +6,9 @@
import { isFirefox, isLegacyEdge } from 'common/Platform';
export const INVERTED_DEFAULT_COLOR = 257;
export const DIM_OPACITY = 0.5;
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge would
// result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
// unaligned Powerline fonts (PR 3356#issuecomment-850928179).
export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic';
export const CHAR_ATLAS_CELL_SPACING = 1;
-53
View File
@@ -54,56 +54,3 @@ export interface IRenderer extends IDisposable {
renderRows(start: number, end: number): void;
clearTextureAtlas?(): void;
}
export interface IRenderLayer extends IDisposable {
/**
* Called when the terminal loses focus.
*/
onBlur(): void;
/**
* * Called when the terminal gets focus.
*/
onFocus(): void;
/**
* Called when the cursor is moved.
*/
onCursorMove(): void;
/**
* Called when options change.
*/
onOptionsChanged(): void;
/**
* Called when the theme changes.
*/
setColors(colorSet: IColorSet): void;
/**
* Called when the data in the grid has changed (or needs to be rendered
* again).
*/
onGridChanged(startRow: number, endRow: number): void;
/**
* Calls when the selection changes.
*/
onSelectionChanged(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 -1
View File
@@ -5,7 +5,7 @@
import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants';
import { Disposable } from 'common/Lifecycle';
import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services';
@@ -4,13 +4,13 @@
*/
import { IBufferLine, ICellData, IColor } from 'common/Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants';
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { color, rgba } from 'common/Color';
import { IColorSet } from 'browser/Types';
import { ICharacterJoinerService, ISelectionService } from 'browser/services/Services';
import { ICharacterJoinerService } from 'browser/services/Services';
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
import { excludeFromContrastRatioDemands } from 'browser/renderer/RendererUtils';
-2
View File
@@ -37,7 +37,6 @@ export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
tabStopWidth: 8,
theme: {},
rightClickSelectsWord: isMac,
rendererType: 'canvas',
windowOptions: {},
windowsMode: false,
wordSeparator: ' ()[]{}\',"`',
@@ -120,7 +119,6 @@ export class OptionsService implements IOptionsService {
}
break;
case 'cursorStyle':
case 'rendererType':
case 'wordSeparator':
if (!value) {
value = DEFAULT_OPTIONS[key];
-3
View File
@@ -203,8 +203,6 @@ export interface IOptionsService {
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
export type RendererType = 'dom' | 'canvas';
export interface ITerminalOptions {
allowProposedApi: boolean;
allowTransparency: boolean;
@@ -229,7 +227,6 @@ export interface ITerminalOptions {
macOptionIsMeta: boolean;
macOptionClickForcesSelection: boolean;
minimumContrastRatio: number;
rendererType: RendererType;
rightClickSelectsWord: boolean;
rows: number;
screenReaderMode: boolean;
+11 -18
View File
@@ -157,7 +157,6 @@ describe('API Integration Tests', function(): void {
describe('options', () => {
it('getter', async () => {
await openTerminal(page);
assert.equal(await page.evaluate(`window.term.options.rendererType`), 'canvas');
assert.equal(await page.evaluate(`window.term.options.cols`), 80);
assert.equal(await page.evaluate(`window.term.options.rows`), 24);
});
@@ -190,7 +189,7 @@ describe('API Integration Tests', function(): void {
describe('renderer', () => {
it('foreground', async () => {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
await writeSync(page, '\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7');
await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9);
assert.deepEqual(await page.evaluate(`
@@ -215,7 +214,7 @@ describe('API Integration Tests', function(): void {
});
it('background', async () => {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
await writeSync(page, '\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7');
await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9);
assert.deepEqual(await page.evaluate(`
@@ -727,9 +726,7 @@ describe('API Integration Tests', function(): void {
describe('registerDecoration', () => {
describe('bufferDecorations', () => {
it('should register decorations and render them when terminal open is called', async () => {
await page.evaluate(`window.term = new Terminal({ allowProposedApi: true })`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await openTerminal(page);
await page.evaluate(`window.marker1 = window.term.registerMarker(1)`);
await page.evaluate(`window.marker2 = window.term.registerMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);
@@ -758,9 +755,7 @@ describe('API Integration Tests', function(): void {
});
describe('overviewRulerDecorations', () => {
it('should not add an overview ruler when width is not set', async () => {
await page.evaluate(`window.term = new Terminal({ allowProposedApi: true })`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await openTerminal(page);
await page.evaluate(`window.marker1 = window.term.registerMarker(1)`);
await page.evaluate(`window.marker2 = window.term.registerMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);
@@ -769,9 +764,7 @@ describe('API Integration Tests', function(): void {
await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0);
});
it('should add an overview ruler when width is set', async () => {
await page.evaluate(`window.term = new Terminal({ allowProposedApi: true, overviewRulerWidth: 15 })`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await openTerminal(page, { overviewRulerWidth: 15 });
await page.evaluate(`window.marker1 = window.term.registerMarker(1)`);
await page.evaluate(`window.marker2 = window.term.registerMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);
@@ -784,7 +777,7 @@ describe('API Integration Tests', function(): void {
describe('registerLinkProvider', () => {
it('should fire provideLinks when hovering cells', async () => {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
@@ -806,7 +799,7 @@ describe('API Integration Tests', function(): void {
});
it('should fire hover and leave events on the link', async () => {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
@@ -844,7 +837,7 @@ describe('API Integration Tests', function(): void {
});
it('should work fine when hover and leave callbacks are not provided', async () => {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
@@ -887,7 +880,7 @@ describe('API Integration Tests', function(): void {
});
it('should fire activate events when clicking the link', async () => {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
@@ -929,7 +922,7 @@ describe('API Integration Tests', function(): void {
});
it('should work when multiple links are provided on the same line', async () => {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
@@ -978,7 +971,7 @@ describe('API Integration Tests', function(): void {
});
it('should dispose links when hovering away', async () => {
await openTerminal(page, { rendererType: 'dom' });
await openTerminal(page);
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
+2 -6
View File
@@ -46,11 +46,7 @@ export async function timeout(ms: number): Promise<void> {
export async function openTerminal(page: playwright.Page, options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify({ allowProposedApi: true, ...options })})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
if (options.rendererType === 'dom') {
await page.waitForSelector('.xterm-rows');
} else {
await page.waitForSelector('.xterm-text-layer');
}
await page.waitForSelector('.xterm-rows');
}
export function getBrowserType(): playwright.BrowserType<playwright.WebKitBrowser> | playwright.BrowserType<playwright.ChromiumBrowser> | playwright.BrowserType<playwright.FirefoxBrowser> {
@@ -68,7 +64,7 @@ export function getBrowserType(): playwright.BrowserType<playwright.WebKitBrowse
return browserType;
}
export function launchBrowser() {
export function launchBrowser(): Promise<playwright.Browser> {
const browserType = getBrowserType();
const options: Record<string, unknown> = {
headless: process.argv.includes('--headless')
+1
View File
@@ -7,6 +7,7 @@
{ "path": "./test/api" },
{ "path": "./test/benchmark" },
{ "path": "./addons/xterm-addon-attach" },
{ "path": "./addons/xterm-addon-canvas" },
{ "path": "./addons/xterm-addon-fit" },
{ "path": "./addons/xterm-addon-ligatures" },
{ "path": "./addons/xterm-addon-search" },
-15
View File
@@ -20,11 +20,6 @@ declare module 'xterm' {
*/
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
/**
* A string representing a renderer type.
*/
export type RendererType = 'dom' | 'canvas';
/**
* An object containing start up options for the terminal.
*/
@@ -176,16 +171,6 @@ declare module 'xterm' {
*/
minimumContrastRatio?: number;
/**
* The type of renderer to use, this allows using the fallback DOM renderer
* when canvas is too slow for the environment. The following features do
* not work when the DOM renderer is used:
*
* - Letter spacing
* - Cursor blink
*/
rendererType?: RendererType;
/**
* Whether to select the word under the cursor on right click, this is
* standard behavior in a lot of macOS applications.