fix(cli): make Alt+Enter insert a newline on LF-as-Enter terminals
On terminals that send a bare LF for plain Enter (WSL is the case
prompt_toolkit itself calls out), Alt+Enter arrives as ESC + LF
("\x1b\x0a" = Escape + ControlJ), not the ESC + CR the existing
"escape","enter" binding matches. The Escape was swallowed and the bare
LF hit prompt_toolkit's default submit, so the documented "universally
supported" Alt+Enter newline fallback failed on exactly the terminal path
plain Enter is preserved for.
Bind ESC + ControlJ to insert a newline too, and add a real-PromptSession
regression test covering the WSL Alt+Enter path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
committed by
Xubin Ren
co-authored by
Claude Opus 4.8
parent
25a477050d
commit
77a6003255
+10
-1
@@ -346,7 +346,16 @@ def _build_cli_key_bindings() -> KeyBindings:
|
||||
def _(event):
|
||||
event.current_buffer.validate_and_handle()
|
||||
|
||||
@kb.add("escape", "enter") # Alt+Enter / Meta+Enter
|
||||
@kb.add("escape", "enter") # Alt+Enter / Meta+Enter (ESC + CR, "\x1b\r")
|
||||
def _(event):
|
||||
event.current_buffer.insert_text("\n")
|
||||
|
||||
# On the same LF-as-Enter terminals (WSL) we preserve plain Enter for,
|
||||
# Alt+Enter arrives as ESC + LF ("\x1b\x0a" = Escape + ControlJ) rather than
|
||||
# ESC + CR, so the "escape","enter" binding above never matches: Escape is
|
||||
# swallowed and the bare LF triggers prompt_toolkit's default submit. Bind
|
||||
# ESC + ControlJ too so Alt+Enter reliably inserts a newline there as well.
|
||||
@kb.add("escape", Keys.ControlJ) # Alt+Enter on LF-as-Enter terminals
|
||||
def _(event):
|
||||
event.current_buffer.insert_text("\n")
|
||||
|
||||
|
||||
@@ -140,6 +140,29 @@ async def test_xterm_modifyotherkeys_shift_enter_inserts_newline():
|
||||
assert result == "foo\nbar"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_alt_enter_inserts_newline_on_lf_terminals():
|
||||
"""On LF-as-Enter terminals (WSL), Alt+Enter arrives as ESC + LF
|
||||
("\\x1b\\x0a" = Escape + ControlJ) rather than the ESC + CR that the
|
||||
"escape","enter" binding matches. Without a dedicated ESC + ControlJ
|
||||
binding the Escape is swallowed and the bare LF submits, so the documented
|
||||
Alt+Enter newline fallback breaks on exactly the terminal path plain Enter
|
||||
is preserved for. Drive a real PromptSession to prove it inserts a newline.
|
||||
"""
|
||||
from prompt_toolkit.application import create_app_session
|
||||
from prompt_toolkit.input import create_pipe_input
|
||||
from prompt_toolkit.output import DummyOutput
|
||||
|
||||
with create_pipe_input() as pipe_input:
|
||||
with create_app_session(input=pipe_input, output=DummyOutput()):
|
||||
commands._init_prompt_session()
|
||||
session = commands._PROMPT_SESSION
|
||||
pipe_input.send_text("foo\x1b\x0abar\r")
|
||||
result = await session.prompt_async("> ")
|
||||
|
||||
assert result == "foo\nbar"
|
||||
|
||||
|
||||
def test_thinking_spinner_pause_stops_and_restarts():
|
||||
"""Pause should stop the active spinner and restart it afterward."""
|
||||
spinner = MagicMock()
|
||||
|
||||
Reference in New Issue
Block a user