-
Notifications
You must be signed in to change notification settings - Fork 4k
fix(tui): add multiline input and cross-platform clipboard support #4451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…denhq#4423) Replace single-line Input widget with TextArea in chat REPL so Shift+Enter inserts newlines and multiline paste works correctly. Add Windows clipboard support (clip.exe) and xsel fallback for Linux.
jose-blockchain
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this: multiline input and cross-platform clipboard are good improvements to have.
I found two issues that would prevent the Enter-to-submit mechanism from working at all, and a couple of smaller items. Details in the inline comments.
Blocking:
event.shiftdoes not exist on TextualKeyevents — will raiseAttributeErrorTextArea._on_keycallsevent.stop()on Enter, so the event never bubbles toChatRepl.on_key— submission never triggers, Enter just inserts a newline
Suggested approach: subclass TextArea and override _on_key to intercept Enter before the base class consumes it.
Happy to re-review once those are addressed.
| focused = self.app.focused | ||
| if not isinstance(focused, TextArea): | ||
| return | ||
| if event.key == "enter" and not event.shift: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key events have no .shift attribute. The code not event.shift will raise AttributeError. The correct approach is to check event.key == "enter" (Shift+Enter arrives as event.key == "shift+enter", so it naturally wouldn't match).
| async def on_input_submitted(self, message: Input.Submitted) -> None: | ||
| """Handle input submission — either start new execution or inject input.""" | ||
| user_input = message.value.strip() | ||
| async def on_key(self, event) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TextArea._on_key handles "enter" by:
- Inserting
"\n"into the document - Calling
event.stop()— which stops event propagation entirely
This means ChatRepl.on_key will never receive the Enter key event at all. The Enter key is consumed by TextArea before it can bubble. The entire submission mechanism is broken — pressing Enter will just insert newlines, and submission will never trigger.
The fix requires subclassing TextArea and overriding _on_key to intercept Enter before the base class processes it.
| chat_input = chat_repl.query_one("#chat-input", Input) | ||
| chat_input.value = "/sessions" | ||
| chat_input = chat_repl.query_one("#chat-input", TextArea) | ||
| chat_input.text = "/sessions" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit (pre-existing): Setting chat_input.text = "/sessions" only places text in the widget — it does not trigger submission. The comment says # Trigger submission but nothing actually submits. This was already broken in the original Input.value code, so not a regression from this PR, but worth fixing while you're here. You could call await chat_repl._submit_input("/sessions") directly instead.
Description
Fixes #4423
The TUI chat input uses a single-line
Inputwidget that drops multilinepastes and has no newline support. Copy-to-clipboard also fails on some
platforms.
Type of Change
Related Issues
Fixes #4423
Changes Made
Inputwidget withTextAreain chat REPLclip.exe) in_copy_to_clipboardxselfallback for Linux whenxclipis not installedapp.pyto match new widgetTesting
cd core && pytest tests/)cd core && ruff check .)Manual verification:
Checklist