fix(cli): Ghostty gets modifyOtherKeys level 1 — Shift+letters leak as literal '[27;2;<code>~' when IME splits the ESC byte - #91937
Conversation
No blocking issues found. The root-cause analysis is convincing and the fix is minimal: dropping Ghostty from modifyOtherKeys level 2 to level 1 keeps every alias the CLI maps (Shift/Ctrl+Enter, modified Backspace/Tab are still re-encoded under level 1) while Shift+letters return to plain text, so the IME-split-ESC leak path has nothing left to emit. Both detection paths (TERM_PROGRAM and TERM) are covered by updated assertions, including the negative check that level 2 is gone. Nit: tests/cli/test_ctrl_enter_newline.py:171-193 — the suite pins which escape sequence is written, but nothing reproduces the actual failure mode: two consecutive |
…eral [27;2;<code>~ when IME splits the ESC byte (NousResearch#87390)
c566ada to
d77d987
Compare
|
Thanks for the review! Addressed both points. Split-delivery regression test (nit, accepted): added modifyOtherKeys pop on shutdown (observation): the teardown sequence actually does reset it — 244 passed, 2 skipped with the new test in place. |
Summary
On Ghostty, Shift+letter in the classic CLI inserts the literal text
[27;2;65~instead of the capital (Shift+W →[27;2;87~, etc.). Regression class of #87390: the alias table added in #87511 fixed the whole-sequence case, but the split-delivery path remains broken.Root cause chain
_enable_extended_enter_keys()pushes xterm modifyOtherKeys level 2 (CSI >4;2m) for Ghostty (allowlist entry), so Ghostty re-encodes every modified key (Shift+A) asESC[27;2;65~.[27;...~body.vt100_parser.flush()after each chunk (Vt100Input.flush_keys— 'Used for flushing the escape key'), so the lone\x1bis emitted as the Escape key; the body then arrives as literal characters and is inserted into the prompt.Parser repro (prompt_toolkit 3.0.52 + current alias table):
feed('\x1b[27;2;65~') + flush→['A']✓feed('\x1b') + flush; feed('[27;2;65~') + flush→[Escape,'[','2','7',';','2',';','6','5','~']✗ (exact symptom)Fix
Ghostty branch now pushes modifyOtherKeys level 1 (
CSI >4;1m) instead of level 2. Level 1 only re-encodes modified keys whose unmodified form is a control character (Enter 13, Backspace 127, Tab 9) — exactly the set this CLI decodes — while Shift+letters arrive as plain capitals again, immune to the split-ESC/IME path. Kitty protocol is still not pushed for Ghostty (keeps the #87630 Backspace-Alt workaround). Other allowlisted terminals (iTerm2, WezTerm, tmux, VS Code) keep the dual kitty+level-2 push and the full alias table.Preserved: Shift+Enter → newline, Ctrl+Enter → newline, Cmd+Backspace kill, Shift+Tab BackTab, etc. — all level-1 control-key encodings already in
pt_input_extras.py.Verification
\x1b[>4;1m) and plain-'A' delivery for Shift+A.tests/cli/test_ctrl_enter_newline.py.pytest tests/cli/test_ctrl_enter_newline.py tests/cli/test_cli_shift_enter_newline.py tests/cli/test_cli_cmd_backspace.py tests/cli/test_modify_other_keys_aliases.py→ 244 passed, 2 skipped.Closes the #87390 leftover (split-delivery path).