From a050e26c0310372caa2fc1e9370c2578e92be78a Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Sun, 22 Mar 2026 16:48:05 -0700 Subject: [PATCH] Add Ctrl+C quit handling to REPL - Add _quit_requested flag to TerminalDisplay - Add request_quit() method to TerminalDisplay - Handle 'ctrl_c' key in REPL input loops in both pipeline_runner.py and main.py - When Ctrl+C is pressed, request_quit() is called which sets the flag - The main loop checks is_quit_requested() and raises KeyboardInterrupt --- engine/app/main.py | 8 +++++++- engine/app/pipeline_runner.py | 8 +++++++- engine/display/backends/terminal.py | 9 +++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/engine/app/main.py b/engine/app/main.py index 4e65759..a67af0f 100644 --- a/engine/app/main.py +++ b/engine/app/main.py @@ -550,7 +550,13 @@ def run_pipeline_mode_direct(): keys = display.get_input_keys(timeout=0.0) for key in keys: - if key == "return": + if key == "ctrl_c": + # Request quit when Ctrl+C is pressed + if hasattr(display, "request_quit"): + display.request_quit() + else: + raise KeyboardInterrupt() + elif key == "return": # Get command string before processing cmd_str = repl_effect.state.current_command if cmd_str: diff --git a/engine/app/pipeline_runner.py b/engine/app/pipeline_runner.py index cf512ed..43e1c8b 100644 --- a/engine/app/pipeline_runner.py +++ b/engine/app/pipeline_runner.py @@ -986,7 +986,13 @@ def run_pipeline_mode(preset_name: str = "demo", graph_config: str | None = None keys = display.get_input_keys(timeout=0.0) for key in keys: - if key == "return": + if key == "ctrl_c": + # Request quit when Ctrl+C is pressed + if hasattr(display, "request_quit"): + display.request_quit() + else: + raise KeyboardInterrupt() + elif key == "return": # Get command string before processing cmd_str = repl_effect.state.current_command if cmd_str: diff --git a/engine/display/backends/terminal.py b/engine/display/backends/terminal.py index b8cb152..0575ee2 100644 --- a/engine/display/backends/terminal.py +++ b/engine/display/backends/terminal.py @@ -28,6 +28,7 @@ class TerminalDisplay: self._cached_dimensions: tuple[int, int] | None = None self._raw_mode_enabled: bool = False self._original_termios: list = [] + self._quit_requested: bool = False def init(self, width: int, height: int, reuse: bool = False) -> None: """Initialize display with dimensions. @@ -163,11 +164,15 @@ class TerminalDisplay: def is_quit_requested(self) -> bool: """Check if quit was requested (optional protocol method).""" - return False + return self._quit_requested def clear_quit_request(self) -> None: """Clear quit request (optional protocol method).""" - pass + self._quit_requested = False + + def request_quit(self) -> None: + """Request quit (e.g., when Ctrl+C is pressed).""" + self._quit_requested = True def set_raw_mode(self, enable: bool = True) -> None: """Enable/disable raw terminal mode for input capture.