mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-09 03:11:58 +00:00
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:
parent
26f5af52a8
commit
1cebb3bad8
3 changed files with 63 additions and 13 deletions
40
cli.py
40
cli.py
|
|
@ -1848,9 +1848,20 @@ _TERMINAL_INPUT_MODE_RESET_SEQ = (
|
||||||
|
|
||||||
|
|
||||||
def _bind_prompt_submit_keys(kb, handler) -> None:
|
def _bind_prompt_submit_keys(kb, handler) -> None:
|
||||||
"""Bind both CR and LF terminal Enter forms to the submit handler."""
|
"""Bind terminal Enter forms to the submit handler.
|
||||||
for key in ("enter", "c-j"):
|
|
||||||
kb.add(key)(handler)
|
Enter is always submit. On POSIX we also bind c-j (LF) to submit because
|
||||||
|
some thin PTYs (docker exec, certain SSH flavors) deliver Enter as LF
|
||||||
|
instead of CR — without this, Enter appears dead on those terminals.
|
||||||
|
|
||||||
|
On Windows, Windows Terminal delivers Ctrl+Enter as a distinct c-j key
|
||||||
|
while plain Enter is c-m, so we leave c-j unbound here — it becomes the
|
||||||
|
multi-line newline keystroke, giving Windows users an Enter-involving
|
||||||
|
newline without any terminal settings changes.
|
||||||
|
"""
|
||||||
|
kb.add("enter")(handler)
|
||||||
|
if sys.platform != "win32":
|
||||||
|
kb.add("c-j")(handler)
|
||||||
|
|
||||||
|
|
||||||
def _disable_prompt_toolkit_cpr_warning(app) -> None:
|
def _disable_prompt_toolkit_cpr_warning(app) -> None:
|
||||||
|
|
@ -10636,9 +10647,30 @@ class HermesCLI:
|
||||||
|
|
||||||
@kb.add('escape', 'enter')
|
@kb.add('escape', 'enter')
|
||||||
def handle_alt_enter(event):
|
def handle_alt_enter(event):
|
||||||
"""Alt+Enter inserts a newline for multi-line input."""
|
"""Alt+Enter inserts a newline for multi-line input.
|
||||||
|
|
||||||
|
Works on mac/Linux/WSL. On Windows Terminal this keystroke is
|
||||||
|
intercepted at the terminal layer (toggles fullscreen) and never
|
||||||
|
reaches here — Windows users get newline via Ctrl+Enter instead
|
||||||
|
(bound below as c-j, since WT delivers Ctrl+Enter as LF).
|
||||||
|
"""
|
||||||
event.current_buffer.insert_text('\n')
|
event.current_buffer.insert_text('\n')
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
@kb.add('c-j')
|
||||||
|
def handle_ctrl_enter_newline_windows(event):
|
||||||
|
"""Ctrl+Enter inserts a newline on Windows.
|
||||||
|
|
||||||
|
Windows Terminal delivers Ctrl+Enter as LF (c-j), distinct
|
||||||
|
from plain Enter (c-m). This binding makes Ctrl+Enter the
|
||||||
|
Windows equivalent of Alt+Enter, giving an Enter-involving
|
||||||
|
newline keystroke without requiring terminal settings changes.
|
||||||
|
Ctrl+J (the raw LF keystroke) also triggers this by virtue
|
||||||
|
of being the same key code — a harmless side effect since
|
||||||
|
Ctrl+J has no conflicting Hermes binding.
|
||||||
|
"""
|
||||||
|
event.current_buffer.insert_text('\n')
|
||||||
|
|
||||||
# VSCode/Cursor bind Ctrl+G to "Find Next" at the editor level, so
|
# VSCode/Cursor bind Ctrl+G to "Find Next" at the editor level, so
|
||||||
# the keystroke never reaches the embedded terminal. Alt+G is unbound
|
# the keystroke never reaches the embedded terminal. Alt+G is unbound
|
||||||
# in those IDEs and arrives here as ('escape', 'g') — register it as
|
# in those IDEs and arrives here as ('escape', 'g') — register it as
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ TIPS = [
|
||||||
"Combine multiple references: \"Review @file:main.py and @file:test.py for consistency.\"",
|
"Combine multiple references: \"Review @file:main.py and @file:test.py for consistency.\"",
|
||||||
|
|
||||||
# --- Keybindings ---
|
# --- Keybindings ---
|
||||||
"Alt+Enter (or Ctrl+J) inserts a newline for multi-line input.",
|
"Alt+Enter inserts a newline for multi-line input. (Windows Terminal intercepts Alt+Enter — use Ctrl+Enter instead.)",
|
||||||
"Ctrl+C interrupts the agent. Double-press within 2 seconds to force exit.",
|
"Ctrl+C interrupts the agent. Double-press within 2 seconds to force exit.",
|
||||||
"Ctrl+Z suspends Hermes to the background — run fg in your shell to resume.",
|
"Ctrl+Z suspends Hermes to the background — run fg in your shell to resume.",
|
||||||
"Tab accepts auto-suggestion ghost text or autocompletes slash commands.",
|
"Tab accepts auto-suggestion ghost text or autocompletes slash commands.",
|
||||||
|
|
|
||||||
|
|
@ -163,22 +163,40 @@ class TestBusyInputMode:
|
||||||
|
|
||||||
|
|
||||||
class TestPromptToolkitTerminalCompatibility:
|
class TestPromptToolkitTerminalCompatibility:
|
||||||
def test_lf_enter_binds_to_submit_handler(self):
|
def test_lf_enter_binds_to_submit_handler_posix(self):
|
||||||
"""Some thin PTYs deliver Enter as LF/c-j instead of CR/enter."""
|
"""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 prompt_toolkit.key_binding import KeyBindings
|
||||||
|
|
||||||
from cli import _bind_prompt_submit_keys
|
from cli import _bind_prompt_submit_keys
|
||||||
|
|
||||||
kb = KeyBindings()
|
|
||||||
|
|
||||||
def submit_handler(event):
|
def submit_handler(event):
|
||||||
return None
|
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}
|
# Windows: only enter submits; c-j is free for the newline binding
|
||||||
assert bindings[("c-m",)] is submit_handler
|
# added separately in the prompt setup.
|
||||||
assert bindings[("c-j",)] is submit_handler
|
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):
|
def test_cpr_warning_callback_is_disabled(self):
|
||||||
from cli import _disable_prompt_toolkit_cpr_warning
|
from cli import _disable_prompt_toolkit_cpr_warning
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue