From a575effbb0bb687c6c2f88574484f577ded7a38e Mon Sep 17 00:00:00 2001 From: ae Date: Wed, 6 Aug 2025 14:56:34 -0700 Subject: [PATCH] feat: interrupt running task on ctrl-z (#1880) - Arguably a bugfix as previously CTRL-Z didn't do anything. - Only in TUI mode for now. This may make sense in other modes... to be researched. - The TUI runs the terminal in raw mode and the signals arrive as key events, so we handle CTRL-Z as a key event just like CTRL-C. - Not adding UI for it as a composer redesign is coming, and we can just add it then. - We should follow with CTRL-Z a second time doing the native terminal action. --- codex-rs/tui/src/app.rs | 10 ++++++++++ codex-rs/tui/src/chatwidget.rs | 33 +++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 47e20287..2ac550b0 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -238,6 +238,16 @@ impl App<'_> { } } } + KeyEvent { + code: KeyCode::Char('z'), + modifiers: crossterm::event::KeyModifiers::CONTROL, + kind: KeyEventKind::Press, + .. + } => { + if let AppState::Chat { widget } = &mut self.app_state { + widget.on_ctrl_z(); + } + } KeyEvent { code: KeyCode::Char('d'), modifiers: crossterm::event::KeyModifiers::CONTROL, diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 69f1600c..128e1ae8 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -110,6 +110,22 @@ fn create_initial_user_message(text: String, image_paths: Vec) -> Optio } impl ChatWidget<'_> { + fn interrupt_running_task(&mut self) { + if self.bottom_pane.is_task_running() { + self.active_history_cell = None; + self.bottom_pane.clear_ctrl_c_quit_hint(); + self.submit_op(Op::Interrupt); + self.bottom_pane.set_task_running(false); + self.bottom_pane.clear_live_ring(); + self.live_builder = RowBuilder::new(self.live_builder.width()); + self.current_stream = None; + self.stream_header_emitted = false; + self.answer_buffer.clear(); + self.reasoning_buffer.clear(); + self.content_buffer.clear(); + self.request_redraw(); + } + } fn layout_areas(&self, area: Rect) -> [Rect; 2] { Layout::vertical([ Constraint::Max( @@ -569,18 +585,7 @@ impl ChatWidget<'_> { CancellationEvent::Ignored => {} } if self.bottom_pane.is_task_running() { - self.active_history_cell = None; - self.bottom_pane.clear_ctrl_c_quit_hint(); - self.submit_op(Op::Interrupt); - self.bottom_pane.set_task_running(false); - self.bottom_pane.clear_live_ring(); - self.live_builder = RowBuilder::new(self.live_builder.width()); - self.current_stream = None; - self.stream_header_emitted = false; - self.answer_buffer.clear(); - self.reasoning_buffer.clear(); - self.content_buffer.clear(); - self.request_redraw(); + self.interrupt_running_task(); CancellationEvent::Ignored } else if self.bottom_pane.ctrl_c_quit_hint_visible() { self.submit_op(Op::Shutdown); @@ -591,6 +596,10 @@ impl ChatWidget<'_> { } } + pub(crate) fn on_ctrl_z(&mut self) { + self.interrupt_running_task(); + } + pub(crate) fn composer_is_empty(&self) -> bool { self.bottom_pane.composer_is_empty() }