From a48372ce5dbfdef94fdb91a173df93119a5aee90 Mon Sep 17 00:00:00 2001 From: ae Date: Mon, 11 Aug 2025 14:15:41 -0700 Subject: [PATCH] feat: add a /mention slash command (#2114) - To help people discover @mentions. - Command just places a @ in the composer. - #2115 then improves the behavior of @mentions with empty queries. --- codex-rs/tui/src/app.rs | 5 ++ codex-rs/tui/src/bottom_pane/chat_composer.rs | 46 +++++++++++++++++++ codex-rs/tui/src/bottom_pane/mod.rs | 5 ++ codex-rs/tui/src/chatwidget.rs | 4 ++ codex-rs/tui/src/slash_command.rs | 2 + 5 files changed, 62 insertions(+) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index a97948f3..4a0adb8d 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -366,6 +366,11 @@ impl App<'_> { widget.add_diff_output(text); } } + SlashCommand::Mention => { + if let AppState::Chat { widget } = &mut self.app_state { + widget.insert_str("@"); + } + } SlashCommand::Status => { if let AppState::Chat { widget } = &mut self.app_state { widget.add_status_output(); diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 09ff8b7a..78506f57 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -198,6 +198,12 @@ impl ChatComposer { self.set_has_focus(has_focus); } + pub(crate) fn insert_str(&mut self, text: &str) { + self.textarea.insert_str(text); + self.sync_command_popup(); + self.sync_file_search_popup(); + } + /// Handle a key event coming from the main UI. pub fn handle_key_event(&mut self, key_event: KeyEvent) -> (InputResult, bool) { let result = match &mut self.active_popup { @@ -1078,6 +1084,46 @@ mod tests { } } + #[test] + fn slash_mention_dispatches_command_and_inserts_at() { + use crossterm::event::KeyCode; + use crossterm::event::KeyEvent; + use crossterm::event::KeyModifiers; + use std::sync::mpsc::TryRecvError; + + let (tx, rx) = std::sync::mpsc::channel(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new(true, sender, false); + + for ch in ['/', 'm', 'e', 'n', 't', 'i', 'o', 'n'] { + let _ = composer.handle_key_event(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE)); + } + + let (result, _needs_redraw) = + composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + match result { + InputResult::None => {} + InputResult::Submitted(text) => { + panic!("expected command dispatch, but composer submitted literal text: {text}") + } + } + assert!(composer.textarea.is_empty(), "composer should be cleared"); + + match rx.try_recv() { + Ok(AppEvent::DispatchCommand(cmd)) => { + assert_eq!(cmd.command(), "mention"); + composer.insert_str("@"); + } + Ok(_other) => panic!("unexpected app event"), + Err(TryRecvError::Empty) => panic!("expected a DispatchCommand event for '/mention'"), + Err(TryRecvError::Disconnected) => { + panic!("app event channel disconnected") + } + } + assert_eq!(composer.textarea.text(), "@"); + } + #[test] fn test_multiple_pastes_submission() { use crossterm::event::KeyCode; diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index 0c861047..4606f9b8 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -196,6 +196,11 @@ impl BottomPane<'_> { } } + pub(crate) fn insert_str(&mut self, text: &str) { + self.composer.insert_str(text); + self.request_redraw(); + } + /// Update the status indicator text. Prefer replacing the composer with /// the StatusIndicatorView so the input pane shows a single-line status /// like: `▌ Working waiting for model`. diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index d03a51bd..173ab64a 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -676,6 +676,10 @@ impl ChatWidget<'_> { self.submit_user_message(text.into()); } + pub(crate) fn insert_str(&mut self, text: &str) { + self.bottom_pane.insert_str(text); + } + pub(crate) fn token_usage(&self) -> &TokenUsage { &self.total_token_usage } diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index e58ab852..0de1f6fa 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -16,6 +16,7 @@ pub enum SlashCommand { Init, Compact, Diff, + Mention, Status, Prompts, Logout, @@ -33,6 +34,7 @@ impl SlashCommand { SlashCommand::Compact => "summarize conversation to prevent hitting the context limit", SlashCommand::Quit => "exit Codex", SlashCommand::Diff => "show git diff (including untracked files)", + SlashCommand::Mention => "mention a file", SlashCommand::Status => "show current session configuration and token usage", SlashCommand::Prompts => "show example prompts", SlashCommand::Logout => "log out of Codex",