diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 387450c904..a6065c283b 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -741,6 +741,14 @@ impl ChatComposer { if let Some(sel) = popup.selected_item() { match sel { CommandItem::Builtin(cmd) => { + // Preserve inline `/review ...` args when the popup stays open. + if cmd == SlashCommand::Review + && let Some((_, rest)) = parse_slash_name(expanded_first_line) + && !rest.is_empty() + { + self.textarea.set_text(""); + return (InputResult::CommandWithArgs(cmd, rest.to_string()), true); + } if self.should_clear_after_bare_slash_command(cmd) { self.textarea.set_text(""); } @@ -4593,6 +4601,39 @@ mod tests { ); } + #[test] + fn slash_popup_enter_preserves_review_args() { + use crossterm::event::KeyCode; + use crossterm::event::KeyEvent; + use crossterm::event::KeyModifiers; + + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + + composer.set_text_content("/review please summarize".to_string()); + composer.textarea.set_cursor(composer.textarea.text().len()); + composer.sync_popups(); + + let (result, _needs_redraw) = + composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + match result { + InputResult::CommandWithArgs(cmd, args) => { + assert_eq!(cmd, SlashCommand::Review); + assert_eq!(args, "please summarize"); + } + other => panic!("expected CommandWithArgs, got: {other:?}"), + } + assert!(composer.textarea.is_empty(), "composer should be cleared"); + } + #[test] fn apply_external_edit_rebuilds_text_and_attachments() { let (tx, _rx) = unbounded_channel::(); diff --git a/codex-rs/tui/tooltips.txt b/codex-rs/tui/tooltips.txt index 0fdb0ca73b..85d6fc279f 100644 --- a/codex-rs/tui/tooltips.txt +++ b/codex-rs/tui/tooltips.txt @@ -4,6 +4,7 @@ Use /feedback to send logs to the maintainers when something looks off. Switch models or reasoning effort quickly with /model. You can run any shell command from Codex using `!` (e.g. `!ls`) Type / to open the command popup; Tab autocompletes slash commands and saved prompts. +Use /review to run a focused review (e.g. `/review check error handling`). You can define your own `/` commands with custom prompts. More info: https://developers.openai.com/codex/guides/slash-commands#create-your-own-slash-commands-with-custom-prompts When the composer is empty, press Esc to step back and edit your last message; Enter confirms. Press Tab to queue a message instead of sending it immediately; Enter always sends immediately. diff --git a/codex-rs/tui2/src/bottom_pane/chat_composer.rs b/codex-rs/tui2/src/bottom_pane/chat_composer.rs index f32a4327b3..43a37e704d 100644 --- a/codex-rs/tui2/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui2/src/bottom_pane/chat_composer.rs @@ -675,6 +675,14 @@ impl ChatComposer { if let Some(sel) = popup.selected_item() { match sel { CommandItem::Builtin(cmd) => { + // Preserve inline `/review ...` args when the popup stays open. + if cmd == SlashCommand::Review + && let Some((_, rest)) = parse_slash_name(expanded_first_line) + && !rest.is_empty() + { + self.textarea.set_text(""); + return (InputResult::CommandWithArgs(cmd, rest.to_string()), true); + } if self.should_clear_after_bare_slash_command(cmd) { self.textarea.set_text(""); } @@ -4453,6 +4461,40 @@ mod tests { ); } + #[test] + fn slash_popup_enter_preserves_review_args() { + use crossterm::event::KeyCode; + use crossterm::event::KeyEvent; + use crossterm::event::KeyModifiers; + use tokio::sync::mpsc::unbounded_channel; + + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + + composer.set_text_content("/review please summarize".to_string()); + composer.textarea.set_cursor(composer.textarea.text().len()); + composer.sync_popups(); + + let (result, _needs_redraw) = + composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + match result { + InputResult::CommandWithArgs(cmd, args) => { + assert_eq!(cmd, SlashCommand::Review); + assert_eq!(args, "please summarize"); + } + other => panic!("expected CommandWithArgs, got: {other:?}"), + } + assert!(composer.textarea.is_empty(), "composer should be cleared"); + } + #[test] fn input_disabled_ignores_keypresses_and_hides_cursor() { use crossterm::event::KeyCode; diff --git a/codex-rs/tui2/tooltips.txt b/codex-rs/tui2/tooltips.txt index 0fdb0ca73b..85d6fc279f 100644 --- a/codex-rs/tui2/tooltips.txt +++ b/codex-rs/tui2/tooltips.txt @@ -4,6 +4,7 @@ Use /feedback to send logs to the maintainers when something looks off. Switch models or reasoning effort quickly with /model. You can run any shell command from Codex using `!` (e.g. `!ls`) Type / to open the command popup; Tab autocompletes slash commands and saved prompts. +Use /review to run a focused review (e.g. `/review check error handling`). You can define your own `/` commands with custom prompts. More info: https://developers.openai.com/codex/guides/slash-commands#create-your-own-slash-commands-with-custom-prompts When the composer is empty, press Esc to step back and edit your last message; Enter confirms. Press Tab to queue a message instead of sending it immediately; Enter always sends immediately.