Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support fallback ligatures and correctly dispose of char joiner #3963

Merged
merged 3 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions addons/xterm-addon-ligatures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,16 @@ This package locates the font file on disk for the font currently in use by the

Since this package depends on being able to find and resolve a system font from disk, it has to have system access that isn't available in the web browser. As a result, this package is mainly useful in environments that combine browser and Node.js runtimes (such as [Electron]).

### Fallback Ligatures

When ligatures cannot be fetched from the environment, a set of "fallback" ligatures is used to get the most common ligatures working. These fallback ligatures can be customized with options passed to `LigatureAddon.constructor`.

### Fonts

This package makes use of the following fonts for testing:

* [Fira Code][Fira Code] - [Licensed under the OFL][Fira Code License] by Nikita
Prokopov, Mozilla Foundation with reserved names Fira Code, Fira Mono, and
Fira Sans
* [Iosevka] - [Licensed under the OFL][Iosevka License] by Belleve Invis with
reserved name Iosevka
* [Fira Code][Fira Code] - [Licensed under the OFL][Fira Code License] by Nikita Prokopov, Mozilla Foundation with reserved names Fira Code, Fira Mono, and Fira Sans
* [Iosevka] - [Licensed under the OFL][Iosevka License] by Belleve Invis with reserved name Iosevka

[xterm.js]: https://github.com/xtermjs/xterm.js
[Electron]: https://electronjs.org/
Expand Down
28 changes: 24 additions & 4 deletions addons/xterm-addon-ligatures/src/LigaturesAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,39 @@

import { Terminal } from 'xterm';
import { enableLigatures } from '.';
import { ILigatureOptions } from './Types';

export interface ITerminalAddon {
activate(terminal: Terminal): void;
dispose(): void;
}

export class LigaturesAddon implements ITerminalAddon {
constructor() {}
private readonly _fallbackLigatures: string[];

private _terminal: Terminal | undefined;
private _characterJoinerId: number | undefined;

constructor(options?: Partial<ILigatureOptions>) {
this._fallbackLigatures = (options?.fallbackLigatures || [
'<--', '<---', '<<-', '<-', '->', '->>', '-->', '--->',
'<==', '<===', '<<=', '<=', '=>', '=>>', '==>', '===>', '>=', '>>=',
'<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '-------->',
'<~~', '<~', '~>', '~~>', '::', ':::', '==', '!=', '===', '!==',
':=', ':-', ':+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+:', '-:', '=:', ':>',
'++', '+++', '<!--', '<!---', '<***>'
]).sort((a, b) => b.length - a.length);
}

public activate(terminal: Terminal): void {
enableLigatures(terminal);
this._terminal = terminal;
this._characterJoinerId = enableLigatures(terminal, this._fallbackLigatures);
}

public dispose(): void {}
public dispose(): void {
if (this._characterJoinerId !== undefined) {
this._terminal?.deregisterCharacterJoiner(this._characterJoinerId);
this._characterJoinerId = undefined;
}
}
}

8 changes: 8 additions & 0 deletions addons/xterm-addon-ligatures/src/Types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/

export interface ILigatureOptions {
fallbackLigatures: string[];
}
3 changes: 0 additions & 3 deletions addons/xterm-addon-ligatures/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ describe('xterm-addon-ligatures', () => {
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.throws(() => term.joiner!(input));
});

it('returns nothing if the font is not present on the system', async () => {
Expand Down Expand Up @@ -176,7 +175,6 @@ describe('xterm-addon-ligatures', () => {
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.throws(() => term.joiner!(input));
});

it('ensures no empty errors are thrown', async () => {
Expand All @@ -185,7 +183,6 @@ describe('xterm-addon-ligatures', () => {
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.throws(() => term.joiner!(input), 'Failure while loading font');
(fontLigatures.loadFile as sinon.SinonStub).restore();
});
});
Expand Down
26 changes: 20 additions & 6 deletions addons/xterm-addon-ligatures/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ const CACHE_SIZE = 100000;
* start to render them.
* @param term Terminal instance from xterm.js
*/
export function enableLigatures(term: Terminal): void {
export function enableLigatures(term: Terminal, fallbackLigatures: string[] = []): number {
let currentFontName: string | undefined = undefined;
let font: Font | undefined = undefined;
let loadingState: LoadingState = LoadingState.UNLOADED;
let loadError: any | undefined = undefined;

term.registerCharacterJoiner((text: string): [number, number][] => {
return term.registerCharacterJoiner((text: string): [number, number][] => {
// If the font hasn't been loaded yet, load it and return an empty result
const termFont = term.options.fontFamily;
if (
Expand Down Expand Up @@ -63,6 +63,9 @@ export function enableLigatures(term: Terminal): void {
// sure our font is still vaild.
if (currentCallFontName === term.options.fontFamily) {
loadingState = LoadingState.FAILED;
if (term.options.logLevel === 'debug') {
console.debug(loadError, new Error('Failure while loading font'));
}
font = undefined;
loadError = e;
}
Expand All @@ -76,10 +79,21 @@ export function enableLigatures(term: Terminal): void {
range => [range[0], range[1]]
);
}
if (loadingState === LoadingState.FAILED) {
throw loadError || new Error('Failure while loading font');
}

return [];
return getFallbackRanges(text, fallbackLigatures);
});
}

function getFallbackRanges(text: string, fallbackLigatures: string[]): [number, number][] {
const ranges: [number, number][] = [];
for (let i = 0; i < text.length; i++) {
for (let j = 0; j < fallbackLigatures.length; j++) {
if (text.startsWith(fallbackLigatures[j], i)) {
ranges.push([i, i + fallbackLigatures[j].length]);
i += fallbackLigatures[j].length - 1;
break;
}
}
}
return ranges;
}
26 changes: 25 additions & 1 deletion addons/xterm-addon-ligatures/typings/xterm-addon-ligatures.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ declare module 'xterm-addon-ligatures' {
export class LigaturesAddon implements ITerminalAddon {
/**
* Creates a new ligatures addon.
*
* @param options Options for the ligatures addon.
*/
constructor();
constructor(options?: Partial<ILigatureOptions>);

/**
* Activates the addon
*
* @param terminal The terminal the addon is being loaded in.
*/
public activate(terminal: Terminal): void;
Expand All @@ -31,4 +34,25 @@ declare module 'xterm-addon-ligatures' {
*/
public dispose(): void;
}

/**
* Options for the ligatures addon.
*/
export interface ILigatureOptions {
/**
* Fallback ligatures to use when the font access API is either not supported by the browser or
* access is denied. The default set of ligatures is taken from Iosevka's default "calt"
* ligation set: https://typeof.net/Iosevka/
*
* ```
* <-- <--- <<- <- -> ->> --> --->
* <== <=== <<= <= => =>> ==> ===> >= >>=
* <-> <--> <---> <----> <=> <==> <===> <====> -------->
* <~~ <~ ~> ~~> :: ::: == != === !==
* := :- :+ <* <*> *> <| <|> |> +: -: =: :>
* ++ +++ <!-- <!--- <***>
* ```
*/
fallbackLigatures: string[]
}
}