When using `@` to mention files, it should default to showing a list of all files rather than 'no matches'

Resolved 💬 5 comments Opened Aug 29, 2025 by 0xdevalias Closed Nov 27, 2025
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

What feature would you like to see?

Currently when we use @ to mention a file, it defaults to showing 'no matches':

▌@
▌no matches

But sometimes I don't remember the name of the file I want to mention straight away, and so need to separately check the folder externally, or prompt with an ls type command or similar.

It would be nice if typing @ defaulted to showing the list of files that I could then select from, rather than the less useful 'no matches'.

A current hacky workaround is to type @ followed by a vowel, and know that at least that way I will see some selection of files that I could pick from, but this feels like a bit of a hack, and there is still a 1 in 5'ish chance I wouldn't see the file I wanted still anyway.

Are you interested in implementing this feature?

Unfortunately I don't have the capacity to currently, nor the familiarity with rust.

Additional information

See Also

View original on GitHub ↗

5 Comments

0xdevalias · 12 months ago

Within sync_file_search_popup, this part looks related to empty query:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/tui/src/bottom_pane/chat_composer.rs#L1188-L1204

And this is FileSearchPopup's set_empty_prompt that it calls:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/tui/src/bottom_pane/file_search_popup.rs#L57-L66

FileSearchPopup's set_query is also used when non-blank:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/tui/src/bottom_pane/file_search_popup.rs#L37-L55

The FileSearchManager's on_user_query function seems to relate to when the user edits the @ token:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/tui/src/file_search.rs#L81-L82

And eventually calls FileSearchManager::spawn_file_search:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/tui/src/file_search.rs#L148-L154

Which seems to call file_search::run:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/tui/src/file_search.rs#L158-L177

Which calls create_pattern:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/file-search/src/lib.rs#L124-L133

And then uses the same library that ripgrep uses:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/file-search/src/lib.rs#L151-L153

create_pattern seems to be a basic wrapper/helper:

https://github.com/openai/codex/blob/3f8184034f12e2c03013273505259999acd03423/codex-rs/file-search/src/lib.rs#L357-L364

If we were just using rg directly we could do something like the following to list the files:

⇒ rg --files

AGENTS.md
foo.json

So something similar to that as the default when using a raw @ would be useful.

---

One potential (naive) suggestion I iterated on with GitHub Copilot + GPT-5:

