From 4ec2d04ad948f118b420bf00f74873ae058e043d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:21:02 +0100 Subject: [PATCH] search: Fix sort order not being maintained in presence of open buffers (#44135) In project search UI code we were seeing an issue where "Go to next match" would act up and behave weirdly. It would not wrap at times. Stuff would be weird, yo. It turned out that match ranges reported by core project search were sometimes out of sync with the state of the multi-buffer. As in, the sort order of `search::ProjectSearch::match_ranges` would not match up with multi-buffer's sort order. This is ~because multi-buffers maintain their own sort order. What happened within project search is that we were skipping straight from stage 1 (filtering paths) to stage 3 via an internal channel and in the process we've dropped the channel used to maintain result sorting. This made is so that, given 2 files to scan: - project/file1.rs <- not open, has to go through stage2 (FS scan) - project/file2.rs <- open, goes straight from stage1 (path filtering) to stage3 (finding all matches) We would report matches for project/file2.rs first, because we would notice that there's an existing language::Buffer for it. However, we should wait for project/file1.rs status to be reported first before we kick off project/file2.rs The fix is to use the sorting channel instead of an internal one, as that keeps the sorting worker "in the loop" about the state of the world. Closes #43672 Co-authored-by: Smit Barmase Release Notes: - Fixed "Select next match" in project search results misbehaving when some of the buffers within the search results were open before search was ran. - Fixed project search results being scrolled to the last file active prior to running the search. --------- Co-authored-by: Smit Barmase Co-authored-by: Smit --- crates/project/src/project_search.rs | 29 ++++++---------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/crates/project/src/project_search.rs b/crates/project/src/project_search.rs index d3e24b47b3eab20391dd390c9b6a21b3fc2a1981..90687f247338750b2c1197037576098281083e36 100644 --- a/crates/project/src/project_search.rs +++ b/crates/project/src/project_search.rs @@ -93,9 +93,6 @@ enum FindSearchCandidates { /// based on disk contents of a buffer. This step is not performed for buffers we already have in memory. confirm_contents_will_match_tx: Sender, confirm_contents_will_match_rx: Receiver, - /// Of those that contain at least one match (or are already in memory), look for rest of matches (and figure out their ranges). - /// But wait - first, we need to go back to the main thread to open a buffer (& create an entity for it). - get_buffer_for_full_scan_tx: Sender, }, Remote, OpenBuffersOnly, @@ -226,7 +223,7 @@ impl Search { .boxed_local(), cx.background_spawn(Self::maintain_sorted_search_results( sorted_search_results_rx, - get_buffer_for_full_scan_tx.clone(), + get_buffer_for_full_scan_tx, self.limit, )) .boxed_local(), @@ -234,7 +231,6 @@ impl Search { ( FindSearchCandidates::Local { fs, - get_buffer_for_full_scan_tx, confirm_contents_will_match_tx, confirm_contents_will_match_rx, input_paths_rx, @@ -593,7 +589,6 @@ impl Worker<'_> { input_paths_rx, confirm_contents_will_match_rx, mut confirm_contents_will_match_tx, - mut get_buffer_for_full_scan_tx, fs, ) = match self.candidates { FindSearchCandidates::Local { @@ -601,21 +596,15 @@ impl Worker<'_> { input_paths_rx, confirm_contents_will_match_rx, confirm_contents_will_match_tx, - get_buffer_for_full_scan_tx, } => ( input_paths_rx, confirm_contents_will_match_rx, confirm_contents_will_match_tx, - get_buffer_for_full_scan_tx, Some(fs), ), - FindSearchCandidates::Remote | FindSearchCandidates::OpenBuffersOnly => ( - unbounded().1, - unbounded().1, - unbounded().0, - unbounded().0, - None, - ), + FindSearchCandidates::Remote | FindSearchCandidates::OpenBuffersOnly => { + (unbounded().1, unbounded().1, unbounded().0, None) + } }; // WorkerA: grabs a request for "find all matches in file/a" <- takes 5 minutes // right after: WorkerB: grabs a request for "find all matches in file/b" <- takes 5 seconds @@ -629,7 +618,6 @@ impl Worker<'_> { open_entries: &self.open_buffers, fs: fs.as_deref(), confirm_contents_will_match_tx: &confirm_contents_will_match_tx, - get_buffer_for_full_scan_tx: &get_buffer_for_full_scan_tx, }; // Whenever we notice that some step of a pipeline is closed, we don't want to close subsequent // steps straight away. Another worker might be about to produce a value that will @@ -645,10 +633,7 @@ impl Worker<'_> { find_first_match = find_first_match.next() => { if let Some(buffer_with_at_least_one_match) = find_first_match { handler.handle_find_first_match(buffer_with_at_least_one_match).await; - } else { - get_buffer_for_full_scan_tx = bounded(1).0; } - }, scan_path = scan_path.next() => { if let Some(path_to_scan) = scan_path { @@ -673,7 +658,6 @@ struct RequestHandler<'worker> { fs: Option<&'worker dyn Fs>, open_entries: &'worker HashSet, confirm_contents_will_match_tx: &'worker Sender, - get_buffer_for_full_scan_tx: &'worker Sender, } impl RequestHandler<'_> { @@ -729,9 +713,8 @@ impl RequestHandler<'_> { _ = maybe!(async move { let InputPath { entry, - snapshot, - should_scan_tx, + mut should_scan_tx, } = req; if entry.is_fifo || !entry.is_file() { @@ -754,7 +737,7 @@ impl RequestHandler<'_> { if self.open_entries.contains(&entry.id) { // The buffer is already in memory and that's the version we want to scan; // hence skip the dilly-dally and look for all matches straight away. - self.get_buffer_for_full_scan_tx + should_scan_tx .send(ProjectPath { worktree_id: snapshot.id(), path: entry.path.clone(),