From 32899cf2a773d4521469bd1b3d3a58f481cc7777 Mon Sep 17 00:00:00 2001 From: haseeb-heaven <11544739+haseeb-heaven@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:52:03 +0000 Subject: [PATCH] Improve terminal UI handling of Ctrl-C and prompt choices --- .jules/palette.md | 3 +++ libs/terminal_ui.py | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .jules/palette.md diff --git a/.jules/palette.md b/.jules/palette.md new file mode 100644 index 0000000..f748ae1 --- /dev/null +++ b/.jules/palette.md @@ -0,0 +1,3 @@ +## 2024-07-05 - Explicit TUI Cancellation and Prompt Options +**Learning:** When using raw terminal input, standard interrupt bytes like `\x03` (Ctrl-C) are intercepted and must be manually handled by explicitly raising `KeyboardInterrupt`. Furthermore, `Esc` (`\x1b`) should not be advertised or mapped as a cancel action because reading it in raw mode followed by a blocking read for ANSI sequences hangs the application. When wrapping `rich.prompt.Prompt.ask()`, available choices must be explicitly formatted into the prompt string with escaped brackets `\[]` to ensure they display without breaking custom case-insensitivity logic or triggering markup warnings. +**Action:** Explicitly catch `\x03` to raise `KeyboardInterrupt`, explicitly append `(Ctrl-C to cancel)` to TUI footers, and manually format prompt choices with escaped brackets in `Prompt.ask()` strings. diff --git a/libs/terminal_ui.py b/libs/terminal_ui.py index 47b8b3b..3977965 100644 --- a/libs/terminal_ui.py +++ b/libs/terminal_ui.py @@ -19,6 +19,8 @@ def _read_key(self): if os.name == 'nt': import msvcrt key = msvcrt.getwch() + if key == '\x03': + raise KeyboardInterrupt('Selection cancelled by user.') if key in ('\x00', '\xe0'): extended = msvcrt.getwch() mapping = {'H': 'up', 'P': 'down', 'K': 'left', 'M': 'right'} @@ -37,6 +39,8 @@ def _read_key(self): try: tty.setraw(fd) key = sys.stdin.read(1) + if key == '\x03': + raise KeyboardInterrupt('Selection cancelled by user.') if key == '\x1b': next_chars = sys.stdin.read(2) mapping = {'[A': 'up', '[B': 'down', '[D': 'left', '[C': 'right'} @@ -68,6 +72,8 @@ def _render_selector(self, title, options, selected_index, help_text, default): table.add_row(marker, label, style=style) footer = help_text or 'Use Up/Down arrows and Enter to select.' + if 'Ctrl-C' not in footer: + footer += ' (Ctrl-C to cancel)' self.console.print(Panel.fit(footer, title='Interpreter TUI', border_style='green')) self.console.print(f"[bold cyan]{title}[/bold cyan]") self.console.print(table) @@ -76,7 +82,7 @@ def _render_selector(self, title, options, selected_index, help_text, default): def _select_option(self, title, options, default, help_text=None): if not sys.stdin.isatty(): default_choice = default if default in options else options[0] - answer = Prompt.ask(f"{title}", default=default_choice).strip() + answer = Prompt.ask(f"{title} \\[{'|'.join(options)}]", default=default_choice).strip() if answer in options: return answer for option in options: