Merge pull request #4482 from Felixoid/fix-wezterm-kitty-esc

Address edge-case with kitty protocol in wezterm
This commit is contained in:
Mario Zechner
2026-05-17 00:36:59 +02:00
committed by GitHub
2 changed files with 43 additions and 0 deletions

View File

@@ -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;

View File

@@ -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");