From 97d00808e155cdffa92496946e18a101f05d20d0 Mon Sep 17 00:00:00 2001 From: Allen Date: Thu, 19 Mar 2026 16:15:01 +0100 Subject: [PATCH] fix: The input prompt box supports selection using the up and down arrow keys (#76) * Add the ability to read text from the system clipboard * The input prompt box supports selection using the up and down arrow keys. --- EvoScientist/cli/clipboard.py | 10 ++++++++-- EvoScientist/cli/tui_interactive.py | 23 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/EvoScientist/cli/clipboard.py b/EvoScientist/cli/clipboard.py index 3ab8219..19cedfd 100644 --- a/EvoScientist/cli/clipboard.py +++ b/EvoScientist/cli/clipboard.py @@ -59,11 +59,17 @@ def _paste_native() -> str | None: result = subprocess.run( ["powershell", "-command", "Get-Clipboard"], capture_output=True, - text=True, timeout=2, ) if result.returncode == 0: - return result.stdout.rstrip("\r\n") + # Try UTF-8 first, fall back to system encoding + for encoding in ("utf-8", "gbk", "cp936"): + try: + return result.stdout.decode(encoding).rstrip("\r\n") + except UnicodeDecodeError: + continue + # Last resort: decode with errors ignored + return result.stdout.decode("utf-8", errors="ignore").rstrip("\r\n") except (subprocess.TimeoutExpired, FileNotFoundError, OSError): pass else: diff --git a/EvoScientist/cli/tui_interactive.py b/EvoScientist/cli/tui_interactive.py index 2ef1035..d3466ff 100644 --- a/EvoScientist/cli/tui_interactive.py +++ b/EvoScientist/cli/tui_interactive.py @@ -1604,6 +1604,13 @@ def run_textual_interactive( def action_edit_queued(self) -> None: """Pop the last queued message back into input for editing.""" + # Handle completion list selection (up key) + comp_widget = self.query_one("#completions", Static) + if comp_widget.display and self._comp_items: + self._comp_index = (self._comp_index - 1) % len(self._comp_items) + self._render_completions() + return + # Skip if an ApprovalWidget, AskUserWidget, ThreadPickerWidget, or SkillBrowserWidget has focus focused = self.focused if focused is not None: @@ -1638,6 +1645,13 @@ def run_textual_interactive( def action_down_delegate(self) -> None: """Delegate down key to focused interactive widget.""" + # Handle completion list selection (down key) + comp_widget = self.query_one("#completions", Static) + if comp_widget.display and self._comp_items: + self._comp_index = (self._comp_index + 1) % len(self._comp_items) + self._render_completions() + return + focused = self.focused if focused is not None: from .widgets.approval_widget import ApprovalWidget @@ -1690,23 +1704,26 @@ def run_textual_interactive( event.prevent_default() event.stop() self._comp_index = (self._comp_index + 1) % len(self._comp_items) - self._apply_selected_completion() + self._render_completions() elif event.key == "up": event.prevent_default() event.stop() self._comp_index = (self._comp_index - 1) % len(self._comp_items) - self._apply_selected_completion() + self._render_completions() elif event.key == "enter" and self._comp_index >= 0: event.prevent_default() event.stop() + self._apply_selected_completion() self._hide_completions() def _apply_selected_completion(self) -> None: + """Apply the currently selected completion to the input field.""" + if self._comp_index < 0 or self._comp_index >= len(self._comp_items): + return selected_cmd = self._comp_items[self._comp_index][0] prompt = self.query_one("#prompt", Input) prompt.value = selected_cmd + " " prompt.cursor_position = len(prompt.value) - self._render_completions() def _hide_completions(self) -> None: self._comp_items = []