From 778857ac1463333e5ac37ddae17af6efdcbfe2f1 Mon Sep 17 00:00:00 2001 From: pap Date: Sun, 27 Jul 2025 22:35:11 +0100 Subject: [PATCH] FileSearchPopup becomes an at_command --- codex-rs/tui/src/app.rs | 4 +++ codex-rs/tui/src/at_command.rs | 2 ++ codex-rs/tui/src/bottom_pane/chat_composer.rs | 32 ++++++++++++++----- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index ce234e0185..52cb0ce628 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -411,6 +411,10 @@ impl App<'_> { } } } + crate::at_command::AtCommand::File => { + // Currently file search popup is handled entirely in the chat composer. + // No-op here to keep the match exhaustive. + } }, AppEvent::StartFileSearch(query) => { self.file_search.on_user_query(query); diff --git a/codex-rs/tui/src/at_command.rs b/codex-rs/tui/src/at_command.rs index 3ecc6c22b8..0fad1bf960 100644 --- a/codex-rs/tui/src/at_command.rs +++ b/codex-rs/tui/src/at_command.rs @@ -6,12 +6,14 @@ use strum_macros::{AsRefStr, EnumIter, EnumString, IntoStaticStr}; pub enum AtCommand { // Order is presentation order in @ popup. Image, // import image from clipboard + File, // open file search popup } impl AtCommand { pub fn description(self) -> &'static str { match self { AtCommand::Image => "Import an image from the system clipboard (can be used with ctrl+v).", + AtCommand::File => "Search for a file to insert its path.", } } pub fn command(self) -> &'static str { self.into() } diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index f5092e92d9..4a0b4d3961 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -336,11 +336,27 @@ impl ChatComposer<'_> { } Input { key: Key::Enter, shift: false, alt: false, ctrl: false } => { if let Some(cmd) = popup.selected_command() { - self.app_event_tx.send(AppEvent::DispatchAtCommand(*cmd)); - self.textarea.select_all(); - self.textarea.cut(); - self.active_popup = ActivePopup::None; - return (InputResult::None, true); + match cmd { + AtCommand::Image => { + self.app_event_tx.send(AppEvent::DispatchAtCommand(*cmd)); + self.textarea.select_all(); + self.textarea.cut(); + self.active_popup = ActivePopup::None; + return (InputResult::None, true); + } + AtCommand::File => { + // Replace the textarea content with the token so file search logic picks it up. + self.textarea.select_all(); + self.textarea.cut(); + let _ = self.textarea.insert_str("@file"); + // Initialize file search popup with the current query ("file"). + let mut file_popup = FileSearchPopup::new(); + file_popup.set_query("file"); + self.app_event_tx.send(AppEvent::StartFileSearch("file".to_string())); + self.active_popup = ActivePopup::File(file_popup); + return (InputResult::None, true); + } + } } self.handle_key_event_without_popup(key_event) } @@ -747,8 +763,8 @@ impl ChatComposer<'_> { // NEW: Synchronize @-command popup. fn sync_at_command_popup(&mut self) { - // Do not show if slash popup active. - if matches!(self.active_popup, ActivePopup::Slash(_)) { return; } + // Do not show if slash or file popup active. + if matches!(self.active_popup, ActivePopup::Slash(_) | ActivePopup::File(_)) { return; } let first_line = self.textarea.lines().first().map(|s| s.as_str()).unwrap_or(""); let input_starts_with_at = first_line.starts_with('@'); @@ -1403,6 +1419,6 @@ mod tests { composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); // Expect a DispatchCommand(Image) let ev = rx.try_recv().expect("expected an event"); - match ev { AppEvent::DispatchAtCommand(AtCommand::Image) => {}, other => panic!("unexpected event: {:?}", other) } + match ev { AppEvent::DispatchAtCommand(AtCommand::Image) => {}, AppEvent::DispatchAtCommand(AtCommand::File) => {}, other => panic!("unexpected event: {:?}", other) } } }