diff --git a/cli.py b/cli.py index da2f32954ba5a..4cc42dea96d52 100644 --- a/cli.py +++ b/cli.py @@ -1913,7 +1913,7 @@ def _strip_leaked_bracketed_paste_wrappers(text: str) -> str: def _preserve_ctrl_enter_newline() -> bool: """Detect environments where Ctrl+Enter must produce a newline, not submit. - Native Windows, WSL, SSH sessions, and Windows Terminal all send Ctrl+Enter + Native macOS, Windows, WSL, SSH sessions, and Windows Terminal all send Ctrl+Enter as bare LF (c-j). On those terminals c-j must NOT be bound to submit; binding it to submit makes Ctrl+Enter (intended as 'newline like Alt+Enter') submit instead. Local POSIX TTYs that deliver Enter as LF (docker exec, @@ -1922,7 +1922,7 @@ def _preserve_ctrl_enter_newline() -> bool: See issue #22379. """ - if sys.platform == "win32": + if sys.platform in {"darwin", "win32"}: return True if any(os.environ.get(v) for v in ("SSH_CONNECTION", "SSH_CLIENT", "SSH_TTY")): return True @@ -1948,7 +1948,7 @@ def _bind_prompt_submit_keys(kb, handler) -> None: some thin PTYs (docker exec, certain SSH flavors) deliver Enter as LF instead of CR — without this, Enter appears dead on those terminals. - Exception: on Windows, WSL, SSH sessions, and Windows Terminal, + Exception: on macOS, Windows, WSL, SSH sessions, and Windows Terminal, c-j is the wire encoding of Ctrl+Enter (a distinct keystroke from plain Enter / c-m). We leave c-j unbound there so the c-j newline handler registered separately can fire — giving the user an @@ -5187,7 +5187,7 @@ def show_help(self): ) _cprint(f"\n {_DIM}Tip: Just type your message to chat with Hermes!{_RST}") - _cprint(f" {_DIM}Multi-line: Alt+Enter for a new line{_RST}") + _cprint(f" {_DIM}Multi-line: Ctrl+J or Alt+Enter for a new line{_RST}") _cprint(f" {_DIM}Draft editor: Ctrl+G (Alt+G in VSCode/Cursor){_RST}") if _is_termux_environment(): _cprint(f" {_DIM}Attach image: /image {_termux_example_image_path()} or start your prompt with a local image path{_RST}\n") @@ -11421,7 +11421,7 @@ def handle_alt_enter(event): 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 + reaches here — Windows and macOS users get newline via Ctrl+J/Ctrl+Enter instead (bound below as c-j, since WT delivers Ctrl+Enter as LF). """ event.current_buffer.insert_text('\n') diff --git a/tests/cli/test_cli_init.py b/tests/cli/test_cli_init.py index ee5ffb390d130..40341fff00d7e 100644 --- a/tests/cli/test_cli_init.py +++ b/tests/cli/test_cli_init.py @@ -168,7 +168,7 @@ def test_lf_enter_binds_to_submit_handler_posix(self): On a bare local POSIX TTY (no SSH/WSL/WT) we keep c-j → submit so Enter works on thin PTYs (docker exec, certain ssh configurations). - On Windows, WSL, SSH sessions, and Windows Terminal we leave c-j + On macOS, Windows, WSL, SSH sessions, and Windows Terminal we leave c-j unbound here so it can be used as the Ctrl+Enter newline keystroke without conflicting with submit. See issue #22379. """ @@ -203,6 +203,16 @@ def submit_handler(event): assert bindings[("c-m",)] is submit_handler assert ("c-j",) not in bindings + # macOS: only enter submits; c-j is free for the newline binding + # added separately in the prompt setup. + with _patch.object(_sys, "platform", "darwin"), \ + _patch.dict(_os.environ, {}, clear=True): + 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 + # Windows: only enter submits; c-j is free for the newline binding # added separately in the prompt setup. with _patch.object(_sys, "platform", "win32"): diff --git a/tests/cli/test_ctrl_enter_newline.py b/tests/cli/test_ctrl_enter_newline.py index 57056ab0e1894..6029fc4f8dbe7 100644 --- a/tests/cli/test_ctrl_enter_newline.py +++ b/tests/cli/test_ctrl_enter_newline.py @@ -22,6 +22,13 @@ def test_native_windows_preserves_newline(): assert cli_mod._preserve_ctrl_enter_newline() is True +def test_native_macos_preserves_newline(): + import cli as cli_mod + with patch.object(sys, "platform", "darwin"): + with patch.dict(os.environ, {}, clear=True): + assert cli_mod._preserve_ctrl_enter_newline() is True + + def test_ssh_session_preserves_newline_on_linux(): import cli as cli_mod with patch.object(sys, "platform", "linux"):