Skip to content

Commit a2e07de

Browse files
authored
Merge pull request #3963 from Tyriar/fallback_ligatures
Support fallback ligatures and correctly dispose of char joiner
2 parents b0c29ca + 7f58c48 commit a2e07de

File tree

6 files changed

+83
-19
lines changed

6 files changed

+83
-19
lines changed

addons/xterm-addon-ligatures/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,16 @@ This package locates the font file on disk for the font currently in use by the
3333

3434
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]).
3535

36+
### Fallback Ligatures
37+
38+
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`.
39+
3640
### Fonts
3741

3842
This package makes use of the following fonts for testing:
3943

40-
* [Fira Code][Fira Code] - [Licensed under the OFL][Fira Code License] by Nikita
41-
Prokopov, Mozilla Foundation with reserved names Fira Code, Fira Mono, and
42-
Fira Sans
43-
* [Iosevka] - [Licensed under the OFL][Iosevka License] by Belleve Invis with
44-
reserved name Iosevka
44+
* [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
45+
* [Iosevka] - [Licensed under the OFL][Iosevka License] by Belleve Invis with reserved name Iosevka
4546

4647
[xterm.js]: https://github.com/xtermjs/xterm.js
4748
[Electron]: https://electronjs.org/

addons/xterm-addon-ligatures/src/LigaturesAddon.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,39 @@
55

66
import { Terminal } from 'xterm';
77
import { enableLigatures } from '.';
8+
import { ILigatureOptions } from './Types';
89

910
export interface ITerminalAddon {
1011
activate(terminal: Terminal): void;
1112
dispose(): void;
1213
}
1314

1415
export class LigaturesAddon implements ITerminalAddon {
15-
constructor() {}
16+
private readonly _fallbackLigatures: string[];
17+
18+
private _terminal: Terminal | undefined;
19+
private _characterJoinerId: number | undefined;
20+
21+
constructor(options?: Partial<ILigatureOptions>) {
22+
this._fallbackLigatures = (options?.fallbackLigatures || [
23+
'<--', '<---', '<<-', '<-', '->', '->>', '-->', '--->',
24+
'<==', '<===', '<<=', '<=', '=>', '=>>', '==>', '===>', '>=', '>>=',
25+
'<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '-------->',
26+
'<~~', '<~', '~>', '~~>', '::', ':::', '==', '!=', '===', '!==',
27+
':=', ':-', ':+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+:', '-:', '=:', ':>',
28+
'++', '+++', '<!--', '<!---', '<***>'
29+
]).sort((a, b) => b.length - a.length);
30+
}
1631

1732
public activate(terminal: Terminal): void {
18-
enableLigatures(terminal);
33+
this._terminal = terminal;
34+
this._characterJoinerId = enableLigatures(terminal, this._fallbackLigatures);
1935
}
2036

21-
public dispose(): void {}
37+
public dispose(): void {
38+
if (this._characterJoinerId !== undefined) {
39+
this._terminal?.deregisterCharacterJoiner(this._characterJoinerId);
40+
this._characterJoinerId = undefined;
41+
}
42+
}
2243
}
23-
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
3+
* @license MIT
4+
*/
5+
6+
export interface ILigatureOptions {
7+
fallbackLigatures: string[];
8+
}

addons/xterm-addon-ligatures/src/index.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ describe('xterm-addon-ligatures', () => {
144144
assert.deepEqual(term.joiner!(input), []);
145145
await delay(500);
146146
assert.isTrue(onRefresh.notCalled);
147-
assert.throws(() => term.joiner!(input));
148147
});
149148

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

182180
it('ensures no empty errors are thrown', async () => {
@@ -185,7 +183,6 @@ describe('xterm-addon-ligatures', () => {
185183
assert.deepEqual(term.joiner!(input), []);
186184
await delay(500);
187185
assert.isTrue(onRefresh.notCalled);
188-
assert.throws(() => term.joiner!(input), 'Failure while loading font');
189186
(fontLigatures.loadFile as sinon.SinonStub).restore();
190187
});
191188
});

addons/xterm-addon-ligatures/src/index.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ const CACHE_SIZE = 100000;
2626
* start to render them.
2727
* @param term Terminal instance from xterm.js
2828
*/
29-
export function enableLigatures(term: Terminal): void {
29+
export function enableLigatures(term: Terminal, fallbackLigatures: string[] = []): number {
3030
let currentFontName: string | undefined = undefined;
3131
let font: Font | undefined = undefined;
3232
let loadingState: LoadingState = LoadingState.UNLOADED;
3333
let loadError: any | undefined = undefined;
3434

35-
term.registerCharacterJoiner((text: string): [number, number][] => {
35+
return term.registerCharacterJoiner((text: string): [number, number][] => {
3636
// If the font hasn't been loaded yet, load it and return an empty result
3737
const termFont = term.options.fontFamily;
3838
if (
@@ -63,6 +63,9 @@ export function enableLigatures(term: Terminal): void {
6363
// sure our font is still vaild.
6464
if (currentCallFontName === term.options.fontFamily) {
6565
loadingState = LoadingState.FAILED;
66+
if (term.options.logLevel === 'debug') {
67+
console.debug(loadError, new Error('Failure while loading font'));
68+
}
6669
font = undefined;
6770
loadError = e;
6871
}
@@ -76,10 +79,21 @@ export function enableLigatures(term: Terminal): void {
7679
range => [range[0], range[1]]
7780
);
7881
}
79-
if (loadingState === LoadingState.FAILED) {
80-
throw loadError || new Error('Failure while loading font');
81-
}
8282

83-
return [];
83+
return getFallbackRanges(text, fallbackLigatures);
8484
});
8585
}
86+
87+
function getFallbackRanges(text: string, fallbackLigatures: string[]): [number, number][] {
88+
const ranges: [number, number][] = [];
89+
for (let i = 0; i < text.length; i++) {
90+
for (let j = 0; j < fallbackLigatures.length; j++) {
91+
if (text.startsWith(fallbackLigatures[j], i)) {
92+
ranges.push([i, i + fallbackLigatures[j].length]);
93+
i += fallbackLigatures[j].length - 1;
94+
break;
95+
}
96+
}
97+
}
98+
return ranges;
99+
}

addons/xterm-addon-ligatures/typings/xterm-addon-ligatures.d.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ declare module 'xterm-addon-ligatures' {
1717
export class LigaturesAddon implements ITerminalAddon {
1818
/**
1919
* Creates a new ligatures addon.
20+
*
21+
* @param options Options for the ligatures addon.
2022
*/
21-
constructor();
23+
constructor(options?: Partial<ILigatureOptions>);
2224

2325
/**
2426
* Activates the addon
27+
*
2528
* @param terminal The terminal the addon is being loaded in.
2629
*/
2730
public activate(terminal: Terminal): void;
@@ -31,4 +34,25 @@ declare module 'xterm-addon-ligatures' {
3134
*/
3235
public dispose(): void;
3336
}
37+
38+
/**
39+
* Options for the ligatures addon.
40+
*/
41+
export interface ILigatureOptions {
42+
/**
43+
* Fallback ligatures to use when the font access API is either not supported by the browser or
44+
* access is denied. The default set of ligatures is taken from Iosevka's default "calt"
45+
* ligation set: https://typeof.net/Iosevka/
46+
*
47+
* ```
48+
* <-- <--- <<- <- -> ->> --> --->
49+
* <== <=== <<= <= => =>> ==> ===> >= >>=
50+
* <-> <--> <---> <----> <=> <==> <===> <====> -------->
51+
* <~~ <~ ~> ~~> :: ::: == != === !==
52+
* := :- :+ <* <*> *> <| <|> |> +: -: =: :>
53+
* ++ +++ <!-- <!--- <***>
54+
* ```
55+
*/
56+
fallbackLigatures: string[]
57+
}
3458
}

0 commit comments

Comments
 (0)