A minimal approach is feasible: (1) always dispatch a search event even when the @ token is empty, and (2) short‑circuit spawn_file_search so that an empty query returns a cached (or lazily-built) shallow file list instead of invoking the fuzzy search. This keeps UI + event flow unchanged and avoids adding new event variants. Summary of minimal changes: 1. In ChatComposer::sync_file_search_popup remove the if !query.is_empty() guard so an empty query still triggers AppEvent::StartFileSearch(""). 2. In FileSearchManager::spawn_file_search, detect query.is_empty(). If empty: - Build (or reuse) a cached default list (shallow WalkDir up to a small depth; cap length at MAX_FILE_SEARCH_RESULTS). - Send AppEvent::FileSearchResult { query: "", matches }. - Clear active_search (so later real queries aren’t blocked). - Return without calling the expensive fuzzy file_search::run. 3. Add a small cache container inside SearchState (e.g. default_file_list: Option<Vec<FileMatch>>) so repeated empty queries are instant. Trade‑offs / caveats: - If the repo changes (new files) you won’t see them until a fresh session (same as current prompts note) unless you add an invalidation condition (optional). - Highlight indices: for an empty query we can leave indices: None (makes UI show plain names). - Threading: Must manually clear active_search because we skip the normal completion code path. Below is a concrete minimal patch (illustrative; adjust paths/imports if needed). It keeps logic tight and avoids introducing new events or popup methods. <details><summary><code>codex-rs/tui/src/file_search.rs</code></summary> <p> ``diff name=codex-rs/tui/src/file_search.rs diff --git a/codex-rs/tui/src/file_search.rs b/codex-rs/tui/src/file_search.rs @@ struct SearchState { latest_query: String, is_search_scheduled: bool, active_search: Option<ActiveSearch>, + /// Cached list of default files for the empty query. + default_file_list: Option<Vec<FileMatch>>, } impl FileSearchManager { pub fn new(search_dir: PathBuf, tx: AppEventSender) -> Self { Self { state: Arc::new(Mutex::new(SearchState { latest_query: String::new(), is_search_scheduled: false, active_search: None, + default_file_list: None, })), search_dir, app_tx: tx, } } @@ fn spawn_file_search( query: String, search_dir: PathBuf, tx: AppEventSender, cancellation_token: Arc<AtomicBool>, search_state: Arc<Mutex<SearchState>>, ) { - let compute_indices = true; - std::thread::spawn(move || { + // Fast-path for empty query: return (cached) shallow file list + if query.is_empty() { + // Build or reuse cache synchronously (cheap shallow walk). + let matches_opt = { + #[expect(clippy::unwrap_used)] + let mut st = search_state.lock().unwrap(); + if let Some(cached) = &st.default_file_list { + Some(cached.clone()) + } else { + drop(st); // release lock during IO + let mut collected: Vec<FileMatch> = Vec::new(); + // Shallow enumeration: depth 2 is usually enough to be useful. + let max_depth = 2; + for entry in walkdir::WalkDir::new(&search_dir) + .max_depth(max_depth) + .into_iter() + .filter_map(|e| e.ok()) + { + if collected.len() >= MAX_FILE_SEARCH_RESULTS { + break; + } + if entry.file_type().is_file() { + if let Ok(rel) = entry.path().strip_prefix(&search_dir) { + let path_str = rel.to_string_lossy().to_string(); + collected.push(FileMatch { + path: path_str, + indices: None, // no highlights for empty query + }); + } + } + } + #[expect(clippy::unwrap_used)] + { + let mut st2 = search_state.lock().unwrap(); + st2.default_file_list = Some(collected.clone()); + // Clear active_search if this empty-query occupied it. + if let Some(active) = &st2.active_search + && active.query.is_empty() + && Arc::ptr_eq(&active.cancellation_token, &cancellation_token) + { + st2.active_search = None; + } + } + Some(collected) + } + }; + if let Some(matches) = matches_opt { + tx.send(AppEvent::FileSearchResult { query, matches }); + } + return; + } + + let compute_indices = true; + std::thread::spawn(move || { let matches = file_search::run( &query, MAX_FILE_SEARCH_RESULTS, &search_dir, Vec::new(), NUM_FILE_SEARCH_THREADS, cancellation_token.clone(), compute_indices, ) .map(|res| res.matches) .unwrap_or_default(); @@ if let Some(active_search) = &st.active_search && Arc::ptr_eq(&active_search.cancellation_token, &cancellation_token) { st.active_search = None; } } }); } } ` </p> </details> <details><summary><code>codex-rs/tui/src/bottom_pane/chat_composer.rs</code></summary> <p> `diff name=codex-rs/tui/src/bottom_pane/chat_composer.rs diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ - if !query.is_empty() { - self.app_event_tx - .send(AppEvent::StartFileSearch(query.clone())); - } + // Always request a search; empty query will yield cached/default file list fast-path. + self.app_event_tx + .send(AppEvent::StartFileSearch(query.clone())); ` </p> </details> Notes: - You’ll need walkdir in Cargo.toml for the shallow enumeration if not already present (it’s likely already there; if not, add walkdir = "2"). - If FileMatch is from codex_file_search and doesn’t have a public constructor or fields, you may need a helper constructor (adjust accordingly). - This doesn’t change popup rendering logic: empty query now immediately shows rows, because set_matches will run with query == "" (you currently only apply results if query == pending_query; ensure pending_query for empty query is set by set_query("") instead of set_empty_prompt(). So you should also replace set_empty_prompt() call paths with set_query("") so that pending_query matches. Minimal additional tweak: - In sync_file_search_popup replace popup.set_empty_prompt() with popup.set_query("") so empty results can land. Optional tiny tweak (not in patch above): Replace two occurrences of popup.set_empty_prompt(); with popup.set_query(""); in sync_file_search_popup`.
alexx-ftw · 12 months ago

I can't even get the @ to show up using AltGr+2...

0xdevalias · 11 months ago

Potentially / tangentially related:

etraut-openai contributor · 9 months ago

Thanks for the feature request. This has received only one upvote in three months, so I'm going to close it.

alexx-ftw · 9 months ago

@etraut-openai anyway it seems to have been solved in a related PR. Have a good one