fix: remove unreliable Shift+Enter shortcut
maintainer edit: keep Alt+Enter as the supported multiline input path and remove the terminal-dependent Shift+Enter ANSI sequence patch.
This commit is contained in:
+1
-33
@@ -312,34 +312,7 @@ def _build_cli_key_bindings() -> KeyBindings:
|
||||
* Alt+Enter -> insert a newline. Universally supported across
|
||||
terminal emulators, so this is the reliable way to
|
||||
compose multi-line input.
|
||||
* Shift+Enter -> insert a newline *if* the terminal sends a dedicated
|
||||
sequence for it (kitty / iTerm2 with the CSI-u /
|
||||
fixterms keyboard protocol). Plain terminals collapse
|
||||
Shift+Enter into Enter and cannot be distinguished, in
|
||||
which case Alt+Enter is the fallback.
|
||||
"""
|
||||
# prompt_toolkit has no symbolic Keys.ShiftEnter and @kb.add() rejects raw
|
||||
# escape strings, so register the CSI-u Shift+Enter sequences against
|
||||
# Keys.ControlF3 -- an enum member prompt_toolkit declares but never wires
|
||||
# to a default ANSI sequence or key binding, unlike e.g. Keys.ControlJ,
|
||||
# which is also the literal LF byte ("\x0a") that some terminals (WSL is
|
||||
# the case prompt_toolkit itself calls out) send for a plain Enter
|
||||
# keypress. Aliasing to ControlJ made Enter stop submitting there, since
|
||||
# our handler shadowed prompt_toolkit's own "treat \n as \r" fallback.
|
||||
#
|
||||
# "\x1b[27;2;13~" (the older xterm modifyOtherKeys / rxvt encoding of
|
||||
# Shift+Enter) is already registered by prompt_toolkit by default -- as
|
||||
# Keys.ControlM, i.e. plain Enter/submit. A plain setdefault() would be a
|
||||
# no-op against that existing entry, silently leaving this Shift+Enter
|
||||
# variant behaving like a submit instead of inserting a newline. Assign
|
||||
# directly to override it, since inserting a newline is the whole point
|
||||
# of a Shift+Enter binding here.
|
||||
with suppress(Exception):
|
||||
from prompt_toolkit.input import ansi_escape_sequences as _aes
|
||||
|
||||
for _seq in ("\x1b[13;2u", "\x1b[27;2;13~"):
|
||||
_aes.ANSI_SEQUENCES[_seq] = Keys.ControlF3
|
||||
|
||||
kb = KeyBindings()
|
||||
|
||||
@kb.add("enter")
|
||||
@@ -359,10 +332,6 @@ def _build_cli_key_bindings() -> KeyBindings:
|
||||
def _(event):
|
||||
event.current_buffer.insert_text("\n")
|
||||
|
||||
@kb.add(Keys.ControlF3) # Shift+Enter on CSI-u capable terminals
|
||||
def _(event):
|
||||
event.current_buffer.insert_text("\n")
|
||||
|
||||
return kb
|
||||
|
||||
|
||||
@@ -385,8 +354,7 @@ def _init_prompt_session() -> None:
|
||||
history=SafeFileHistory(str(history_file)),
|
||||
enable_open_in_editor=False,
|
||||
# Multiline-capable buffer; Enter still submits via the custom key
|
||||
# bindings, while Shift+Enter (supported terminals) or Alt+Enter adds
|
||||
# a newline.
|
||||
# bindings, while Alt+Enter adds a newline.
|
||||
multiline=True,
|
||||
key_bindings=_build_cli_key_bindings(),
|
||||
)
|
||||
|
||||
@@ -46,8 +46,8 @@ def test_init_prompt_session_creates_session():
|
||||
# Ensure global is None before test
|
||||
commands._PROMPT_SESSION = None
|
||||
|
||||
with patch("nanobot.cli.commands.PromptSession") as MockSession, \
|
||||
patch("nanobot.cli.commands.FileHistory") as MockHistory, \
|
||||
with patch("nanobot.cli.commands.PromptSession") as mock_session_cls, \
|
||||
patch("nanobot.cli.commands.FileHistory"), \
|
||||
patch("pathlib.Path.home") as mock_home:
|
||||
|
||||
mock_home.return_value = MagicMock()
|
||||
@@ -55,17 +55,17 @@ def test_init_prompt_session_creates_session():
|
||||
commands._init_prompt_session()
|
||||
|
||||
assert commands._PROMPT_SESSION is not None
|
||||
MockSession.assert_called_once()
|
||||
_, kwargs = MockSession.call_args
|
||||
# Buffer is multiline-capable so Shift+Enter / Alt+Enter can insert
|
||||
# newlines; Enter-to-submit is restored via custom key bindings.
|
||||
mock_session_cls.assert_called_once()
|
||||
_, kwargs = mock_session_cls.call_args
|
||||
# Buffer is multiline-capable so Alt+Enter can insert newlines;
|
||||
# Enter-to-submit is restored via custom key bindings.
|
||||
assert kwargs["multiline"] is True
|
||||
assert kwargs["enable_open_in_editor"] is False
|
||||
assert kwargs.get("key_bindings") is not None
|
||||
|
||||
|
||||
def test_cli_key_bindings_enter_submits_and_alt_enter_newlines():
|
||||
"""Enter submits the buffer; Alt+Enter and Shift+Enter insert a newline."""
|
||||
"""Enter submits the buffer; Alt+Enter inserts a newline."""
|
||||
from prompt_toolkit.keys import Keys
|
||||
|
||||
kb = commands._build_cli_key_bindings()
|
||||
@@ -88,14 +88,6 @@ def test_cli_key_bindings_enter_submits_and_alt_enter_newlines():
|
||||
alt_enter.call(MagicMock(current_buffer=buf))
|
||||
buf.insert_text.assert_called_once_with("\n")
|
||||
|
||||
# ControlF3 (synthetic carrier for the Shift+Enter CSI-u sequence) -> newline.
|
||||
# Not ControlJ: that's also the literal LF byte some terminals (e.g. WSL)
|
||||
# send for a plain Enter keypress, so binding it would break submit there.
|
||||
shift_enter = bound[(Keys.ControlF3.value,)]
|
||||
buf = MagicMock()
|
||||
shift_enter.call(MagicMock(current_buffer=buf))
|
||||
buf.insert_text.assert_called_once_with("\n")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_raw_lf_enter_still_submits_like_wsl_terminals():
|
||||
@@ -118,28 +110,6 @@ async def test_raw_lf_enter_still_submits_like_wsl_terminals():
|
||||
assert result == "hello"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_xterm_modifyotherkeys_shift_enter_inserts_newline():
|
||||
""""\\x1b[27;2;13~" (the older xterm modifyOtherKeys/rxvt encoding of
|
||||
Shift+Enter) is registered by prompt_toolkit *by default* as plain
|
||||
Enter/submit, so a `setdefault()`-based override would silently no-op
|
||||
against it. This must actually insert a newline like the kitty CSI-u
|
||||
encoding does, which only a real PromptSession/parser can confirm.
|
||||
"""
|
||||
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[27;2;13~bar\r")
|
||||
result = await session.prompt_async("> ")
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user