Merge branch 'master' into feat/3014-add-onBell-event-listener

This commit is contained in:
Daniel Imms
2021-04-06 05:28:29 -07:00
committed by GitHub
38 changed files with 572 additions and 348 deletions
+13 -1
View File
@@ -151,6 +151,10 @@
"warn",
"never"
],
"object-curly-spacing": [
"warn",
"always"
],
"prefer-const": "warn",
"spaced-comment": [
"warn",
@@ -160,5 +164,13 @@
"exceptions": ["-"]
}
]
}
},
"overrides": [
{
"files": ["**/*.test.ts"],
"rules": {
"object-curly-spacing": "off"
}
}
]
}
+2
View File
@@ -173,6 +173,8 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes.
- [**NxShell**](https://github.com/nxshell/nxshell): An easy to use new terminal for SSH.
- [**gifcast**](https://dstein64.github.io/gifcast/): Converts an asciinema cast to an animated GIF.
- [**WizardWebssh**](https://gitlab.com/mikeramsey/wizardwebssh): A terminal with Pyqt5 Widget for embedding which can be used as an ssh client to connect to your ssh servers. It is written in Python, based on tornado, paramiko and xterm.js.
- [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease.
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. Note: Please add any new contributions to the end of the list only.
+1
View File
@@ -51,3 +51,4 @@ This package makes use of the following fonts for testing:
[Fira Code License]: https://github.com/tonsky/FiraCode/blob/master/LICENSE
[Iosevka]: https://github.com/be5invis/Iosevka
[Iosevka License]: https://github.com/be5invis/Iosevka/blob/master/LICENSE.md
+1 -1
View File
@@ -32,7 +32,7 @@
"license": "MIT",
"dependencies": {
"font-finder": "^1.1.0",
"font-ligatures": "^1.3.3"
"font-ligatures": "^1.4.0"
},
"devDependencies": {
"@types/sinon": "^5.0.1",
+58 -6
View File
@@ -3,12 +3,19 @@
* @license MIT
*/
import * as fontFinder from 'font-finder';
import * as fontLigatures from 'font-ligatures';
import { FontList } from 'font-finder';
import { Font, loadBuffer, loadFile } from 'font-ligatures';
import parse from './parse';
let fontsPromise: Promise<fontFinder.FontList> | undefined = undefined;
interface IFontMetadata {
family: string;
fullName: string;
postscriptName: string;
blob: () => Promise<Blob>;
}
let fontsPromise: Promise<FontList | Record<string, IFontMetadata[]>> | undefined = undefined;
/**
* Loads the font ligature wrapper for the specified font family if it could be
@@ -16,9 +23,50 @@ let fontsPromise: Promise<fontFinder.FontList> | undefined = undefined;
* @param fontFamily The CSS font family definition to resolve
* @param cacheSize The size of the ligature cache to maintain if the font is resolved
*/
export default async function load(fontFamily: string, cacheSize: number): Promise<fontLigatures.Font | undefined> {
export default async function load(fontFamily: string, cacheSize: number): Promise<Font | undefined> {
if (!fontsPromise) {
fontsPromise = fontFinder.list();
// Web environment that supports font access API
if (typeof navigator !== 'undefined' && 'fonts' in navigator) {
try {
const status = await (navigator as any).permissions.request?.({
name: 'local-fonts'
});
if (status && status.state !== 'granted') {
throw new Error('Permission to access local fonts not granted.');
}
} catch (err) {
// A `TypeError` indicates the 'local-fonts'
// permission is not yet implemented, so
// only `throw` if this is _not_ the problem.
if (err.name !== 'TypeError') {
throw err;
}
}
const fonts: Record<string, IFontMetadata[]> = {};
try {
const fontsIterator: AsyncIterableIterator<IFontMetadata> = (navigator as any).fonts.query();
for await (const metadata of fontsIterator) {
if (!fonts.hasOwnProperty(metadata.family)) {
fonts[metadata.family] = [];
}
fonts[metadata.family].push(metadata);
}
fontsPromise = Promise.resolve(fonts);
} catch (err) {
console.error(err.name, err.message);
}
}
// Node environment or no font access API
else {
try {
fontsPromise = (await import('font-finder')).list();
} catch (err) {
// No-op
}
}
if (!fontsPromise) {
fontsPromise = Promise.resolve({});
}
}
const fonts = await fontsPromise;
@@ -31,7 +79,11 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
}
if (fonts.hasOwnProperty(family) && fonts[family].length > 0) {
return await fontLigatures.loadFile(fonts[family][0].path, { cacheSize });
const font = fonts[family][0];
if ('blob' in font) {
return loadBuffer(await (await font.blob()).arrayBuffer(), { cacheSize });
}
return await loadFile(font.path, { cacheSize });
}
}
+13 -2
View File
@@ -30,7 +30,18 @@ module.exports = {
},
mode: 'production',
externals: {
'font-finder':'font-finder',
'font-ligatures':'font-ligatures'
'font-finder': 'font-finder',
'stream': 'stream',
'os': 'os',
'util': 'util'
},
resolve: {
// The ligature modules contains fallbacks for node environments, we never want to browserify them
fallback: {
stream: false,
util: false,
os: false,
path: false
}
}
};
+15 -18
View File
@@ -87,19 +87,19 @@ font-finder@^1.0.3:
font-finder@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/font-finder/-/font-finder-1.1.0.tgz#2bff2b2762acba720239c8bec898a96daae90858"
resolved "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz#2bff2b2762acba720239c8bec898a96daae90858"
integrity sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==
dependencies:
get-system-fonts "^2.0.0"
promise-stream-reader "^1.0.1"
font-ligatures@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/font-ligatures/-/font-ligatures-1.3.3.tgz#63fff18dc8adb3a11fe5eec1f4e8d7edfa8075b9"
integrity sha512-NSGpHgVNX81M7AWS1XylK1UZbN3QllfUIDAAuPv6TUcl5O2b781JcKS5L2RopAU0AqlTyX3ZuX/04eaMpbVrHA==
font-ligatures@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.0.tgz#6a7b370d96be1358dddfad67830e82fbfd59e6dc"
integrity sha512-n7DFnnEpJ0NrVoLqZIL4tMGVs+CnFwQc92m80LWyrbgAFO4x234+t2/H9o4eOYA1eh6ta9dZAEEsJAwsBdNezA==
dependencies:
font-finder "^1.0.3"
lru-cache "^4.1.3"
lru-cache "^6.0.0"
opentype.js "^0.8.0"
get-system-fonts@^2.0.0:
@@ -144,12 +144,12 @@ lolex@^5.0.1:
dependencies:
"@sinonjs/commons" "^1.7.0"
lru-cache@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
yallist "^4.0.0"
minimist@^1.2.5:
version "1.2.5"
@@ -198,10 +198,6 @@ promise-stream-reader@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649"
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
sinon@6.3.5:
version "6.3.5"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.3.5.tgz#0f6d6a5b4ebaad1f6e8e019395542d1d02c144a0"
@@ -232,9 +228,10 @@ type-detect@4.0.8, type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yauzl@^2.10.0:
version "2.10.0"
+3 -2
View File
@@ -5,7 +5,7 @@
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
import { WebglRenderer } from './WebglRenderer';
import { IRenderService } from 'browser/services/Services';
import { ICharacterJoinerService, IRenderService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
@@ -25,8 +25,9 @@ export class WebglAddon implements ITerminalAddon {
}
this._terminal = terminal;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const characterJoinerService: ICharacterJoinerService = (<any>terminal)._core._characterJoinerService;
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer);
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer);
this._renderer.onContextLoss(() => this._onContextLoss.fire());
renderService.setRenderer(this._renderer);
}
+98 -9
View File
@@ -12,7 +12,7 @@ import { RectangleRenderer } from './RectangleRenderer';
import { IWebGL2RenderingContext } from './Types';
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
import { Disposable } from 'common/Lifecycle';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal, IEvent } from 'xterm';
import { IRenderLayer } from './renderLayer/Types';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
@@ -20,6 +20,9 @@ import { ITerminal, IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { CellData } from 'common/buffer/CellData';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { ICharacterJoinerService } from 'browser/services/Services';
import { CharData, ICellData } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
@@ -48,6 +51,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
constructor(
private _terminal: Terminal,
private _colors: IColorSet,
private readonly _characterJoinerService: ICharacterJoinerService,
preserveDrawingBuffer?: boolean
) {
super();
@@ -288,16 +292,41 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _updateModel(start: number, end: number): void {
const terminal = this._core;
let cell: ICellData = this._workCell;
for (let y = start; y <= end; y++) {
const row = y + terminal.buffer.ydisp;
const line = terminal.buffer.lines.get(row)!;
this._model.lineLengths[y] = 0;
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
for (let x = 0; x < terminal.cols; x++) {
line.loadCell(x, this._workCell);
line.loadCell(x, cell);
const chars = this._workCell.getChars();
let code = this._workCell.getCode();
// If true, indicates that the current character(s) to draw were joined.
let isJoined = false;
let lastCharX = x;
// 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(
cell,
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;
}
const chars = cell.getChars();
let code = cell.getCode();
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
if (code !== NULL_CELL_CODE) {
@@ -306,8 +335,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Nothing has changed, no updates needed
if (this._model.cells[i] === code &&
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workCell.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workCell.fg) {
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) {
continue;
}
@@ -318,10 +347,24 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Cache the results in the model
this._model.cells[i] = code;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = cell.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = cell.fg;
this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars);
this._glyphRenderer.updateCell(x, y, code, cell.bg, cell.fg, chars);
if (isJoined) {
// Restore work cell
cell = this._workCell;
// Null out non-first cells
for (x++; x < lastCharX; x++) {
const j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, NULL_CELL_CHAR);
this._model.cells[j] = NULL_CELL_CODE;
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
}
}
}
}
this._rectangleRenderer.updateBackgrounds(this._model);
@@ -438,3 +481,49 @@ export class WebglRenderer extends Disposable implements IRenderer {
this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio;
}
}
// TODO: Share impl with core
export class JoinedCellData extends AttributeData implements ICellData {
private _width: number;
// .content carries no meaning for joined CellData, simply nullify it
// thus we have to overload all other .content accessors
public content: number = 0;
public fg: number;
public bg: number;
public combinedData: string = '';
constructor(firstCell: ICellData, chars: string, width: number) {
super();
this.fg = firstCell.fg;
this.bg = firstCell.bg;
this.combinedData = chars;
this._width = width;
}
public isCombined(): number {
// always mark joined cell data as combined
return Content.IS_COMBINED_MASK;
}
public getWidth(): number {
return this._width;
}
public getChars(): string {
return this.combinedData;
}
public getCode(): number {
// code always gets the highest possible fake codepoint (read as -1)
// this is needed as code is used by caches as identifier
return 0x1FFFFF;
}
public setFromCharData(value: CharData): void {
throw new Error('not implemented');
}
public getAsCharData(): CharData {
return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
}
}
@@ -80,12 +80,12 @@ export class WebglCharAtlas implements IDisposable {
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
// It might also contain some characters with transparent backgrounds if allowTransparency is
// set.
this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', {alpha: true}));
this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', { alpha: true }));
this._tmpCanvas = document.createElement('canvas');
this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCanvas.width = this._config.scaledCharWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}));
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency }));
}
public dispose(): void {
@@ -317,6 +317,13 @@ export class WebglCharAtlas implements IDisposable {
this.hasCanvasChanged = true;
// Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used
// to draw the glyph to the canvas as well as to restrict the bounding box search to ensure
// giant ligatures (eg. =====>) don't impact overall performance.
const allowedWidth = this._config.scaledCharWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2;
if (this._tmpCanvas.width < allowedWidth) {
this._tmpCanvas.width = allowedWidth;
}
this._tmpCtx.save();
this._workAttributeData.fg = fg;
@@ -405,7 +412,7 @@ export class WebglCharAtlas implements IDisposable {
return NULL_RASTERIZED_GLYPH;
}
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, isPowerlineGlyph);
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, isPowerlineGlyph);
const clippedImageData = this._clipImageData(imageData, this._workBoundingBox);
// Check if there is enough room in the current row and go to next if needed
@@ -438,14 +445,14 @@ export class WebglCharAtlas implements IDisposable {
* @param imageData The image data to read.
* @param boundingBox An IBoundingBox to put the clipped bounding box values.
*/
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, restrictedGlyph: boolean): IRasterizedGlyph {
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean): IRasterizedGlyph {
boundingBox.top = 0;
const height = restrictedGlyph ? this._config.scaledCharHeight : this._tmpCanvas.height;
const width = restrictedGlyph ? this._config.scaledCharWidth : this._tmpCanvas.width;
const width = restrictedGlyph ? this._config.scaledCharWidth : allowedWidth;
let found = false;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const alphaOffset = y * width * 4 + x * 4 + 3;
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
if (imageData.data[alphaOffset] !== 0) {
boundingBox.top = y;
found = true;
@@ -460,7 +467,7 @@ export class WebglCharAtlas implements IDisposable {
found = false;
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const alphaOffset = y * width * 4 + x * 4 + 3;
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
if (imageData.data[alphaOffset] !== 0) {
boundingBox.left = x;
found = true;
@@ -475,7 +482,7 @@ export class WebglCharAtlas implements IDisposable {
found = false;
for (let x = width - 1; x >= 0; x--) {
for (let y = 0; y < height; y++) {
const alphaOffset = y * width * 4 + x * 4 + 3;
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
if (imageData.data[alphaOffset] !== 0) {
boundingBox.right = x;
found = true;
@@ -490,7 +497,7 @@ export class WebglCharAtlas implements IDisposable {
found = false;
for (let y = height - 1; y >= 0; y--) {
for (let x = 0; x < width; x++) {
const alphaOffset = y * width * 4 + x * 4 + 3;
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
if (imageData.data[alphaOffset] !== 0) {
boundingBox.bottom = y;
found = true;
@@ -46,7 +46,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
private _initCanvas(): void {
this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha}));
this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha }));
// Draw the background if this is an opaque layer
if (!this._alpha) {
this._clearAll();
-1
View File
@@ -36,7 +36,6 @@
*/
.xterm {
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
+13 -4
View File
@@ -16,6 +16,7 @@ import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAdd
import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon';
import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon';
import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Addon';
import { LigaturesAddon } from '../addons/xterm-addon-ligatures/out/LigaturesAddon';
// Use webpacked version (yarn package)
// import { Terminal } from '../lib/xterm';
@@ -26,6 +27,7 @@ import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Add
// import { WebLinksAddon } from 'xterm-addon-web-links';
// import { WebglAddon } from 'xterm-addon-webgl';
// import { Unicode11Addon } from 'xterm-addon-unicode11';
// import { LigaturesAddon } from 'xterm-addon-ligatures';
// Pulling in the module's types relies on the <reference> above, it's looks a
// little weird here as we're importing "this" module
@@ -41,6 +43,7 @@ export interface IWindowWithTerminal extends Window {
WebLinksAddon?: typeof WebLinksAddon;
WebglAddon?: typeof WebglAddon;
Unicode11Addon?: typeof Unicode11Addon;
LigaturesAddon?: typeof LigaturesAddon;
}
declare let window: IWindowWithTerminal;
@@ -50,7 +53,7 @@ let socketURL;
let socket;
let pid;
type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl';
type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures';
interface IDemoAddon<T extends AddonType> {
name: T;
@@ -62,8 +65,9 @@ interface IDemoAddon<T extends AddonType> {
T extends 'serialize' ? typeof SerializeAddon :
T extends 'web-links' ? typeof WebLinksAddon :
T extends 'unicode11' ? typeof Unicode11Addon :
T extends 'ligatures' ? typeof LigaturesAddon :
typeof WebglAddon;
instance?:
instance?:
T extends 'attach' ? AttachAddon :
T extends 'fit' ? FitAddon :
T extends 'search' ? SearchAddon :
@@ -71,6 +75,7 @@ interface IDemoAddon<T extends AddonType> {
T extends 'web-links' ? WebLinksAddon :
T extends 'webgl' ? WebglAddon :
T extends 'unicode11' ? typeof Unicode11Addon :
T extends 'ligatures' ? typeof LigaturesAddon :
never;
}
@@ -81,7 +86,8 @@ const addons: { [T in AddonType]: IDemoAddon<T>} = {
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true },
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true },
unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }
unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true },
ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true }
};
const terminalContainer = document.getElementById('terminal-container');
@@ -117,6 +123,7 @@ const disposeRecreateButtonHandler = () => {
addons.search.instance = undefined;
addons.serialize.instance = undefined;
addons.unicode11.instance = undefined;
addons.ligatures.instance = undefined;
addons['web-links'].instance = undefined;
addons.webgl.instance = undefined;
document.getElementById('dispose').innerHTML = 'Recreate Terminal';
@@ -133,6 +140,7 @@ if (document.location.pathname === '/test') {
window.SearchAddon = SearchAddon;
window.SerializeAddon = SerializeAddon;
window.Unicode11Addon = Unicode11Addon;
window.LigaturesAddon = LigaturesAddon;
window.WebLinksAddon = WebLinksAddon;
window.WebglAddon = WebglAddon;
} else {
@@ -149,7 +157,8 @@ function createTerminal(): void {
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
term = new Terminal({
windowsMode: isWindows
windowsMode: isWindows,
fontFamily: 'Fira Code, courier-new, courier, monospace'
} as ITerminalOptions);
// Load addons
+8
View File
@@ -49,6 +49,14 @@ const clientConfig = {
alias: {
common: path.resolve('./out/common'),
browser: path.resolve('./out/browser')
},
fallback: {
// The ligature modules contains fallbacks for node environments, we never want to browserify them
stream: false,
util: false,
os: false,
path: false,
fs: false
}
},
output: {
+16 -5
View File
@@ -21,8 +21,8 @@
* http://linux.die.net/man/7/urxvt
*/
import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types';
import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types';
import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types';
import { IRenderer } from 'browser/renderer/Types';
import { CompositionHelper } from 'browser/input/CompositionHelper';
import { Viewport } from 'browser/Viewport';
import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard';
@@ -45,7 +45,7 @@ import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services';
import { CharSizeService } from 'browser/services/CharSizeService';
import { IBuffer } from 'common/buffer/Types';
import { MouseService } from 'browser/services/MouseService';
@@ -54,6 +54,7 @@ import { CoreBrowserService } from 'browser/services/CoreBrowserService';
import { CoreTerminal } from 'common/CoreTerminal';
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
import { rgba } from 'browser/Color';
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
// Let it work inside Node.js for automated testing purposes.
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
@@ -82,6 +83,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _charSizeService: ICharSizeService | undefined;
private _mouseService: IMouseService | undefined;
private _renderService: IRenderService | undefined;
private _characterJoinerService: ICharacterJoinerService | undefined;
private _selectionService: ISelectionService | undefined;
private _soundService: ISoundService | undefined;
@@ -451,6 +453,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e)));
this._colorManager.setTheme(this._theme);
this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService);
this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService);
const renderer = this._createRenderer();
this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement));
this._instantiationService.setService(IRenderService, this._renderService);
@@ -916,13 +921,19 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
const joinerId = this._renderService!.registerCharacterJoiner(handler);
if (!this._characterJoinerService) {
throw new Error('Terminal must be opened first');
}
const joinerId = this._characterJoinerService.register(handler);
this.refresh(0, this.rows - 1);
return joinerId;
}
public deregisterCharacterJoiner(joinerId: number): void {
if (this._renderService!.deregisterCharacterJoiner(joinerId)) {
if (!this._characterJoinerService) {
throw new Error('Terminal must be opened first');
}
if (this._characterJoinerService.deregister(joinerId)) {
this.refresh(0, this.rows - 1);
}
}
+16 -11
View File
@@ -5,9 +5,9 @@
import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper } from 'browser/Types';
import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } from 'browser/Types';
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
@@ -285,8 +285,6 @@ export class MockRenderer implements IRenderer {
public onDevicePixelRatioChange(): void { }
public clear(): void { }
public renderRows(start: number, end: number): void { }
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
public deregisterCharacterJoiner(): boolean { return true; }
}
export class MockViewport implements IViewport {
@@ -410,13 +408,20 @@ export class MockRenderService implements IRenderService {
public clear(): void {
throw new Error('Method not implemented.');
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
throw new Error('Method not implemented.');
}
public deregisterCharacterJoiner(joinerId: number): boolean {
throw new Error('Method not implemented.');
}
public dispose(): void {
throw new Error('Method not implemented.');
}
}
export class MockCharacterJoinerService implements ICharacterJoinerService {
public serviceBrand: undefined;
public register(handler: (text: string) => [number, number][]): number {
return 0;
}
public deregister(joinerId: number): boolean {
return true;
}
public getJoinedCharacters(row: number): [number, number][] {
return [];
}
}
+7
View File
@@ -303,3 +303,10 @@ interface IBufferCellPosition {
x: number;
y: number;
}
export type CharacterJoinerHandler = (text: string) => [number, number][];
export interface ICharacterJoiner {
id: number;
handler: CharacterJoinerHandler;
}
+1 -1
View File
@@ -66,7 +66,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
private _initCanvas(): void {
this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha}));
this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha }));
// Draw the background if this is an opaque layer
if (!this._alpha) {
this._clearAll();
+4 -4
View File
@@ -37,10 +37,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
colors: IColorSet,
rendererId: number,
private _onRequestRedraw: IEventEmitter<IRequestRedrawEvent>,
bufferService: IBufferService,
optionsService: IOptionsService,
private readonly _coreService: ICoreService,
private readonly _coreBrowserService: ICoreBrowserService
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService,
@ICoreService private readonly _coreService: ICoreService,
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
) {
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
this._state = {
+2 -2
View File
@@ -20,8 +20,8 @@ export class LinkRenderLayer extends BaseRenderLayer {
rendererId: number,
linkifier: ILinkifier,
linkifier2: ILinkifier2,
bufferService: IBufferService,
optionsService: IOptionsService
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService
) {
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));

Some files were not shown because too many files have changed in this diff Show More