feat: Ctrl+Enter inserts newline on Windows Terminal

Windows Terminal intercepts Alt+Enter for its fullscreen shortcut, leaving
Windows users with no Enter-involving way to insert a newline in the Hermes
prompt. Fix it by reclaiming c-j on Windows only:

- _bind_prompt_submit_keys now binds c-j (LF) to submit only on POSIX, where
  thin PTYs (docker exec, some SSH configs) deliver Enter as LF. On Windows
  plain Enter is always c-m, so c-j is free.
- Windows-only prompt binding: c-j inserts a newline. Windows Terminal sends
  Ctrl+Enter as LF, so the user-facing keystroke is Ctrl+Enter — no terminal
  settings changes required.
- Alt+Enter binding unchanged; still works on mac/Linux/WSL.
- Test TestPromptToolkitTerminalCompatibility::test_lf_enter_binds_to_submit_handler
  split into platform-aware assertions for POSIX vs win32.
- Fixed the Ctrl+J claim in hermes_cli/tips.py (was wrong before this commit
  even on POSIX) to point Windows users at Ctrl+Enter.

Tradeoff: on Windows, raw Ctrl+J (without Enter) also inserts a newline,
since WT collapses Ctrl+Enter and Ctrl+J to the same c-j keycode. No
conflicting Hermes binding existed for Ctrl+J, so this is a harmless side
effect.
This commit is contained in:
Teknium 2026-05-08 06:23:25 -07:00
parent 40e7a71c35
commit d1838041e5
3 changed files with 63 additions and 13 deletions

View file

@ -163,22 +163,40 @@ class TestBusyInputMode:
class TestPromptToolkitTerminalCompatibility:
def test_lf_enter_binds_to_submit_handler(self):
"""Some thin PTYs deliver Enter as LF/c-j instead of CR/enter."""
def test_lf_enter_binds_to_submit_handler_posix(self):
"""Some thin PTYs deliver Enter as LF/c-j instead of CR/enter.
On POSIX we keep the c-j submit binding so Enter works on thin
PTYs (docker exec, certain SSH configurations). On Windows c-j is
reclaimed as the newline keystroke because Windows Terminal
delivers Ctrl+Enter as LF, and we want an Enter-involving newline
without requiring terminal-settings changes.
"""
import sys as _sys
from unittest.mock import patch as _patch
from prompt_toolkit.key_binding import KeyBindings
from cli import _bind_prompt_submit_keys
kb = KeyBindings()
def submit_handler(event):
return None
_bind_prompt_submit_keys(kb, submit_handler)
# POSIX: both enter and c-j submit
with _patch.object(_sys, "platform", "linux"):
kb = KeyBindings()
_bind_prompt_submit_keys(kb, submit_handler)
bindings = {tuple(key.value for key in binding.keys): binding.handler for binding in kb.bindings}
assert bindings[("c-m",)] is submit_handler
assert bindings[("c-j",)] is submit_handler
bindings = {tuple(key.value for key in binding.keys): binding.handler for binding in kb.bindings}
assert bindings[("c-m",)] is submit_handler
assert bindings[("c-j",)] is submit_handler
# Windows: only enter submits; c-j is free for the newline binding
# added separately in the prompt setup.
with _patch.object(_sys, "platform", "win32"):
kb = KeyBindings()
_bind_prompt_submit_keys(kb, submit_handler)
bindings = {tuple(key.value for key in binding.keys): binding.handler for binding in kb.bindings}
assert bindings[("c-m",)] is submit_handler
assert ("c-j",) not in bindings
def test_cpr_warning_callback_is_disabled(self):
from cli import _disable_prompt_toolkit_cpr_warning