diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-05-08 09:51:20 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-08 09:51:20 +0200 |
| commit | e927b2b659296c19e421bc36d1b702f2c8d24fd3 (patch) | |
| tree | 3b33e0ca9fb1b89c08bdfb5e12ef87b103449aee /zellij-utils/src | |
| parent | fix(windows): bump windows-sys to 0.59 (#5139) (diff) | |
| download | sl0ppij-e927b2b659296c19e421bc36d1b702f2c8d24fd3.tar.gz sl0ppij-e927b2b659296c19e421bc36d1b702f2c8d24fd3.tar.xz sl0ppij-e927b2b659296c19e421bc36d1b702f2c8d24fd3.zip | |
fix: occasional stall when querying host terminal (introduced in 0.44.1) (#5149)
* fix(client): unwedge termwiz on unrecognized CSI sequences
* rustfmt
* add pr
Diffstat (limited to 'zellij-utils/src')
| -rw-r--r-- | zellij-utils/src/vendored/termwiz/input.rs | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/zellij-utils/src/vendored/termwiz/input.rs b/zellij-utils/src/vendored/termwiz/input.rs index c25f712b..ec84a77f 100644 --- a/zellij-utils/src/vendored/termwiz/input.rs +++ b/zellij-utils/src/vendored/termwiz/input.rs @@ -838,6 +838,38 @@ fn parse_sgr_mouse(buf: &[u8]) -> Option<(InputEvent, usize)> { /// `ESC [` for two distinct cases — not currently disambiguated here: /// - truly malformed / unsupported sequence, or /// - incomplete input; caller handles incompleteness via `maybe_more`. +/// Return `Some(len)` if `buf` starts with a structurally complete CSI +/// sequence (`\x1b[ <params>* <intermediates>* <final>` per ECMA-48 §5.4), +/// regardless of whether the final byte is one we have a use for. Used by +/// `process_bytes` to advance past CSI sequences the keymap doesn't +/// recognise — most importantly Kitty keyboard-protocol events +/// `\x1b[<keycode>;<mods>u`. Without this, the keymap returns +/// `Found::NeedData` (it sees the bytes as a possible prefix of a longer +/// registered key) and the parser wedges holding bytes that will never +/// extend into anything. +/// +/// Returns `None` if the buffer doesn't start with `\x1b[`, contains a +/// non-CSI byte, or hasn't yet received its final byte. +fn complete_csi_len(buf: &[u8]) -> Option<usize> { + if buf.get(0) != Some(&0x1b) || buf.get(1) != Some(&b'[') { + return None; + } + let mut i = 2; + let max_scan = buf.len().min(256); + while i < max_scan { + let b = buf[i]; + match b { + // Parameters (digits, ;, :, ?, <, =, >) and intermediates (space..'/'). + 0x30..=0x3F | 0x20..=0x2F => i += 1, + // Any byte in the final-byte range terminates a CSI sequence. + 0x40..=0x7E => return Some(i + 1), + // Anything else means this isn't a well-formed CSI. + _ => return None, + } + } + None +} + fn parse_csi_report(buf: &[u8]) -> Option<(InputEvent, usize)> { if buf.get(0) != Some(&0x1b) || buf.get(1) != Some(&b'[') { return None; @@ -1701,6 +1733,42 @@ impl InputParser { self.buf.advance(len); }, (Found::Ambiguous(_, _), true) | (Found::NeedData, true) => { + // The keymap is signalling "this buffer + // could still grow into a registered key, + // give me more bytes." That verdict is + // wrong when the buffer already holds a + // structurally complete CSI sequence whose + // final byte isn't in the keymap — most + // importantly Kitty keyboard-protocol + // events `\x1b[<keycode>;<mods>u`, which + // never grow into anything the keymap + // knows. Returning here would wedge + // `self.buf` indefinitely, swallowing every + // host reply that arrives behind it (the + // OSC + DA1 bytes for a forwarded + // `OSC 11;?` query among them) and stalling + // host-color forwards until session exit. + // + // Both `Ambiguous(_, true)` and + // `NeedData(true)` reach this point in + // practice: for `\x1b[<digits>;<digits>u` + // the trie reports `Ambiguous(1, Escape)` + // (it has ESC alone as a match and ESC[…] + // as longer prefixes), so the fix must + // cover both verdicts. + // + // Skip past the unrecognised CSI without + // emitting an event; callers that need + // keyboard dispatch (kitty_parser, the + // separate `input_parser` instance fed the + // residue from `StdinAnsiParser`) see the + // same bytes via `strip_replies`, which + // already treats unwhitelisted-final CSIs + // as `Malformed` and pushes them through. + if let Some(len) = complete_csi_len(self.buf.as_slice()) { + self.buf.advance(len); + continue; + } return; }, (Found::None, _) | (Found::NeedData, false) => { |
