Skip to content

feat(cli): recognise Shift+Enter as a newline key (salvage #21545 + Windows docs) - #22130

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-c88029bd
May 8, 2026
Merged

feat(cli): recognise Shift+Enter as a newline key (salvage #21545 + Windows docs)#22130
teknium1 merged 2 commits into
mainfrom
hermes/hermes-c88029bd

Conversation

@teknium1

@teknium1 teknium1 commented May 8, 2026

Copy link
Copy Markdown
Contributor

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's ANSI_SEQUENCES so they decode to (Escape, ControlM) — the same tuple Alt+Enter produces. The existing Alt+Enter newline handler in cli.py fires 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 in cli.py:10758-10771 but was not called out in the docs.

  • Main keybindings table: flag that Alt+Enter is intercepted on WT and point at Ctrl+Enter / Ctrl+J.
  • Shift+Enter compatibility table: split 'stock Windows Terminal (stable)' from 'Windows Terminal Preview 1.25+' (which gained Kitty protocol support, so the salvaged binding applies once enabled).

Validation

scripts/run_tests.sh tests/cli/test_cli_shift_enter_newline.py -v
# 6 passed

E2E with real prompt_toolkit (no mocks) confirmed the helper registers 3 sequences, is idempotent, and that the Vt100Parser resolves 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.

Abd0r and others added 2 commits May 8, 2026 16:24
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.
@teknium1
teknium1 merged commit d606df8 into main May 8, 2026
15 of 16 checks passed
@teknium1
teknium1 deleted the hermes/hermes-c88029bd branch May 8, 2026 23:26
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-c88029bd vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 7823 on HEAD, 7817 on base (🆕 +6)

🆕 New issues (6):

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants