fix(tui): uppercase shift+letter input from kitty keyboard protocol - #37687
liuhao1024 wants to merge 1 commit into
Conversation
When the kitty keyboard protocol is active, Shift+A sends \x1b[97;2u which hermes-ink reduces to input='a' + key.shift=true. The composer's useInput handler inserted the lowercase character without checking the shift flag, so Shift+A produced 'a' instead of 'A'. Add a guard before applyPrintableInsert that uppercases single ASCII letters when k.shift is true. Multi-char paste, bracketed input, and non-ASCII characters pass through unchanged. Fixes NousResearch#37680
|
Thanks for the focused TUI fix. The premise remains reproducible from the current input pipeline: Problems
Suggested changes
Automated hermes-sweeper review. |
|
Update: the CLI-side variant of this bug is fixed by #87511, which maps all Shift+letter combos under modifyOtherKeys level 2 in This PR (#37687) fixes the TUI side ( If this PR is merged, both the CLI and TUI will correctly uppercase Shift+letter input on terminals with modifyOtherKeys/Kitty keyboard protocol active. |
|
Thanks for the cross-link — good to have the two-surface picture spelled out. Confirming the split: this PR fixes the TUI/Ink text input ( |
Under enhanced key reporting, Shift+letter arrives as an escape sequence, and hermes-ink rebuilds typed text from the parsed key name (input-event.ts uses `name` when ctrl is clear). keycodeToName() unconditionally lowercased printable ASCII and dropped the shift bit, so every Shift+letter decoded as lowercase: the composer silently lost capitalization. This became a default-path regression for Ghostty when the Ink TUI stopped pushing the kitty keyboard protocol for it (45f1126 on the maintained fork; upstream equivalent of the cli.py Ghostty exception), leaving xterm modifyOtherKeys level 2 as the only enhanced mode — and Ghostty re-encodes every Shift+letter under it. The same decoder gap was reported for the classic CLI in NousResearch#87390/NousResearch#87631; NousResearch#37687 patches the composer instead, which leaves other parsed-key consumers broken. Fix at the decoder: keycodeToName() takes the shift modifier and capitalizes letters when set (shift && !ctrl at both call sites). Terminals disagree on which codepoint they report (kitty CSI-u sends the base key, ESC[97;2u; xterm modifyOtherKeys usually the shifted result, ESC[27;2;65~), so an uppercase report is preserved and a lowercase one promoted. Control chords keep the lowercase name because input-event.ts takes input = name verbatim when ctrl is set, and all chord matching is case-folded. Co-Authored-By: ox-alpha <noreply@nousresearch.com>
Under enhanced key reporting, Shift+letter arrives as an escape sequence, and hermes-ink rebuilds typed text from the parsed key name (input-event.ts uses `name` when ctrl is clear). keycodeToName() unconditionally lowercased printable ASCII and dropped the shift bit, so every Shift+letter decoded as lowercase: the composer silently lost capitalization. This became a default-path regression for Ghostty when the Ink TUI stopped pushing the kitty keyboard protocol for it (45f1126 on the maintained fork; upstream equivalent of the cli.py Ghostty exception), leaving xterm modifyOtherKeys level 2 as the only enhanced mode — and Ghostty re-encodes every Shift+letter under it. The same decoder gap was reported for the classic CLI in NousResearch#87390/NousResearch#87631; NousResearch#37687 patches the composer instead, which leaves other parsed-key consumers broken. Fix at the decoder: keycodeToName() takes the shift modifier and capitalizes letters when set (shift && !ctrl at both call sites). Terminals disagree on which codepoint they report (kitty CSI-u sends the base key, ESC[97;2u; xterm modifyOtherKeys usually the shifted result, ESC[27;2;65~), so an uppercase report is preserved and a lowercase one promoted. Control chords keep the lowercase name because input-event.ts takes input = name verbatim when ctrl is set, and all chord matching is case-folded. Co-Authored-By: ox-alpha <noreply@nousresearch.com>
Under enhanced key reporting, Shift+letter arrives as an escape sequence, and hermes-ink rebuilds typed text from the parsed key name (input-event.ts uses `name` when ctrl is clear). keycodeToName() unconditionally lowercased printable ASCII and dropped the shift bit, so every Shift+letter decoded as lowercase: the composer silently lost capitalization. This became a default-path regression for Ghostty when the Ink TUI stopped pushing the kitty keyboard protocol for it (45f1126 on the maintained fork; upstream equivalent of the cli.py Ghostty exception), leaving xterm modifyOtherKeys level 2 as the only enhanced mode — and Ghostty re-encodes every Shift+letter under it. The same decoder gap was reported for the classic CLI in NousResearch#87390/NousResearch#87631; NousResearch#37687 patches the composer instead, which leaves other parsed-key consumers broken. Fix at the decoder: keycodeToName() takes the shift modifier and capitalizes letters when set (shift && !ctrl at both call sites). Terminals disagree on which codepoint they report (kitty CSI-u sends the base key, ESC[97;2u; xterm modifyOtherKeys usually the shifted result, ESC[27;2;65~), so an uppercase report is preserved and a lowercase one promoted. Control chords keep the lowercase name because input-event.ts takes input = name verbatim when ctrl is set, and all chord matching is case-folded. Co-Authored-By: ox-alpha <noreply@nousresearch.com>
What does this PR do?
Fixes Shift+letter capitalization in
hermes --tuiwhen the kitty keyboard protocol is active. Previously, holding Shift+A would insertainstead ofAbecause the composer'suseInputhandler inserted the raw input character without checking the shift modifier.Related Issue
Fixes #37680
Type of Change
Changes Made
ui-tui/src/components/textInput.tsx: Add shift-uppercase guard beforeapplyPrintableInsert— whenk.shiftis true and the input is a single ASCII letter (a-z), uppercase it before insertion. Multi-char paste, bracketed input, and non-ASCII characters pass through unchanged.How to Test
hermes --tuiin Ghostty (or any terminal with kitty keyboard protocol support)A, notaZ, notzcd ui-tui && npm test -- --run— all textInput-related tests should pass (1 pre-existing failure invirtualHeights.test.tsis unrelated)Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/ACode Intelligence
ui-tui/src/components/textInput.tsx(useInput handler, line 1111-1152)k.shiftalready used for selection (line 960) and newline insertion (line 974); this extends it to printable character uppercase