feat: add Shift+Enter as newline insert keybinding#229
feat: add Shift+Enter as newline insert keybinding#229Arkevius wants to merge 1 commit intompfaffenberger:mainfrom
Conversation
Add Shift+Enter (s-enter) as an intuitive alternative to Ctrl+J for inserting newlines in the prompt. Works in modern terminals that support the CSI u / kitty keyboard protocol (kitty, WezTerm, iTerm2, Windows Terminal). Silently ignored on older terminals via try/except guard. - Add s-enter key binding following the established c-enter pattern - Update help text to show 'Shift+Enter or Ctrl+J' (clarifies alternatives) - Add test_shift_enter_newline matching existing test conventions
📝 WalkthroughWalkthroughAdds support for Shift+Enter as an alternative keyboard shortcut to insert newlines in the CLI interface alongside the existing Ctrl+J binding. Changes include updating help documentation, implementing the new key binding in the prompt toolkit layer, and adding corresponding test coverage. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@code_puppy/command_line/prompt_toolkit_completion.py`:
- Around line 645-654: The Shift+Enter keybinding registration using
bindings.add("s-enter", ...) is silently swallowed by the surrounding try/except
because prompt_toolkit 3.0.52 does not recognize "s-enter"; update the code by
either removing the failing handler or replacing it with a terminal-aware
implementation: detect terminal capabilities and register a valid key sequence
(or CSI/kitty protocol sequence) that prompt_toolkit accepts instead of
"s-enter", or skip registration and update help/tests; locate the handler added
via bindings.add("s-enter", eager=True) and the anonymous function def _(event)
that inserts "\n" and change its registration logic accordingly so the binding
is only installed when a supported key name/sequence is validated.
In `@tests/command_line/test_prompt_toolkit_coverage.py`:
- Around line 988-995: The test test_shift_enter_newline currently skips if the
Shift+Enter binding is missing; change it to fail instead by asserting the
handler exists and then invoking it: locate the use of
_find_handler(captured_bindings, "s-enter") in test_shift_enter_newline, replace
the conditional no-op with an explicit assertion that handler is not None (or
raise an AssertionError) so the test fails when the binding isn't registered,
then keep calling handler(event) and asserting
event.app.current_buffer.insert_text.assert_called_with("\n").
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e0d7b39d-d113-4f4d-b47c-74a9bd44b7c2
📒 Files selected for processing (3)
code_puppy/cli_runner.pycode_puppy/command_line/prompt_toolkit_completion.pytests/command_line/test_prompt_toolkit_coverage.py
| # Shift+Enter for newline (terminal-dependent — works in modern terminals | ||
| # like kitty, WezTerm, iTerm2, and Windows Terminal that support CSI u / | ||
| # kitty keyboard protocol; silently ignored on older terminals). | ||
| try: | ||
|
|
||
| @bindings.add("s-enter", eager=True) | ||
| def _(event): | ||
| event.app.current_buffer.insert_text("\n") | ||
| except Exception: | ||
| pass |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== prompt_toolkit 3.0.52 supported key names =="
curl -fsSL https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/3.0.52/src/prompt_toolkit/keys.py \
| rg -n '"s-enter"|"c-enter"|Shift|Enter = "c-m"|BackTab'
echo
echo "== prompt_toolkit key validation path =="
curl -fsSL https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/3.0.52/src/prompt_toolkit/key_binding/key_bindings.py \
| rg -n '_parse_key|Invalid key|Keys\(key\)|len\(key\) != 1'Repository: mpfaffenberger/code_puppy
Length of output: 1795
Update or remove Shift+Enter handler that silently fails to register.
prompt_toolkit 3.0.52 does not define s-enter as a valid key name. When KeyBindings.add("s-enter", ...) is called, _parse_key() rejects it with ValueError (unrecognized multi-character keys raise Invalid key), and the surrounding try/except silently swallows the error. This means Shift+Enter is never installed, even though the CLI help and test imply it works.
Either remove this handler if Shift+Enter support is not available, or replace it with a terminal-aware implementation that actually works. Update any help text or tests that describe this feature as available.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@code_puppy/command_line/prompt_toolkit_completion.py` around lines 645 - 654,
The Shift+Enter keybinding registration using bindings.add("s-enter", ...) is
silently swallowed by the surrounding try/except because prompt_toolkit 3.0.52
does not recognize "s-enter"; update the code by either removing the failing
handler or replacing it with a terminal-aware implementation: detect terminal
capabilities and register a valid key sequence (or CSI/kitty protocol sequence)
that prompt_toolkit accepts instead of "s-enter", or skip registration and
update help/tests; locate the handler added via bindings.add("s-enter",
eager=True) and the anonymous function def _(event) that inserts "\n" and change
its registration logic accordingly so the binding is only installed when a
supported key name/sequence is validated.
| def test_shift_enter_newline(self, captured_bindings): | ||
| """Test shift-enter inserts newline (terminal-dependent).""" | ||
| handler = self._find_handler(captured_bindings, "s-enter") | ||
| if handler is not None: | ||
| event = MagicMock() | ||
| handler(event) | ||
| event.app.current_buffer.insert_text.assert_called_with("\n") | ||
|
|
There was a problem hiding this comment.
Make this test fail when the binding is missing.
if handler is not None turns this into a no-op, so CI stays green even when Shift+Enter is never registered.
Proposed fix
def test_shift_enter_newline(self, captured_bindings):
"""Test shift-enter inserts newline (terminal-dependent)."""
handler = self._find_handler(captured_bindings, "s-enter")
- if handler is not None:
- event = MagicMock()
- handler(event)
- event.app.current_buffer.insert_text.assert_called_with("\n")
+ assert handler is not None
+ event = MagicMock()
+ handler(event)
+ event.app.current_buffer.insert_text.assert_called_with("\n")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def test_shift_enter_newline(self, captured_bindings): | |
| """Test shift-enter inserts newline (terminal-dependent).""" | |
| handler = self._find_handler(captured_bindings, "s-enter") | |
| if handler is not None: | |
| event = MagicMock() | |
| handler(event) | |
| event.app.current_buffer.insert_text.assert_called_with("\n") | |
| def test_shift_enter_newline(self, captured_bindings): | |
| """Test shift-enter inserts newline (terminal-dependent).""" | |
| handler = self._find_handler(captured_bindings, "s-enter") | |
| assert handler is not None | |
| event = MagicMock() | |
| handler(event) | |
| event.app.current_buffer.insert_text.assert_called_with("\n") |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/command_line/test_prompt_toolkit_coverage.py` around lines 988 - 995,
The test test_shift_enter_newline currently skips if the Shift+Enter binding is
missing; change it to fail instead by asserting the handler exists and then
invoking it: locate the use of _find_handler(captured_bindings, "s-enter") in
test_shift_enter_newline, replace the conditional no-op with an explicit
assertion that handler is not None (or raise an AssertionError) so the test
fails when the binding isn't registered, then keep calling handler(event) and
asserting event.app.current_buffer.insert_text.assert_called_with("\n").
|
@Arkevius Thanks for the PR. This is what I very much wanted too. |
What
Adds Shift+Enter (
s-enter) as a new keybinding for inserting newlines in the interactive prompt, alongside the existing Ctrl+J and Ctrl+Enter options.Why
Shift+Enter is the most intuitive newline shortcut for most users — it's the standard in Slack, Discord, Teams, ChatGPT, and virtually every modern chat UI. Adding it reduces the learning curve for new users who would otherwise need to discover Ctrl+J or toggle multiline mode.
Changes
3 files changed, 20 insertions(+), 1 deletion(-)
code_puppy/command_line/prompt_toolkit_completion.pys-enterkey binding following the identical pattern used byc-enter(lines 637–643):try/exceptguard witheager=True, since Shift+Enter is terminal-dependent (works in modern terminals supporting CSI u / kitty keyboard protocol — kitty, WezTerm, iTerm2, Windows Terminal; silently ignored on older terminals).code_puppy/cli_runner.py"newline: Ctrl+J"→"newline: Shift+Enter or Ctrl+J"— uses "or" (not comma) to clearly indicate these are alternatives, not a sequence.tests/command_line/test_prompt_toolkit_coverage.pytest_shift_enter_newlinemirroring the existingtest_ctrl_enter_newlinepattern exactly: finds the handler via_find_handler, guards withif handler is not None(since the binding is terminal-dependent), and assertsinsert_text("\n").Note on
command_line/modificationPer
AGENTS.md, new functionality should prefer the plugin/callback system over direct edits tocode_puppy/command_line/. However, there is no hook for key binding registration —callbacks.pyhas no phase for input bindings, prompt session configuration, or TUI customization. TheKeyBindingsobject is created locally insideget_input_with_combined_completion()and passed directly toPromptSession(), with no extension point for plugins.This change adds 11 lines to an existing block of key binding definitions (Ctrl+J, Ctrl+Enter, multiline Enter) in the only location where those bindings can live. It follows the established pattern exactly.
Summary by CodeRabbit
New Features
Tests