fix(terminal): preserve dim mid-line composition tails

This commit is contained in:
Neil
2026-08-29 04:18:22 -07:00
parent 322c636f5f
commit 84259ea589
4 changed files with 74 additions and 61 deletions
File diff suppressed because one or more lines are too long
@@ -88,7 +88,7 @@ index 497afcf535f3eaca00889525a77e15eb633ccd96..96d499b34605f860608382114c3fbdc0
export interface IBrowser {
diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts
index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693de600bd5d 100644
index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..946a95d3b87d68f0f0324b5d74346d3f3ff3710c 100644
--- a/src/browser/input/CompositionHelper.ts
+++ b/src/browser/input/CompositionHelper.ts
@@ -3,8 +3,9 @@
@@ -108,7 +108,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+interface IRowRemainder {
+ text: string;
+ dimOnly: boolean;
+ maskAsGhost: boolean;
+}
+
+interface IPendingComposition {
@@ -216,7 +216,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+ private _compositionRemainder?: HTMLElement;
+
+ /** Whether the rendered tail is application ghost text that must stay masked. */
+ private _compositionRemainderDimOnly: boolean;
+ private _compositionRemainderMasked: boolean;
+
+ /** The insertion caret painted above the renderer cursor the composition view covers. */
+ private _compositionCaret?: HTMLElement;
@@ -265,7 +265,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+ this._compositionTransactionId = 0;
+ this._compositionTimers = new Set();
+ this._imeKeydownAwaitingCommit = false;
+ this._compositionRemainderDimOnly = false;
+ this._compositionRemainderMasked = false;
}
/**
@@ -970,7 +970,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+ // its trailing glyph cells to the left of where the grid has them.
+ remainder.style.whiteSpace = 'pre';
+ // Preserve the ghost text's width so the opaque view masks it without repeating it.
+ remainder.style.visibility = rowRemainder.dimOnly ? 'hidden' : '';
+ remainder.style.visibility = rowRemainder.maskAsGhost ? 'hidden' : '';
+ remainder.textContent = rowRemainder.text;
+ children.push(remainder);
+ }
@@ -978,19 +978,19 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+ this._compositionPreedit = preedit;
+ this._compositionCaret = caret;
+ this._compositionRemainder = remainder;
+ this._compositionRemainderDimOnly = rowRemainder.dimOnly;
+ this._compositionRemainderMasked = rowRemainder.maskAsGhost;
+ this._styleCompositionCaret();
+ }
+
+ /** The row tail and whether every visible cell in it is dim application ghost text. */
+ /** The row tail and whether its full-row dim geometry identifies application ghost text. */
+ private _getRowRemainder(): IRowRemainder {
+ const buffer = this._bufferService.buffer;
+ if (!buffer.isCursorInViewport) {
+ return { text: '', dimOnly: false };
+ return { text: '', maskAsGhost: false };
+ }
+ const line = buffer.lines.get(buffer.ybase + buffer.y);
+ if (!line) {
+ return { text: '', dimOnly: false };
+ return { text: '', maskAsGhost: false };
+ }
+ const start = Math.min(buffer.x, this._bufferService.cols - 1);
+ // The explicit end column keeps this off the line string cache, whose self-renewing
@@ -998,7 +998,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+ const end = line.getTrimmedLength();
+ const text = line.translateToString(false, start, end);
+ if (!text) {
+ return { text, dimOnly: false };
+ return { text, maskAsGhost: false };
+ }
+ let hasVisibleCell = false;
+ for (let x = start; x < end; x++) {
@@ -1007,10 +1007,10 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+ }
+ hasVisibleCell = true;
+ if (!(line.getBg(x) & BgFlags.DIM)) {
+ return { text, dimOnly: false };
+ return { text, maskAsGhost: false };
+ }
+ }
+ return { text, dimOnly: hasVisibleCell };
+ return { text, maskAsGhost: hasVisibleCell && start === 0 };
+ }
+
+ private _styleCompositionCaret(): void {
@@ -1034,7 +1034,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+ this._compositionView.textContent = '';
+ this._compositionPreedit = undefined;
+ this._compositionRemainder = undefined;
+ this._compositionRemainderDimOnly = false;
+ this._compositionRemainderMasked = false;
+ this._compositionCaret = undefined;
+ this._compositionViewData = '';
+ this._compositionView.style.textAlign = '';
@@ -1081,7 +1081,7 @@ index c9ec396ab66cb966d49aa63bed09cdf9cd6c4246..d9883af6ebb1745bfcdd83c4b94e693d
+ const rowRemainder = this._getRowRemainder();
+ if (this._compositionViewData && (
+ rowRemainder.text !== (this._compositionRemainder?.textContent ?? '') ||
+ rowRemainder.dimOnly !== this._compositionRemainderDimOnly
+ rowRemainder.maskAsGhost !== this._compositionRemainderMasked
+ )) {
+ this._renderCompositionView(this._compositionViewData, rowRemainder);
+ }
+24 -24
View File
@@ -21,7 +21,7 @@ patchedDependencies:
hash: 118f649459ba56a51627d46603e2d5d81d837ac9c4dc393f5cc20263d0ac1b91
path: config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch
'@xterm/xterm@6.1.0-beta.287':
hash: 55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312
hash: 8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68
path: config/patches/@xterm__xterm@6.1.0-beta.287.patch
lint-staged@16.4.0:
hash: 7333b3837f80a7fbd045964db6d76ba4fc118e49134bdbabb00585b6b7b60673
@@ -51,7 +51,7 @@ importers:
version: 2.5.6
'@xterm/addon-serialize':
specifier: 0.15.0-beta.287
version: 0.15.0-beta.287(patch_hash=af3b156143ed2ee9903b753145b63dd4a2ee72cadd7ef333ab0f4d5d3ebfc32c)(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))
version: 0.15.0-beta.287(patch_hash=af3b156143ed2ee9903b753145b63dd4a2ee72cadd7ef333ab0f4d5d3ebfc32c)(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))
'@xterm/headless':
specifier: 6.1.0-beta.287
version: 6.1.0-beta.287
@@ -220,25 +220,25 @@ importers:
version: 5.2.0(rolldown-vite@7.3.1(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@25.9.5)(jiti@2.7.0)(yaml@2.8.4))
'@xterm/addon-fit':
specifier: 0.12.0-beta.287
version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))
version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))
'@xterm/addon-ligatures':
specifier: 0.11.0-beta.287
version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))
version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))
'@xterm/addon-search':
specifier: 0.17.0-beta.287
version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))
version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))
'@xterm/addon-unicode11':
specifier: 0.10.0-beta.287
version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))
version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))
'@xterm/addon-web-links':
specifier: 0.13.0-beta.287
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))
'@xterm/addon-webgl':
specifier: 0.20.0-beta.286
version: 0.20.0-beta.286(patch_hash=118f649459ba56a51627d46603e2d5d81d837ac9c4dc393f5cc20263d0ac1b91)(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))
version: 0.20.0-beta.286(patch_hash=118f649459ba56a51627d46603e2d5d81d837ac9c4dc393f5cc20263d0ac1b91)(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))
'@xterm/xterm':
specifier: 6.1.0-beta.287
version: 6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)
version: 6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -9624,39 +9624,39 @@ snapshots:
'@xmldom/xmldom@0.8.13': {}
'@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))':
'@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))':
dependencies:
'@xterm/xterm': 6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)
'@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))':
'@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))':
dependencies:
'@xterm/xterm': 6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)
lru-cache: 11.5.1
opentype.js: 2.0.0
'@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))':
'@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))':
dependencies:
'@xterm/xterm': 6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)
'@xterm/addon-serialize@0.15.0-beta.287(patch_hash=af3b156143ed2ee9903b753145b63dd4a2ee72cadd7ef333ab0f4d5d3ebfc32c)(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))':
'@xterm/addon-serialize@0.15.0-beta.287(patch_hash=af3b156143ed2ee9903b753145b63dd4a2ee72cadd7ef333ab0f4d5d3ebfc32c)(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))':
dependencies:
'@xterm/xterm': 6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)
'@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))':
'@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))':
dependencies:
'@xterm/xterm': 6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)
'@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))':
'@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))':
dependencies:
'@xterm/xterm': 6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=118f649459ba56a51627d46603e2d5d81d837ac9c4dc393f5cc20263d0ac1b91)(@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312))':
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=118f649459ba56a51627d46603e2d5d81d837ac9c4dc393f5cc20263d0ac1b91)(@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68))':
dependencies:
'@xterm/xterm': 6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)
'@xterm/xterm': 6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)
'@xterm/headless@6.1.0-beta.287': {}
'@xterm/xterm@6.1.0-beta.287(patch_hash=55b66527827006dc5dbe9bc486a25afd84292f277b0f47171409619ea618b312)': {}
'@xterm/xterm@6.1.0-beta.287(patch_hash=8ed744c9ce7594ef00062494dc54c59abaf4c71bb892a268b4e109890b363c68)': {}
abbrev@4.0.0: {}
@@ -299,6 +299,19 @@ describe('mid-line composition renders the covered row tail after the preedit',
expect(remainder!.style.visibility).toBe('hidden')
})
it('keeps a wholly dim mid-line tail visible', async () => {
const rig = openTerminal()
const tail = 'status'
await rig.write(`> \x1b[2m${tail}\x1b[22m\x1b[${tail.length}D`)
expect(rig.terminal.buffer.active.cursorX).toBe(2)
rig.compose('아')
const { remainder } = viewParts(rig.compositionView)
expect(remainder!.textContent).toBe(tail)
expect(remainder!.style.visibility).toBe('')
})
it('keeps a mixed dim and committed tail visible', async () => {
const rig = openTerminal()
await rig.write('\x1b[2mghost\x1b[22m!\x1b[6D')