Skip to content

Commit d1b01c5

Browse files
authored
Merge pull request #5276 from Tyriar/ligature_selection
Fix selection rendering on ligatures in both renderers
2 parents a61fff6 + c7a1e0a commit d1b01c5

File tree

2 files changed

+64
-27
lines changed

2 files changed

+64
-27
lines changed

addons/addon-webgl/src/WebglRenderer.ts

+31-12
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ 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];
382384
let chars: string;
@@ -405,6 +407,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
405407
row = y + terminal.buffer.ydisp;
406408
line = terminal.buffer.lines.get(row)!;
407409
this._model.lineLengths[y] = 0;
410+
skipJoinedCheckUntilX = 0;
408411
joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
409412
for (x = 0; x < terminal.cols; x++) {
410413
lastBg = this._cellColorResolver.result.bg;
@@ -416,25 +419,41 @@ export class WebglRenderer extends Disposable implements IRenderer {
416419

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

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

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

440459
chars = cell.getChars();

src/browser/renderer/dom/DomRendererRowFactory.ts

+33-15
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,44 @@ 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+
if (!isValidJoinRange) {
131+
skipJoinedCheckUntilX = range[1];
132+
} else {
133+
isJoined = true;
134+
135+
// We already know the exact start and end column of the joined range,
136+
// so we get the string and width representing it directly
137+
cell = new JoinedCellData(
138+
this._workCell,
139+
lineData.translateToString(true, range[0], range[1]),
140+
range[1] - range[0]
141+
);
118142

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;
143+
// Skip over the cells occupied by this range in the loop
144+
lastCharX = range[1] - 1;
129145

130-
// Recalculate width
131-
width = cell.getWidth();
146+
// Recalculate width
147+
width = cell.getWidth();
148+
}
132149
}
133150

134151
const isInSelection = this._isCellInSelection(x, row);
@@ -178,6 +195,7 @@ export class DomRendererRowFactory {
178195
&& !isCursorCell
179196
&& !isJoined
180197
&& !isDecorated
198+
&& isValidJoinRange
181199
) {
182200
// no span alterations, thus only account chars skipping all code below
183201
if (cell.isInvisible()) {
@@ -435,7 +453,7 @@ export class DomRendererRowFactory {
435453
}
436454

437455
// exclude conditions for cell merging - never merge these
438-
if (!isCursorCell && !isJoined && !isDecorated) {
456+
if (!isCursorCell && !isJoined && !isDecorated && isValidJoinRange) {
439457
cellAmount++;
440458
} else {
441459
charElement.textContent = text;

0 commit comments

Comments
 (0)