Address edge-case with kitty protocol in wezterm
packages/tui/src/stdin-buffer.ts — in extractCompleteSequences, after finding \x1b\x1b as "complete" (legacy meta-key), check if the next character in the buffer would start a new escape sequence ([, ], O, P, _). If so, emit only the first \x1b and let the second ESC begin a new parse iteration. packages/tui/test/stdin-buffer.test.ts — three new cases in the Kitty section: - \x1b\x1b[27;129:3u (num_lock) splits into ["\x1b", "\x1b[27;129:3u"] - \x1b\x1b[27;1:3u (no num_lock) splits into ["\x1b", "\x1b[27;1:3u"] - \x1b\x1b alone (no following CSI) still emits as ["\x1b\x1b"] — preserves ctrl+alt+[
This commit is contained in:
@@ -205,6 +205,29 @@ function extractCompleteSequences(buffer: string): { sequences: string[]; remain
|
||||
const status = isCompleteSequence(candidate);
|
||||
|
||||
if (status === "complete") {
|
||||
// WezTerm with enable_kitty_keyboard sends the Escape key press as a
|
||||
// raw '\x1b' byte (simple text path in encode_kitty, ignoring
|
||||
// DISAMBIGUATE_ESCAPE_CODES) and the release as a full Kitty CSI-u
|
||||
// sequence. These arrive concatenated as '\x1b\x1b[27;...u'.
|
||||
// The buffer would normally treat '\x1b\x1b' as a complete meta-key
|
||||
// sequence (ESC + single char), leaving '[27;...u' to be typed as
|
||||
// plain text. If the character immediately following '\x1b\x1b'
|
||||
// would begin a new escape sequence, emit only the first ESC and
|
||||
// restart from the second.
|
||||
if (candidate === "\x1b\x1b") {
|
||||
const nextChar = remaining[seqEnd];
|
||||
if (
|
||||
nextChar === "[" || // CSI
|
||||
nextChar === "]" || // OSC
|
||||
nextChar === "O" || // SS3
|
||||
nextChar === "P" || // DCS
|
||||
nextChar === "_" // APC
|
||||
) {
|
||||
sequences.push(ESC);
|
||||
pos += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sequences.push(candidate);
|
||||
pos += seqEnd;
|
||||
break;
|
||||
|
||||
@@ -198,6 +198,26 @@ describe("StdinBuffer", () => {
|
||||
assert.deepStrictEqual(emittedSequences, ["\x1b[3;1:3~"]);
|
||||
});
|
||||
|
||||
it("should split ESC+ESC+CSI into standalone ESC and the CSI sequence (WezTerm Escape key regression)", () => {
|
||||
// WezTerm with enable_kitty_keyboard sends Escape key press as raw \x1b
|
||||
// and the release as a full Kitty CSI-u sequence, concatenated.
|
||||
// The buffer must not treat \x1b\x1b as a complete meta-key when the
|
||||
// following byte starts a new escape sequence.
|
||||
processInput("\x1b\x1b[27;129:3u");
|
||||
assert.deepStrictEqual(emittedSequences, ["\x1b", "\x1b[27;129:3u"]);
|
||||
});
|
||||
|
||||
it("should split ESC+ESC+CSI with no modifier (no num_lock)", () => {
|
||||
processInput("\x1b\x1b[27;1:3u");
|
||||
assert.deepStrictEqual(emittedSequences, ["\x1b", "\x1b[27;1:3u"]);
|
||||
});
|
||||
|
||||
it("should still emit ESC+ESC as a single sequence when not followed by a new escape", () => {
|
||||
// \x1b\x1b alone (no following CSI) stays as-is — e.g. ctrl+alt+[
|
||||
processInput("\x1b\x1b");
|
||||
assert.deepStrictEqual(emittedSequences, ["\x1b\x1b"]);
|
||||
});
|
||||
|
||||
it("should handle plain characters mixed with Kitty sequences", () => {
|
||||
// Plain 'a' followed by Kitty release
|
||||
processInput("a\x1b[97;1:3u");
|
||||
|
||||
Reference in New Issue
Block a user