Skip to content

Commit db71de2

Browse files
authored
Merge branch 'master' into dependabot/npm_and_yarn/cross-spawn-7.0.6
2 parents 8d23cbc + 0151805 commit db71de2

33 files changed

+376
-166
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,9 @@ jobs:
167167
strategy:
168168
matrix:
169169
node-version: [18] # just one as integration tests are about testing in browser
170-
runs-on: [ubuntu] # macos is flaky
170+
runs-on: [ubuntu-22.04] # macos is flaky
171171
browser: [chromium, firefox, webkit]
172-
runs-on: ${{ matrix.runs-on }}-latest
172+
runs-on: ${{ matrix.runs-on }}
173173
steps:
174174
- uses: actions/checkout@v3
175175
- name: Use Node.js ${{ matrix.node-version }}.x

addons/addon-ligatures/src/LigaturesAddon.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,22 @@ export interface ITerminalAddon {
1515

1616
export class LigaturesAddon implements ITerminalAddon , ILigaturesApi {
1717
private readonly _fallbackLigatures: string[];
18+
private readonly _fontFeatureSettings?: string;
1819

1920
private _terminal: Terminal | undefined;
2021
private _characterJoinerId: number | undefined;
2122

2223
constructor(options?: Partial<ILigatureOptions>) {
24+
// Source: calt set from https://github.com/be5invis/Iosevka?tab=readme-ov-file#ligations
2325
this._fallbackLigatures = (options?.fallbackLigatures || [
2426
'<--', '<---', '<<-', '<-', '->', '->>', '-->', '--->',
2527
'<==', '<===', '<<=', '<=', '=>', '=>>', '==>', '===>', '>=', '>>=',
26-
'<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '-------->',
27-
'<~~', '<~', '~>', '~~>', '::', ':::', '==', '!=', '===', '!==',
28-
':=', ':-', ':+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+:', '-:', '=:', ':>',
29-
'++', '+++', '<!--', '<!---', '<***>'
28+
'<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '::', ':::',
29+
'<~~', '</', '</>', '/>', '~~>', '==', '!=', '/=', '~=', '<>', '===', '!==', '!===',
30+
'<:', ':=', '*=', '*+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+*', '=*', '=:', ':>',
31+
'/*', '*/', '+++', '<!--', '<!---'
3032
]).sort((a, b) => b.length - a.length);
33+
this._fontFeatureSettings = options?.fontFeatureSettings;
3134
}
3235

3336
public activate(terminal: Terminal): void {
@@ -36,7 +39,7 @@ export class LigaturesAddon implements ITerminalAddon , ILigaturesApi {
3639
}
3740
this._terminal = terminal;
3841
this._characterJoinerId = enableLigatures(terminal, this._fallbackLigatures);
39-
terminal.element.style.fontFeatureSettings = '"liga" on, "calt" on';
42+
terminal.element.style.fontFeatureSettings = this._fontFeatureSettings ?? '"calt" on';
4043
}
4144

4245
public dispose(): void {

addons/addon-ligatures/src/Types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55

66
export interface ILigatureOptions {
77
fallbackLigatures: string[];
8+
fontFeatureSettings: string;
89
}

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

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ declare module '@xterm/addon-ligatures' {
2323
constructor(options?: Partial<ILigatureOptions>);
2424

2525
/**
26-
* Activates the addon
26+
* Activates the addon. Note that if webgl is also being used, that addon
27+
* should be reactivated after ligatures is activated in order to apply
28+
* {@link ILigatureOptions.fontFeatureSettings} to the texture atlas.
29+
*
2730
*
2831
* @param terminal The terminal the addon is being loaded in.
2932
*/
@@ -40,19 +43,28 @@ declare module '@xterm/addon-ligatures' {
4043
*/
4144
export interface ILigatureOptions {
4245
/**
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+
* Fallback ligatures to use when the font access API is either not
47+
* supported by the browser or access is denied. The default set of
48+
* ligatures is taken from Iosevka's default "calt" ligation set:
49+
* https://typeof.net/Iosevka/
4650
*
4751
* ```
4852
* <-- <--- <<- <- -> ->> --> --->
4953
* <== <=== <<= <= => =>> ==> ===> >= >>=
50-
* <-> <--> <---> <----> <=> <==> <===> <====> -------->
51-
* <~~ <~ ~> ~~> :: ::: == != === !==
52-
* := :- :+ <* <*> *> <| <|> |> +: -: =: :>
53-
* ++ +++ <!-- <!--- <***>
54-
* ```
54+
* <-> <--> <---> <----> <=> <==> <===> <====> :: :::
55+
* <~~ </ </> /> ~~> == != /= ~= <> === !== !===
56+
* <: := *= *+ <* <*> *> <| <|> |> +* =* =: :>
57+
* /* <close block comment> +++ <!-- <!---
5558
*/
5659
fallbackLigatures: string[]
60+
61+
/**
62+
* The CSS `font-feature-settings` value to use for enabling ligatures. This
63+
* also supports font variants for example with a value like
64+
* `"calt" on, "ss03"`.
65+
*
66+
* The default value is `"calt" on`.
67+
*/
68+
fontFeatureSettings: string;
5769
}
5870
}

addons/addon-search/src/SearchAddon.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,12 +499,16 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
499499
const [stringLine, offsets] = cache;
500500

501501
const offset = this._bufferColsToStringOffset(row, col);
502-
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
503-
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
502+
let searchTerm = term;
503+
let searchStringLine = stringLine;
504+
if (!searchOptions.regex) {
505+
searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
506+
searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
507+
}
504508

505509
let resultIndex = -1;
506510
if (searchOptions.regex) {
507-
const searchRegex = RegExp(searchTerm, 'g');
511+
const searchRegex = RegExp(searchTerm, searchOptions.caseSensitive ? 'g' : 'gi');
508512
let foundTerm: RegExpExecArray | null;
509513
if (isReverseSearch) {
510514
// This loop will get the resultIndex of the _last_ regex match in the range 0..offset

addons/addon-serialize/src/SerializeAddon.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ describe('SerializeAddon', () => {
222222

223223
it('empty terminal with default options', async () => {
224224
const output = serializeAddon.serializeAsHTML();
225-
assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
225+
assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: monospace; font-size: 15px;/g) || []).length, 1, output);
226226
});
227227

228228
it('empty terminal with custom options', async () => {
@@ -242,7 +242,7 @@ describe('SerializeAddon', () => {
242242
const output = serializeAddon.serializeAsHTML({
243243
includeGlobalBackground: true
244244
});
245-
assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
245+
assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: monospace; font-size: 15px;/g) || []).length, 1, output);
246246
});
247247

248248
it('cells with custom color styling', async () => {

addons/addon-webgl/src/GlyphRenderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,9 @@ export class GlyphRenderer extends Disposable {
238238

239239
// Get the glyph
240240
if (chars && chars.length > 1) {
241-
$glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, false);
241+
$glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, false, this._terminal.element);
242242
} else {
243-
$glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, false);
243+
$glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, false, this._terminal.element);
244244
}
245245

246246
$leftCellPadding = Math.floor((this._dimensions.device.cell.width - this._dimensions.device.char.width) / 2);

addons/addon-webgl/src/WebglRenderer.ts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
3333
private _charAtlasDisposable = this._register(new MutableDisposable());
3434
private _charAtlas: ITextureAtlas | undefined;
3535
private _devicePixelRatio: number;
36+
private _deviceMaxTextureSize: number;
3637
private _observerDisposable = this._register(new MutableDisposable());
3738

3839
private _model: RenderModel = new RenderModel();
@@ -102,6 +103,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
102103
throw new Error('WebGL2 not supported ' + this._gl);
103104
}
104105

106+
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
107+
105108
this._register(addDisposableListener(this._canvas, 'webglcontextlost', (e) => {
106109
console.log('webglcontextlost event received');
107110
// Prevent the default behavior in order to enable WebGL context restoration.
@@ -272,7 +275,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
272275
this.dimensions.device.cell.height,
273276
this.dimensions.device.char.width,
274277
this.dimensions.device.char.height,
275-
this._coreBrowserService.dpr
278+
this._coreBrowserService.dpr,
279+
this._deviceMaxTextureSize
276280
);
277281
if (this._charAtlas !== atlas) {
278282
this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
@@ -354,7 +358,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
354358
}
355359

356360
private _updateCursorBlink(): void {
357-
if (this._terminal.options.cursorBlink) {
361+
if (this._coreService.decPrivateModes.cursorBlink ?? this._terminal.options.cursorBlink) {
358362
this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => {
359363
this._requestRedrawCursor();
360364
}, this._coreBrowserService);
@@ -377,8 +381,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
377381
let line: IBufferLine;
378382
let joinedRanges: [number, number][];
379383
let isJoined: boolean;
384+
let skipJoinedCheckUntilX: number = 0;
385+
let isValidJoinRange: boolean = true;
380386
let lastCharX: number;
381387
let range: [number, number];
388+
let isCursorRow: boolean;
382389
let chars: string;
383390
let code: number;
384391
let width: number;
@@ -387,6 +394,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
387394
let j: number;
388395
start = clamp(start, terminal.rows - 1, 0);
389396
end = clamp(end, terminal.rows - 1, 0);
397+
const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? terminal.options.cursorStyle ?? 'block';
390398

391399
const cursorY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY;
392400
const viewportRelativeCursorY = cursorY - terminal.buffer.ydisp;
@@ -404,6 +412,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
404412
row = y + terminal.buffer.ydisp;
405413
line = terminal.buffer.lines.get(row)!;
406414
this._model.lineLengths[y] = 0;
415+
isCursorRow = cursorY === row;
416+
skipJoinedCheckUntilX = 0;
407417
joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
408418
for (x = 0; x < terminal.cols; x++) {
409419
lastBg = this._cellColorResolver.result.bg;
@@ -415,25 +425,43 @@ export class WebglRenderer extends Disposable implements IRenderer {
415425

416426
// If true, indicates that the current character(s) to draw were joined.
417427
isJoined = false;
428+
429+
// Indicates whether this cell is part of a joined range that should be ignored as it cannot
430+
// be rendered entirely, like the selection state differs across the range.
431+
isValidJoinRange = (x >= skipJoinedCheckUntilX);
432+
418433
lastCharX = x;
419434

420435
// Process any joined character ranges as needed. Because of how the
421436
// ranges are produced, we know that they are valid for the characters
422437
// and attributes of our input.
423-
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
424-
isJoined = true;
438+
if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
425439
range = joinedRanges.shift()!;
426440

427-
// We already know the exact start and end column of the joined range,
428-
// so we get the string and width representing it directly.
429-
cell = new JoinedCellData(
430-
cell,
431-
line!.translateToString(true, range[0], range[1]),
432-
range[1] - range[0]
433-
);
434-
435-
// Skip over the cells occupied by this range in the loop
436-
lastCharX = range[1] - 1;
441+
// If the ligature's selection state is not consistent, don't join it. This helps the
442+
// selection render correctly regardless whether they should be joined.
443+
const firstSelectionState = this._model.selection.isCellSelected(this._terminal, range[0], row);
444+
for (i = range[0] + 1; i < range[1]; i++) {
445+
isValidJoinRange &&= (firstSelectionState === this._model.selection.isCellSelected(this._terminal, i, row));
446+
}
447+
// Similarly, if the cursor is in the ligature, don't join it.
448+
isValidJoinRange &&= !isCursorRow || cursorX < range[0] || cursorX >= range[1];
449+
if (!isValidJoinRange) {
450+
skipJoinedCheckUntilX = range[1];
451+
} else {
452+
isJoined = true;
453+
454+
// We already know the exact start and end column of the joined range,
455+
// so we get the string and width representing it directly.
456+
cell = new JoinedCellData(
457+
cell,
458+
line!.translateToString(true, range[0], range[1]),
459+
range[1] - range[0]
460+
);
461+
462+
// Skip over the cells occupied by this range in the loop
463+
lastCharX = range[1] - 1;
464+
}
437465
}
438466

439467
chars = cell.getChars();
@@ -450,18 +478,18 @@ export class WebglRenderer extends Disposable implements IRenderer {
450478
x: cursorX,
451479
y: viewportRelativeCursorY,
452480
width: cell.getWidth(),
453-
style: this._coreBrowserService.isFocused ?
454-
(terminal.options.cursorStyle || 'block') : terminal.options.cursorInactiveStyle,
481+
style: this._coreBrowserService.isFocused ? cursorStyle : terminal.options.cursorInactiveStyle,
455482
cursorWidth: terminal.options.cursorWidth,
456483
dpr: this._devicePixelRatio
457484
};
458485
lastCursorX = cursorX + cell.getWidth() - 1;
459486
}
460487
if (x >= cursorX && x <= lastCursorX &&
461488
((this._coreBrowserService.isFocused &&
462-
(terminal.options.cursorStyle || 'block') === 'block') ||
489+
cursorStyle === 'block') ||
463490
(this._coreBrowserService.isFocused === false &&
464-
terminal.options.cursorInactiveStyle === 'block'))) {
491+
terminal.options.cursorInactiveStyle === 'block'))
492+
) {
465493
this._cellColorResolver.result.fg =
466494
Attributes.CM_RGB | (this._themeService.colors.cursorAccent.rgba >> 8 & Attributes.RGB_MASK);
467495
this._cellColorResolver.result.bg =

addons/addon-webgl/src/renderLayer/BaseRenderLayer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
9494
if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) {
9595
return;
9696
}
97-
this._charAtlas = acquireTextureAtlas(terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr);
97+
98+
this._charAtlas = acquireTextureAtlas(terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr, 2048);
9899
this._charAtlas.warmUp();
99100
}
100101

0 commit comments

Comments
 (0)