feat(cli): recognise Shift+Enter as a newline key (salvage #21545 + Windows docs) - #22130
Merged
Conversation
Closes #5346. Most terminals send the same byte sequence for `Enter` and `Shift+Enter` by default, so the application can't tell them apart — this is a terminal protocol limitation, not something Hermes can paper over. But terminals that implement the Kitty keyboard protocol (Kitty / foot / WezTerm / Ghostty by default; iTerm2 / Alacritty / VS Code terminal / Warp once the protocol is enabled) DO emit a distinct sequence for `Shift+Enter`: - `\x1b[13;2u` — Kitty / CSI-u, modifier=2 - `\x1b[27;2;13~` — xterm modifyOtherKeys=2 Stock prompt_toolkit doesn't have the CSI-u sequence in its `ANSI_SEQUENCES` table at all, and it maps the modifyOtherKeys variant to plain `Keys.ControlM` (Enter) — i.e. it strips the Shift modifier, which is the bug users actually hit on iTerm2 and friends. This PR adds `hermes_cli/pt_input_extras.install_shift_enter_alias()`, called once at CLI startup from `cli.py`, which inserts/overwrites those sequences in `ANSI_SEQUENCES` so they decode to `(Keys.Escape, Keys.ControlM)` — the same key tuple `Alt+Enter` produces. The existing Alt+Enter newline handler (`@kb.add('escape', 'enter')` in `cli.py`) then fires unchanged, so there is no new keybinding to register and no behavioral change for terminals that don't emit the distinct sequences. Files ===== * `hermes_cli/pt_input_extras.py` — new module hosting the helper. Lives outside `cli.py` so it's importable in tests without dragging in the full CLI runtime (which depends on `fire`, `rich`, etc.). * `cli.py` — calls `install_shift_enter_alias()` once at module import. Wrapped in try/except so prompt_toolkit version drift can't break CLI startup. * `tests/cli/test_cli_shift_enter_newline.py` — 6 tests: - registration of all three byte sequences - overwrite of stock prompt_toolkit's broken modifyOtherKeys mapping - idempotency - parser equivalence: CSI-u Shift+Enter == Alt+Enter - parser equivalence: modifyOtherKeys Shift+Enter == Alt+Enter - plain Enter remains a single key (submit), distinct from the two-key Alt+Enter / Shift+Enter tuple * `website/docs/user-guide/cli.md` — keybinding table updated; new "Shift+Enter compatibility" subsection with a per-terminal status table noting macOS Terminal / stock Windows Terminal cannot distinguish the keystroke at the protocol level. * `website/docs/getting-started/quickstart.md`, `website/docs/guides/tips.md` — short mention pointing readers at the full compatibility note in `cli.md`. Tested ====== pytest tests/cli/test_cli_shift_enter_newline.py # 6 passed Live-tested by triggering `\x1b[13;2u` against the running Vt100Parser (see test). Not exercised in a real terminal end-to-end because that requires a Kitty-protocol-capable host; the test exercises the parser path that drives the live terminal too.
Windows Terminal captures Alt+Enter at the terminal layer (fullscreen toggle), so documenting 'Alt+Enter or Ctrl+J' without qualification leaves stock Windows Terminal users with no working newline key they can discover from the docs alone. - Main keybindings row: note Alt+Enter is intercepted on WT and direct users to Ctrl+Enter / Ctrl+J instead. - Shift+Enter compatibility table: split 'stock Windows Terminal' from Windows Terminal Preview 1.25+ (which added Kitty protocol support and works with the keybinding from this PR once enabled). - Add AUTHOR_MAP entry for ra2157218@gmail.com -> Abd0r so the salvage commit passes the email-mapping CI gate.
15 tasks
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
6 |
First entries
hermes_cli/pt_input_extras.py:40: [unresolved-import] unresolved-import: Cannot resolve imported module `prompt_toolkit.input.ansi_escape_sequences`
tests/cli/test_cli_shift_enter_newline.py:9: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
hermes_cli/pt_input_extras.py:41: [unresolved-import] unresolved-import: Cannot resolve imported module `prompt_toolkit.keys`
tests/cli/test_cli_shift_enter_newline.py:12: [unresolved-import] unresolved-import: Cannot resolve imported module `prompt_toolkit.input.vt100_parser`
tests/cli/test_cli_shift_enter_newline.py:11: [unresolved-import] unresolved-import: Cannot resolve imported module `prompt_toolkit.input.ansi_escape_sequences`
tests/cli/test_cli_shift_enter_newline.py:13: [unresolved-import] unresolved-import: Cannot resolve imported module `prompt_toolkit.keys`
✅ Fixed issues: none
Unchanged: 4116 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
1 task
This was referenced Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Salvages #21545 (@Abd0r) onto current main and adds a Windows Terminal docs callout on top.
What the salvaged PR does
Registers CSI-u (
\x1b[13;2u) and xterm modifyOtherKeys (\x1b[27;2;13~) Shift+Enter byte sequences in prompt_toolkit'sANSI_SEQUENCESso they decode to(Escape, ControlM)— the same tuple Alt+Enter produces. The existing Alt+Enter newline handler incli.pyfires unchanged. Zero new keybindings, zero behavioral change for terminals that don't emit those sequences.Benefits terminals that speak the Kitty keyboard protocol: Kitty, foot, WezTerm, Ghostty (by default); iTerm2 / Alacritty / VS Code terminal / Warp (once enabled); and Windows Terminal Preview 1.25+ (Kitty protocol added March 2026).
Docs follow-up (this PR's addition)
A user reported (Discord, stock Windows Terminal) that Alt+Enter doesn't work and the docs don't explain why. Windows Terminal captures Alt+Enter at the terminal layer as the fullscreen toggle, so the keystroke never reaches Hermes. The Windows-specific answer — Ctrl+Enter (delivered as
Ctrl+J) — is already wired incli.py:10758-10771but was not called out in the docs.Validation
E2E with real prompt_toolkit (no mocks) confirmed the helper registers 3 sequences, is idempotent, and that the
Vt100Parserresolves CSI-u Shift+Enter to the exact same[Escape, ControlM]tuple as Alt+Enter.Attribution
Cherry-picked commit 643ac85 authored by @Abd0r preserved via rebase-merge; AUTHOR_MAP entry added for
ra2157218@gmail.com. Docs callout is a separate commit by us.