From db3102e3d321156b6b8364dbb638e3fe625d4f4a Mon Sep 17 00:00:00 2001 From: tjazerzen Date: Fri, 13 Feb 2026 11:09:13 +0100 Subject: [PATCH 1/3] Refactor TUI exit keybinding: use Ctrl+D instead of double Ctrl+C - Changed Ctrl+C to only copy selected text (does nothing if no selection) - Added Ctrl+D for immediate exit without confirmation - Removed double Ctrl+C to quit logic and quit-pending state - Removed ESC to cancel quit logic - Updated footer and success messages to reflect new keybindings - Cleaned up unused CSS class for quit-pending state Co-Authored-By: Claude Sonnet 4.5 --- tui/components.py | 20 ++------------------ tui/plain2code_tui.py | 41 +++++------------------------------------ tui/styles.css | 5 ----- tui/widget_helpers.py | 2 +- 4 files changed, 8 insertions(+), 60 deletions(-) diff --git a/tui/components.py b/tui/components.py index 5fefaa1..33d7f38 100644 --- a/tui/components.py +++ b/tui/components.py @@ -13,33 +13,17 @@ class CustomFooter(Horizontal): """A custom footer with keyboard shortcuts and render ID.""" - NORMAL_TEXT = "ctrl+c: quit/copy/ * ctrl+l: toggle logs" - QUIT_PENDING_TEXT = "press ctrl+c again to quit * esc: cancel" + FOOTER_TEXT = "ctrl+c: copy * ctrl+d: quit * ctrl+l: toggle logs" def __init__(self, render_id: str = "", **kwargs): super().__init__(**kwargs) self.render_id = render_id - self._footer_text_widget: Optional[Static] = None def compose(self): - self._footer_text_widget = Static(self.NORMAL_TEXT, classes="custom-footer-text") - yield self._footer_text_widget + yield Static(self.FOOTER_TEXT, classes="custom-footer-text") if self.render_id: yield Static(f"render id: {self.render_id}", classes="custom-footer-render-id") - def update_quit_state(self, quit_pending: bool) -> None: - """Update footer text based on quit-pending state.""" - if self._footer_text_widget is None: - return - if quit_pending: - self._footer_text_widget.update(self.QUIT_PENDING_TEXT) - self._footer_text_widget.remove_class("custom-footer-text") - self._footer_text_widget.add_class("custom-footer-quit-pending") - else: - self._footer_text_widget.update(self.NORMAL_TEXT) - self._footer_text_widget.remove_class("custom-footer-quit-pending") - self._footer_text_widget.add_class("custom-footer-text") - class ScriptOutputType(str, Enum): UNIT_TEST_OUTPUT_TEXT = "Unit tests output: " diff --git a/tui/plain2code_tui.py b/tui/plain2code_tui.py index b4e822a..d477849 100644 --- a/tui/plain2code_tui.py +++ b/tui/plain2code_tui.py @@ -55,8 +55,8 @@ class Plain2CodeTUI(App): """A Textual TUI for plain2code.""" BINDINGS = [ - Binding("ctrl+c", "smart_quit", "Copy/Quit", show=False), - Binding("escape", "cancel_quit", "Cancel Quit", show=False), + Binding("ctrl+c", "copy_selection", "Copy", show=False), + Binding("ctrl+d", "quit", "Quit", show=False), ("ctrl+l", "toggle_logs", "Toggle Logs"), ] @@ -80,7 +80,6 @@ def __init__( self.conformance_tests_script: Optional[str] = conformance_tests_script self.prepare_environment_script: Optional[str] = prepare_environment_script self.state_machine_version = state_machine_version - self._quit_pending = False # Initialize state handlers self._state_handlers: dict[str, StateHandler] = { @@ -302,47 +301,17 @@ def ensure_exit(): # daemon=True ensures this thread dies with the process if it exits before the timer fires threading.Thread(target=ensure_exit, daemon=True).start() - @property - def quit_pending(self) -> bool: - """Whether a quit confirmation is pending.""" - return self._quit_pending + async def action_copy_selection(self) -> None: + """Handle ctrl+c: copy selected text if any. - async def action_smart_quit(self) -> None: - """Handle ctrl+c: copy selected text if any, otherwise quit. - - Copy-first, quit-second design: - If text is selected -> copy it to clipboard - - If no text is selected -> enter quit-pending state - - If already in quit-pending state -> actually quit - - ESC cancels the quit confirmation + - If no text is selected -> do nothing """ selected_text = self.screen.get_selected_text() if selected_text: self.copy_to_clipboard(selected_text) self.screen.clear_selection() self.notify("Copied to clipboard", timeout=2) - return - - if self._quit_pending: - self.action_quit() - return - - self._quit_pending = True - self._refresh_footer() - - def action_cancel_quit(self) -> None: - """Cancel the quit confirmation when ESC is pressed.""" - if self._quit_pending: - self._quit_pending = False - self._refresh_footer() - - def _refresh_footer(self) -> None: - """Refresh the CustomFooter widget to reflect current quit-pending state.""" - try: - footer = self.screen.query_one(CustomFooter) - footer.update_quit_state(self._quit_pending) - except NoMatches: - pass def action_quit(self) -> None: """Quit the application immediately. diff --git a/tui/styles.css b/tui/styles.css index 15a5f87..5824faa 100644 --- a/tui/styles.css +++ b/tui/styles.css @@ -399,11 +399,6 @@ CustomFooter { color: #888; } -.custom-footer-quit-pending { - width: 1fr; - color: #E0FF6E; -} - .custom-footer-render-id { width: auto; color: #888; diff --git a/tui/widget_helpers.py b/tui/widget_helpers.py index 4e41c1c..57799b6 100644 --- a/tui/widget_helpers.py +++ b/tui/widget_helpers.py @@ -105,7 +105,7 @@ def display_success_message(tui, rendered_code_path: str): rendered_code_path: The path to the rendered code """ - message = f"[#79FC96]✓ Rendering finished![/#79FC96] [#888888](ctrl+c to exit)[/#888888]\n[#888888]Generated code: {rendered_code_path}[/#888888] " + message = f"[#79FC96]✓ Rendering finished![/#79FC96] [#888888](ctrl+d to exit)[/#888888]\n[#888888]Generated code: {rendered_code_path}[/#888888] " widget: Static = tui.query_one(f"#{TUIComponents.RENDER_STATUS_WIDGET.value}", Static) widget.update(message) From 2b90e77f95b98104e1aaeea4377e113a9ef7065f Mon Sep 17 00:00:00 2001 From: tjazerzen Date: Fri, 13 Feb 2026 12:13:05 +0100 Subject: [PATCH 2/3] Add Enter key to exit TUI after render completes Update footer to show render-finished keybindings and allow exiting with Enter once rendering is done. Co-Authored-By: Claude Opus 4.6 --- tui/components.py | 10 +++++++++- tui/plain2code_tui.py | 13 +++++++++++++ tui/widget_helpers.py | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tui/components.py b/tui/components.py index 33d7f38..9597bf1 100644 --- a/tui/components.py +++ b/tui/components.py @@ -14,16 +14,24 @@ class CustomFooter(Horizontal): """A custom footer with keyboard shortcuts and render ID.""" FOOTER_TEXT = "ctrl+c: copy * ctrl+d: quit * ctrl+l: toggle logs" + RENDER_FINISHED_TEXT = "enter: exit * ctrl+c: copy * ctrl+l: toggle logs" def __init__(self, render_id: str = "", **kwargs): super().__init__(**kwargs) self.render_id = render_id + self._footer_text_widget: Optional[Static] = None def compose(self): - yield Static(self.FOOTER_TEXT, classes="custom-footer-text") + self._footer_text_widget = Static(self.FOOTER_TEXT, classes="custom-footer-text") + yield self._footer_text_widget if self.render_id: yield Static(f"render id: {self.render_id}", classes="custom-footer-render-id") + def show_render_finished(self) -> None: + """Update footer text to show render-finished keybindings.""" + if self._footer_text_widget is not None: + self._footer_text_widget.update(self.RENDER_FINISHED_TEXT) + class ScriptOutputType(str, Enum): UNIT_TEST_OUTPUT_TEXT = "Unit tests output: " diff --git a/tui/plain2code_tui.py b/tui/plain2code_tui.py index d477849..e8f669e 100644 --- a/tui/plain2code_tui.py +++ b/tui/plain2code_tui.py @@ -57,6 +57,7 @@ class Plain2CodeTUI(App): BINDINGS = [ Binding("ctrl+c", "copy_selection", "Copy", show=False), Binding("ctrl+d", "quit", "Quit", show=False), + Binding("enter", "enter_exit", "Exit", show=False), ("ctrl+l", "toggle_logs", "Toggle Logs"), ] @@ -80,6 +81,7 @@ def __init__( self.conformance_tests_script: Optional[str] = conformance_tests_script self.prepare_environment_script: Optional[str] = prepare_environment_script self.state_machine_version = state_machine_version + self._render_finished = False # Initialize state handlers self._state_handlers: dict[str, StateHandler] = { @@ -278,6 +280,12 @@ def _handle_frid_state( def on_render_completed(self, event: RenderCompleted): """Handle successful render completion.""" self._render_success_handler.handle(event.rendered_code_path) + self._render_finished = True + try: + footer = self.screen.query_one(CustomFooter) + footer.show_render_finished() + except NoMatches: + pass def on_render_failed(self, event: RenderFailed): """Handle render failure.""" @@ -313,6 +321,11 @@ async def action_copy_selection(self) -> None: self.screen.clear_selection() self.notify("Copied to clipboard", timeout=2) + def action_enter_exit(self) -> None: + """Handle enter: exit the TUI only after rendering has finished.""" + if self._render_finished: + self.action_quit() + def action_quit(self) -> None: """Quit the application immediately. diff --git a/tui/widget_helpers.py b/tui/widget_helpers.py index 57799b6..6fab10e 100644 --- a/tui/widget_helpers.py +++ b/tui/widget_helpers.py @@ -105,7 +105,7 @@ def display_success_message(tui, rendered_code_path: str): rendered_code_path: The path to the rendered code """ - message = f"[#79FC96]✓ Rendering finished![/#79FC96] [#888888](ctrl+d to exit)[/#888888]\n[#888888]Generated code: {rendered_code_path}[/#888888] " + message = f"[#79FC96]✓ Rendering finished![/#79FC96] [#888888](enter to exit)[/#888888]\n[#888888]Generated code: {rendered_code_path}[/#888888] " widget: Static = tui.query_one(f"#{TUIComponents.RENDER_STATUS_WIDGET.value}", Static) widget.update(message) From e631861017684899e84a36ba766b074dd0e1bfd0 Mon Sep 17 00:00:00 2001 From: tjazerzen Date: Fri, 13 Feb 2026 11:09:13 +0100 Subject: [PATCH 3/3] Refactor TUI exit keybinding: use Ctrl+D instead of double Ctrl+C - Changed Ctrl+C to only copy selected text (does nothing if no selection) - Added Ctrl+D for immediate exit without confirmation - Removed double Ctrl+C to quit logic and quit-pending state - Removed ESC to cancel quit logic - Updated footer and success messages to reflect new keybindings - Cleaned up unused CSS class for quit-pending state Co-Authored-By: Claude Sonnet 4.5 --- tui/components.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tui/components.py b/tui/components.py index 9597bf1..31441ba 100644 --- a/tui/components.py +++ b/tui/components.py @@ -19,7 +19,6 @@ class CustomFooter(Horizontal): def __init__(self, render_id: str = "", **kwargs): super().__init__(**kwargs) self.render_id = render_id - self._footer_text_widget: Optional[Static] = None def compose(self): self._footer_text_widget = Static(self.FOOTER_TEXT, classes="custom-footer-text")