Skip to content

Commit 3c790ff

Browse files
authored
Merge branch 'master' into fixing-#5270
2 parents 763d2e7 + cc78831 commit 3c790ff

File tree

9 files changed

+94
-32
lines changed

9 files changed

+94
-32
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-webgl/src/WebglRenderer.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
377377
let line: IBufferLine;
378378
let joinedRanges: [number, number][];
379379
let isJoined: boolean;
380+
let skipJoinedCheckUntilX: number = 0;
381+
let isValidJoinRange: boolean = true;
380382
let lastCharX: number;
381383
let range: [number, number];
384+
let isCursorRow: boolean;
382385
let chars: string;
383386
let code: number;
384387
let width: number;
@@ -405,6 +408,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
405408
row = y + terminal.buffer.ydisp;
406409
line = terminal.buffer.lines.get(row)!;
407410
this._model.lineLengths[y] = 0;
411+
isCursorRow = cursorY === row;
412+
skipJoinedCheckUntilX = 0;
408413
joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
409414
for (x = 0; x < terminal.cols; x++) {
410415
lastBg = this._cellColorResolver.result.bg;
@@ -416,25 +421,43 @@ export class WebglRenderer extends Disposable implements IRenderer {
416421

417422
// If true, indicates that the current character(s) to draw were joined.
418423
isJoined = false;
424+
425+
// Indicates whether this cell is part of a joined range that should be ignored as it cannot
426+
// be rendered entirely, like the selection state differs across the range.
427+
isValidJoinRange = (x >= skipJoinedCheckUntilX);
428+
419429
lastCharX = x;
420430

421431
// Process any joined character ranges as needed. Because of how the
422432
// ranges are produced, we know that they are valid for the characters
423433
// and attributes of our input.
424-
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
425-
isJoined = true;
434+
if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
426435
range = joinedRanges.shift()!;
427436

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

440463
chars = cell.getChars();

demo/client.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ if (document.location.pathname === '/test') {
240240
document.getElementById('add-decoration').addEventListener('click', addDecoration);
241241
document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler);
242242
document.getElementById('decoration-stress-test').addEventListener('click', decorationStressTest);
243+
document.getElementById('ligatures-test').addEventListener('click', ligaturesTest);
243244
document.getElementById('weblinks-test').addEventListener('click', testWeblinks);
244245
document.getElementById('bce').addEventListener('click', coloredErase);
245246
addVtButtons();
@@ -1307,6 +1308,20 @@ function addVtButtons(): void {
13071308
document.querySelector('#vt-container').appendChild(vtFragment);
13081309
}
13091310

1311+
function ligaturesTest(): void {
1312+
term.write([
1313+
'',
1314+
'-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-',
1315+
'=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=',
1316+
'<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __',
1317+
'<~~ </ </> /> ~~> == != /= ~= <> === !== !=== =/= =!=',
1318+
'<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>',
1319+
'(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| <!-- <!---',
1320+
'==== ===== ====== ======= ======== =========',
1321+
'---- ----- ------ ------- -------- ---------'
1322+
].join('\r\n'));
1323+
}
1324+
13101325
function testWeblinks(): void {
13111326
const linkExamples = `
13121327
aaa http://example.com aaa http://example.com aaa

demo/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ <h3>Test</h3>
106106
<dd><button id="add-overview-ruler" title="Add an overview ruler to the terminal">Add Overview Ruler</button></dd>
107107
<dd><button id="decoration-stress-test" title="Toggle between adding and removing a decoration to each line">Stress Test</button></dd>
108108

109+
<dt>Ligatures Addon</dt>
110+
<dd><button id="ligatures-test" title="Write common ligatures sequences">Common ligatures</button></dd>
111+
109112
<dt>Weblinks Addon</dt>
110113
<dd><button id="weblinks-test" title="Various url conditions from demo data, hover&click to test">Test URLs</button></dd>
111114

src/browser/CoreBrowserTerminal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
400400
}
401401

402402
// If the terminal is already opened
403-
if (this.element?.ownerDocument.defaultView && this._coreBrowserService) {
403+
if (this.element?.ownerDocument.defaultView && this._coreBrowserService && this.element?.isConnected) {
404404
// Adjust the window if needed
405405
if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) {
406406
this._coreBrowserService.window = this.element.ownerDocument.defaultView;

src/browser/renderer/dom/DomRendererRowFactory.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,15 @@ export class DomRendererRowFactory {
8484
let charElement: HTMLSpanElement | undefined;
8585
let cellAmount = 0;
8686
let text = '';
87+
let i = 0;
8788
let oldBg = 0;
8889
let oldFg = 0;
8990
let oldExt = 0;
9091
let oldLinkHover: number | boolean = false;
9192
let oldSpacing = 0;
9293
let oldIsInSelection: boolean = false;
9394
let spacing = 0;
95+
let skipJoinedCheckUntilX = 0;
9496
const classes: string[] = [];
9597

9698
const hasHover = linkStart !== -1 && linkEnd !== -1;
@@ -106,29 +108,46 @@ export class DomRendererRowFactory {
106108

107109
// If true, indicates that the current character(s) to draw were joined.
108110
let isJoined = false;
111+
112+
// Indicates whether this cell is part of a joined range that should be ignored as it cannot
113+
// be rendered entirely, like the selection state differs across the range.
114+
let isValidJoinRange = (x >= skipJoinedCheckUntilX);
115+
109116
let lastCharX = x;
110117

111118
// Process any joined character ranges as needed. Because of how the
112119
// ranges are produced, we know that they are valid for the characters
113120
// and attributes of our input.
114121
let cell = this._workCell;
115-
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
116-
isJoined = true;
122+
if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
117123
const range = joinedRanges.shift()!;
124+
// If the ligature's selection state is not consistent, don't join it. This helps the
125+
// selection render correctly regardless whether they should be joined.
126+
const firstSelectionState = this._isCellInSelection(range[0], row);
127+
for (i = range[0] + 1; i < range[1]; i++) {
128+
isValidJoinRange &&= (firstSelectionState === this._isCellInSelection(i, row));
129+
}
130+
// Similarly, if the cursor is in the ligature, don't join it.
131+
isValidJoinRange &&= !isCursorRow || cursorX < range[0] || cursorX >= range[1];
132+
if (!isValidJoinRange) {
133+
skipJoinedCheckUntilX = range[1];
134+
} else {
135+
isJoined = true;
136+
137+
// We already know the exact start and end column of the joined range,
138+
// so we get the string and width representing it directly
139+
cell = new JoinedCellData(
140+
this._workCell,
141+
lineData.translateToString(true, range[0], range[1]),
142+
range[1] - range[0]
143+
);
118144

119-
// We already know the exact start and end column of the joined range,
120-
// so we get the string and width representing it directly
121-
cell = new JoinedCellData(
122-
this._workCell,
123-
lineData.translateToString(true, range[0], range[1]),
124-
range[1] - range[0]
125-
);
126-
127-
// Skip over the cells occupied by this range in the loop
128-
lastCharX = range[1] - 1;
145+
// Skip over the cells occupied by this range in the loop
146+
lastCharX = range[1] - 1;
129147

130-
// Recalculate width
131-
width = cell.getWidth();
148+
// Recalculate width
149+
width = cell.getWidth();
150+
}
132151
}
133152

134153
const isInSelection = this._isCellInSelection(x, row);
@@ -178,6 +197,7 @@ export class DomRendererRowFactory {
178197
&& !isCursorCell
179198
&& !isJoined
180199
&& !isDecorated
200+
&& isValidJoinRange
181201
) {
182202
// no span alterations, thus only account chars skipping all code below
183203
if (cell.isInvisible()) {
@@ -435,7 +455,7 @@ export class DomRendererRowFactory {
435455
}
436456

437457
// exclude conditions for cell merging - never merge these
438-
if (!isCursorCell && !isJoined && !isDecorated) {
458+
if (!isCursorCell && !isJoined && !isDecorated && isValidJoinRange) {
439459
cellAmount++;
440460
} else {
441461
charElement.textContent = text;

src/common/services/InstantiationService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class InstantiationService implements IInstantiationService {
6767
for (const dependency of serviceDependencies) {
6868
const service = this._services.get(dependency.id);
6969
if (!service) {
70-
throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`);
70+
throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id._id}.`);
7171
}
7272
serviceArgs.push(service);
7373
}

src/common/services/ServiceRegistry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function createDecorator<T>(id: string): IServiceIdentifier<T> {
3333
storeServiceDependency(decorator, target, index);
3434
};
3535

36-
decorator.toString = () => id;
36+
decorator._id = id;
3737

3838
serviceRegistry.set(id, decorator);
3939
return decorator;

src/common/services/Services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export interface ICharsetService {
124124
export interface IServiceIdentifier<T> {
125125
(...args: any[]): void;
126126
type: T;
127+
_id: string;
127128
}
128129

129130
export interface IBrandedService {

0 commit comments

Comments
 (0)