Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion codex-rs/tui/src/bottom_pane/bottom_pane_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub(crate) trait BottomPaneView {

/// Handle Ctrl-C while this view is active.
fn on_ctrl_c(&mut self, _pane: &mut BottomPane) -> CancellationEvent {
CancellationEvent::Ignored
CancellationEvent::NotHandled
}

/// Return the desired height of the view.
Expand Down
13 changes: 12 additions & 1 deletion codex-rs/tui/src/bottom_pane/chat_composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub(crate) struct ChatComposer {
has_focus: bool,
attached_images: Vec<AttachedImage>,
placeholder_text: String,
is_task_running: bool,
// Non-bracketed paste burst tracker.
paste_burst: PasteBurst,
// When true, disables paste-burst logic and inserts characters immediately.
Expand Down Expand Up @@ -119,6 +120,7 @@ impl ChatComposer {
has_focus: has_input_focus,
attached_images: Vec::new(),
placeholder_text,
is_task_running: false,
paste_burst: PasteBurst::default(),
disable_paste_burst: false,
custom_prompts: Vec::new(),
Expand Down Expand Up @@ -1205,6 +1207,10 @@ impl ChatComposer {
self.has_focus = has_focus;
}

pub fn set_task_running(&mut self, running: bool) {
self.is_task_running = running;
}

pub(crate) fn set_esc_backtrack_hint(&mut self, show: bool) {
self.esc_backtrack_hint = show;
}
Expand All @@ -1229,11 +1235,16 @@ impl WidgetRef for ChatComposer {
ActivePopup::None => {
let bottom_line_rect = popup_rect;
let mut hint: Vec<Span<'static>> = if self.ctrl_c_quit_hint {
let ctrl_c_followup = if self.is_task_running {
" to interrupt"
} else {
" to quit"
};
vec![
" ".into(),
key_hint::ctrl('C'),
" again".into(),
" to quit".into(),
ctrl_c_followup.into(),
]
} else {
let newline_hint_key = if self.use_shift_enter_hint {
Expand Down
32 changes: 21 additions & 11 deletions codex-rs/tui/src/bottom_pane/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ mod textarea;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CancellationEvent {
Ignored,
Handled,
NotHandled,
}

pub(crate) use chat_composer::ChatComposer;
Expand Down Expand Up @@ -195,7 +195,15 @@ impl BottomPane {
pub(crate) fn on_ctrl_c(&mut self) -> CancellationEvent {
let mut view = match self.active_view.take() {
Some(view) => view,
None => return CancellationEvent::Ignored,
None => {
return if self.composer_is_empty() {
CancellationEvent::NotHandled
} else {
self.set_composer_text(String::new());
self.show_ctrl_c_quit_hint();
CancellationEvent::Handled
};
}
};

let event = view.on_ctrl_c(self);
Expand All @@ -208,7 +216,7 @@ impl BottomPane {
}
self.show_ctrl_c_quit_hint();
}
CancellationEvent::Ignored => {
CancellationEvent::NotHandled => {
self.active_view = Some(view);
}
}
Expand Down Expand Up @@ -267,6 +275,7 @@ impl BottomPane {
}
}

#[cfg(test)]
pub(crate) fn ctrl_c_quit_hint_visible(&self) -> bool {
self.ctrl_c_quit_hint
}
Expand All @@ -289,6 +298,7 @@ impl BottomPane {

pub fn set_task_running(&mut self, running: bool) {
self.is_task_running = running;
self.composer.set_task_running(running);

if running {
if self.status.is_none() {
Expand Down Expand Up @@ -504,7 +514,7 @@ mod tests {
let tx = AppEventSender::new(tx_raw);
let mut pane = BottomPane::new(BottomPaneParams {
app_event_tx: tx,
frame_requester: crate::tui::FrameRequester::test_dummy(),
frame_requester: FrameRequester::test_dummy(),
has_input_focus: true,
enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
Expand All @@ -513,7 +523,7 @@ mod tests {
pane.push_approval_request(exec_request());
assert_eq!(CancellationEvent::Handled, pane.on_ctrl_c());
assert!(pane.ctrl_c_quit_hint_visible());
assert_eq!(CancellationEvent::Ignored, pane.on_ctrl_c());
assert_eq!(CancellationEvent::NotHandled, pane.on_ctrl_c());
}

// live ring removed; related tests deleted.
Expand All @@ -524,7 +534,7 @@ mod tests {
let tx = AppEventSender::new(tx_raw);
let mut pane = BottomPane::new(BottomPaneParams {
app_event_tx: tx,
frame_requester: crate::tui::FrameRequester::test_dummy(),
frame_requester: FrameRequester::test_dummy(),
has_input_focus: true,
enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
Expand Down Expand Up @@ -555,7 +565,7 @@ mod tests {
let tx = AppEventSender::new(tx_raw);
let mut pane = BottomPane::new(BottomPaneParams {
app_event_tx: tx.clone(),
frame_requester: crate::tui::FrameRequester::test_dummy(),
frame_requester: FrameRequester::test_dummy(),
has_input_focus: true,
enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
Expand Down Expand Up @@ -583,7 +593,7 @@ mod tests {

// Render and ensure the top row includes the Working header and a composer line below.
// Give the animation thread a moment to tick.
std::thread::sleep(std::time::Duration::from_millis(120));
std::thread::sleep(Duration::from_millis(120));
let area = Rect::new(0, 0, 40, 6);
let mut buf = Buffer::empty(area);
(&pane).render_ref(area, &mut buf);
Expand Down Expand Up @@ -623,7 +633,7 @@ mod tests {
let tx = AppEventSender::new(tx_raw);
let mut pane = BottomPane::new(BottomPaneParams {
app_event_tx: tx,
frame_requester: crate::tui::FrameRequester::test_dummy(),
frame_requester: FrameRequester::test_dummy(),
has_input_focus: true,
enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
Expand Down Expand Up @@ -654,7 +664,7 @@ mod tests {
let tx = AppEventSender::new(tx_raw);
let mut pane = BottomPane::new(BottomPaneParams {
app_event_tx: tx,
frame_requester: crate::tui::FrameRequester::test_dummy(),
frame_requester: FrameRequester::test_dummy(),
has_input_focus: true,
enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
Expand Down Expand Up @@ -705,7 +715,7 @@ mod tests {
let tx = AppEventSender::new(tx_raw);
let mut pane = BottomPane::new(BottomPaneParams {
app_event_tx: tx,
frame_requester: crate::tui::FrameRequester::test_dummy(),
frame_requester: FrameRequester::test_dummy(),
has_input_focus: true,
enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
Expand Down
18 changes: 10 additions & 8 deletions codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1288,15 +1288,17 @@ impl ChatWidget {

/// Handle Ctrl-C key press.
fn on_ctrl_c(&mut self) {
if self.bottom_pane.on_ctrl_c() == CancellationEvent::Ignored {
if self.bottom_pane.is_task_running() {
self.submit_op(Op::Interrupt);
} else if self.bottom_pane.ctrl_c_quit_hint_visible() {
self.submit_op(Op::Shutdown);
} else {
self.bottom_pane.show_ctrl_c_quit_hint();
}
if self.bottom_pane.on_ctrl_c() == CancellationEvent::Handled {
return;
}

if self.bottom_pane.is_task_running() {
self.bottom_pane.show_ctrl_c_quit_hint();
self.submit_op(Op::Interrupt);
return;
}

self.submit_op(Op::Shutdown);
}

pub(crate) fn composer_is_empty(&self) -> bool {
Expand Down
Loading