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

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions codex-rs/tui/src/bottom_pane/chat_composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2846,11 +2846,11 @@ impl ChatComposer {
return None;
}
if self.reject_slash_command_if_unavailable(cmd) {
self.stage_slash_command_history();
self.stage_slash_command_history(cmd);
self.record_pending_slash_command_history();
return Some(InputResult::None);
}
self.stage_slash_command_history();
self.stage_slash_command_history(cmd);
self.textarea.set_text_clearing_elements("");
self.is_bash_mode = false;
Some(InputResult::Command(cmd))
Expand Down Expand Up @@ -2878,12 +2878,12 @@ impl ChatComposer {
return None;
}
if self.reject_slash_command_if_unavailable(cmd) {
self.stage_slash_command_history();
self.stage_slash_command_history(cmd);
self.record_pending_slash_command_history();
return Some(InputResult::None);
}

self.stage_slash_command_history();
self.stage_slash_command_history(cmd);

let mut args_elements =
Self::slash_command_args_elements(rest, rest_offset, &self.textarea.text_elements());
Expand Down Expand Up @@ -2959,7 +2959,10 @@ impl ChatComposer {
/// Staging snapshots the rich composer state before the textarea is cleared. `ChatWidget`
/// commits the staged entry after dispatch so command recall follows the submitted text, not
/// the command outcome.
fn stage_slash_command_history(&mut self) {
fn stage_slash_command_history(&mut self, cmd: SlashCommand) {
if cmd == SlashCommand::Clear {
return;
}
self.stage_slash_command_history_text(self.textarea.text().trim().to_string());
}

Expand All @@ -2968,6 +2971,9 @@ impl ChatComposer {
/// Popup filtering text can be partial, so recording the selected command avoids recalling
/// `/di` after the user actually accepted `/diff`.
fn stage_selected_slash_command_history(&mut self, cmd: SlashCommand) {
if cmd == SlashCommand::Clear {
return;
}
self.stage_slash_command_history_text(format!("/{}", cmd.command()));
}

Expand Down
7 changes: 6 additions & 1 deletion codex-rs/tui/src/bottom_pane/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use std::collections::VecDeque;
use std::path::PathBuf;

use crate::app::app_server_requests::ResolvedAppServerRequest;
use crate::app_command::AppCommand;
use crate::app_event::AppEvent;
use crate::app_event::ConnectorsSnapshot;
use crate::app_event_sender::AppEventSender;
use crate::bottom_pane::pending_input_preview::PendingInputPreview;
Expand Down Expand Up @@ -776,7 +778,10 @@ impl BottomPane {
}

pub(crate) fn clear_composer_for_ctrl_c(&mut self) {
self.composer.clear_for_ctrl_c();
if let Some(text) = self.composer.clear_for_ctrl_c() {
self.app_event_tx
.send(AppEvent::CodexOp(AppCommand::add_to_history(text)));
}
self.request_redraw();
}

Expand Down
28 changes: 28 additions & 0 deletions codex-rs/tui/src/chatwidget/tests/slash_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1594,6 +1594,34 @@ async fn slash_clear_requests_ui_clear_when_idle() {
assert_matches!(rx.try_recv(), Ok(AppEvent::ClearUi));
}

#[tokio::test]
async fn slash_clear_after_ctrl_c_keeps_stashed_draft_recallable() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
submit_composer_text(&mut chat, "ok");

let stashed_draft = "explain why history recall lost this draft";

chat.bottom_pane
.set_composer_text(stashed_draft.to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL));
assert_eq!(chat.bottom_pane.composer_text(), "");
assert_matches!(
rx.try_recv(),
Ok(AppEvent::CodexOp(Op::AddToHistory { text })) if text == stashed_draft
);

chat.bottom_pane
.set_composer_text("/clear".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));

assert_matches!(rx.try_recv(), Ok(AppEvent::ClearUi));
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
assert_eq!(chat.bottom_pane.composer_text(), stashed_draft);

chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
assert_eq!(chat.bottom_pane.composer_text(), "ok");
}

#[tokio::test]
async fn slash_clear_is_disabled_while_task_running() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
Expand Down
Loading