mirror of
https://github.com/openai/codex.git
synced 2026-09-13 11:47:17 +00:00
cr: move empty query logic into main state machine
This commit is contained in:
@@ -487,6 +487,9 @@ impl App<'_> {
|
||||
AppEvent::StartFileSearch(query) => {
|
||||
self.file_search.on_user_query(query);
|
||||
}
|
||||
AppEvent::StopFileSearch => {
|
||||
self.file_search.reset();
|
||||
}
|
||||
AppEvent::FileSearchResult { query, matches } => {
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.apply_file_search_result(query, matches);
|
||||
|
||||
@@ -51,6 +51,10 @@ pub(crate) enum AppEvent {
|
||||
matches: Vec<FileMatch>,
|
||||
},
|
||||
|
||||
/// Stop or reset any pending/active file search in the manager.
|
||||
/// Used when the user cancels the `@` popup or completes a mention.
|
||||
StopFileSearch,
|
||||
|
||||
/// Result of computing a `/diff` command.
|
||||
DiffResult(String),
|
||||
|
||||
|
||||
@@ -328,6 +328,7 @@ impl ChatComposer {
|
||||
self.dismissed_file_popup_token = Some(tok.to_string());
|
||||
}
|
||||
self.active_popup = ActivePopup::None;
|
||||
self.app_event_tx.send(AppEvent::StopFileSearch);
|
||||
(InputResult::None, true)
|
||||
}
|
||||
KeyEvent {
|
||||
@@ -343,6 +344,7 @@ impl ChatComposer {
|
||||
// Drop popup borrow before using self mutably again.
|
||||
self.insert_selected_path(&sel_path);
|
||||
self.active_popup = ActivePopup::None;
|
||||
self.app_event_tx.send(AppEvent::StopFileSearch);
|
||||
return (InputResult::None, true);
|
||||
}
|
||||
(InputResult::None, false)
|
||||
@@ -594,6 +596,8 @@ impl ChatComposer {
|
||||
None => {
|
||||
self.active_popup = ActivePopup::None;
|
||||
self.dismissed_file_popup_token = None;
|
||||
// No active @token under cursor; stop any search.
|
||||
self.app_event_tx.send(AppEvent::StopFileSearch);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ pub(crate) struct FileSearchManager {
|
||||
|
||||
struct SearchState {
|
||||
/// Latest query typed by user (updated every keystroke).
|
||||
latest_query: String,
|
||||
latest_query: Option<String>,
|
||||
|
||||
/// true if a search is currently scheduled.
|
||||
is_search_scheduled: bool,
|
||||
@@ -69,7 +69,7 @@ impl FileSearchManager {
|
||||
pub fn new(search_dir: PathBuf, tx: AppEventSender) -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(SearchState {
|
||||
latest_query: String::new(),
|
||||
latest_query: None,
|
||||
is_search_scheduled: false,
|
||||
active_search: None,
|
||||
})),
|
||||
@@ -83,31 +83,13 @@ impl FileSearchManager {
|
||||
{
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let mut st = self.state.lock().unwrap();
|
||||
// If the query is empty, build quick suggestions immediately and return.
|
||||
// Do this BEFORE the unchanged short-circuit so the initial empty
|
||||
// query ("@") still yields results even though latest_query starts empty.
|
||||
if query.is_empty() {
|
||||
let search_dir = self.search_dir.clone();
|
||||
let tx = self.app_tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let max_total = MAX_FILE_SEARCH_RESULTS.get();
|
||||
let matches = collect_top_level_suggestions(&search_dir, max_total);
|
||||
tx.send(AppEvent::FileSearchResult {
|
||||
query: String::new(),
|
||||
matches,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if query == st.latest_query {
|
||||
// No change, nothing to do.
|
||||
// If the query hasn't changed, nothing to do.
|
||||
if st.latest_query.as_deref() == Some(query.as_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update latest query.
|
||||
st.latest_query.clear();
|
||||
st.latest_query.push_str(&query);
|
||||
st.latest_query = Some(query.clone());
|
||||
|
||||
// If there is an in-flight search that is definitely obsolete,
|
||||
// cancel it now.
|
||||
@@ -148,30 +130,59 @@ impl FileSearchManager {
|
||||
|
||||
// The debounce timer has expired, so start a search using the
|
||||
// latest query.
|
||||
let cancellation_token = Arc::new(AtomicBool::new(false));
|
||||
let token = cancellation_token.clone();
|
||||
let query = {
|
||||
let latest_query_opt = {
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let mut st = state.lock().unwrap();
|
||||
let query = st.latest_query.clone();
|
||||
let q = st.latest_query.clone();
|
||||
st.is_search_scheduled = false;
|
||||
st.active_search = Some(ActiveSearch {
|
||||
query: query.clone(),
|
||||
cancellation_token: token,
|
||||
});
|
||||
query
|
||||
q
|
||||
};
|
||||
|
||||
FileSearchManager::spawn_file_search(
|
||||
query,
|
||||
search_dir,
|
||||
tx_clone,
|
||||
cancellation_token,
|
||||
state,
|
||||
);
|
||||
let Some(query) = latest_query_opt else {
|
||||
return;
|
||||
};
|
||||
|
||||
if query.is_empty() {
|
||||
// Quick, synchronous top-level suggestions for empty query.
|
||||
let max_total = MAX_FILE_SEARCH_RESULTS.get();
|
||||
let matches = collect_top_level_suggestions(&search_dir, max_total);
|
||||
tx_clone.send(AppEvent::FileSearchResult { query, matches });
|
||||
} else {
|
||||
// Full-text file search for non-empty query.
|
||||
let cancellation_token = Arc::new(AtomicBool::new(false));
|
||||
let token = cancellation_token.clone();
|
||||
{
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let mut st = state.lock().unwrap();
|
||||
st.active_search = Some(ActiveSearch {
|
||||
query: query.clone(),
|
||||
cancellation_token: token,
|
||||
});
|
||||
}
|
||||
|
||||
FileSearchManager::spawn_file_search(
|
||||
query,
|
||||
search_dir,
|
||||
tx_clone,
|
||||
cancellation_token,
|
||||
state,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Reset any scheduled or active search and clear the last query.
|
||||
pub fn reset(&self) {
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let mut st = self.state.lock().unwrap();
|
||||
st.latest_query = None;
|
||||
st.is_search_scheduled = false;
|
||||
if let Some(active) = &st.active_search {
|
||||
active.cancellation_token.store(true, Ordering::Relaxed);
|
||||
}
|
||||
st.active_search = None;
|
||||
}
|
||||
|
||||
fn spawn_file_search(
|
||||
query: String,
|
||||
search_dir: PathBuf,
|
||||
|
||||
Reference in New Issue
Block a user