Skip to content

Commit 77e2b2c

Browse files
committed
Wire up searcher (incomplete one) to main search routine
1 parent 9f1f9e4 commit 77e2b2c

File tree

2 files changed

+46
-84
lines changed

2 files changed

+46
-84
lines changed

crates/project/src/project.rs

Lines changed: 14 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use crate::{
4040
agent_server_store::AllAgentServersSettings,
4141
git_store::GitStore,
4242
lsp_store::{SymbolLocation, log_store::LogKind},
43+
project_search::ProjectSearcher,
4344
};
4445
pub use agent_server_store::{AgentServerStore, AgentServersUpdated};
4546
pub use git_store::{
@@ -3943,61 +3944,20 @@ impl Project {
39433944
}
39443945

39453946
pub fn search(&mut self, query: SearchQuery, cx: &mut Context<Self>) -> Receiver<SearchResult> {
3946-
let (result_tx, result_rx) = smol::channel::unbounded();
3947-
3948-
let matching_buffers_rx = if query.is_opened_only() {
3949-
self.sort_search_candidates(&query, cx)
3950-
} else {
3951-
self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
3947+
let snapshots = self
3948+
.visible_worktrees(cx)
3949+
.filter_map(|tree| {
3950+
let tree = tree.read(cx);
3951+
Some((tree.snapshot(), tree.as_local()?.settings()))
3952+
})
3953+
.collect::<Vec<_>>();
3954+
let searcher = ProjectSearcher {
3955+
fs: self.fs.clone(),
3956+
buffer_store: self.buffer_store.downgrade(),
3957+
snapshots,
3958+
open_buffers: Default::default(),
39523959
};
3953-
3954-
cx.spawn(async move |_, cx| {
3955-
let mut range_count = 0;
3956-
let mut buffer_count = 0;
3957-
let mut limit_reached = false;
3958-
let query = Arc::new(query);
3959-
let chunks = matching_buffers_rx.ready_chunks(64);
3960-
3961-
// Now that we know what paths match the query, we will load at most
3962-
// 64 buffers at a time to avoid overwhelming the main thread. For each
3963-
// opened buffer, we will spawn a background task that retrieves all the
3964-
// ranges in the buffer matched by the query.
3965-
let mut chunks = pin!(chunks);
3966-
'outer: while let Some(matching_buffer_chunk) = chunks.next().await {
3967-
let mut chunk_results = Vec::with_capacity(matching_buffer_chunk.len());
3968-
for buffer in matching_buffer_chunk {
3969-
let query = query.clone();
3970-
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
3971-
chunk_results.push(cx.background_spawn(async move {}));
3972-
}
3973-
3974-
let chunk_results = futures::future::join_all(chunk_results).await;
3975-
for result in chunk_results {
3976-
if let Some((buffer, ranges)) = result.log_err() {
3977-
range_count += ranges.len();
3978-
buffer_count += 1;
3979-
result_tx
3980-
.send(SearchResult::Buffer { buffer, ranges })
3981-
.await?;
3982-
if buffer_count > MAX_SEARCH_RESULT_FILES
3983-
|| range_count > MAX_SEARCH_RESULT_RANGES
3984-
{
3985-
limit_reached = true;
3986-
break 'outer;
3987-
}
3988-
}
3989-
}
3990-
}
3991-
3992-
if limit_reached {
3993-
result_tx.send(SearchResult::LimitReached).await?;
3994-
}
3995-
3996-
anyhow::Ok(())
3997-
})
3998-
.detach();
3999-
4000-
result_rx
3960+
searcher.run(query, cx)
40013961
}
40023962

40033963
fn find_search_candidate_buffers(

crates/project/src/project_search.rs

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ use crate::{
2626
};
2727

2828
pub(crate) struct ProjectSearcher {
29-
fs: Arc<dyn Fs>,
30-
buffer_store: WeakEntity<BufferStore>,
29+
pub(crate) fs: Arc<dyn Fs>,
30+
pub(crate) buffer_store: WeakEntity<BufferStore>,
3131
pub(crate) snapshots: Vec<(Snapshot, WorktreeSettings)>,
32-
open_buffers: HashSet<ProjectEntryId>,
32+
pub(crate) open_buffers: HashSet<ProjectEntryId>,
3333
}
3434

3535
const MAX_SEARCH_RESULT_FILES: usize = 5_000;
3636
const MAX_SEARCH_RESULT_RANGES: usize = 10_000;
3737

3838
impl ProjectSearcher {
39-
pub(crate) fn search(self, query: SearchQuery, cx: &mut App) -> Receiver<SearchResult> {
39+
pub(crate) fn run(self, query: SearchQuery, cx: &mut App) -> Receiver<SearchResult> {
4040
let executor = cx.background_executor().clone();
4141
let (tx, rx) = unbounded();
4242
cx.spawn(async move |cx| {
@@ -48,7 +48,7 @@ impl ProjectSearcher {
4848
let matched_buffer_count = AtomicUsize::new(0);
4949
let worker_pool = executor.scoped(|scope| {
5050
let (input_paths_tx, input_paths_rx) = bounded(64);
51-
let (find_first_match_tx, find_first_match_rx) = bounded(64);
51+
let (confirm_contents_will_match_tx, confirm_contents_will_match_rx) = bounded(64);
5252
let (sorted_search_results_tx, sorted_search_results_rx) = bounded(64);
5353
for _ in 0..executor.num_cpus() {
5454
let worker = Worker {
@@ -58,8 +58,8 @@ impl ProjectSearcher {
5858
matches_count: &matches_count,
5959
fs: &*self.fs,
6060
input_paths_rx: input_paths_rx.clone(),
61-
find_first_match_rx: find_first_match_rx.clone(),
62-
find_first_match_tx: find_first_match_tx.clone(),
61+
confirm_contents_will_match_rx: confirm_contents_will_match_rx.clone(),
62+
confirm_contents_will_match_tx: confirm_contents_will_match_tx.clone(),
6363
get_buffer_for_full_scan_tx: get_buffer_for_full_scan_tx.clone(),
6464
find_all_matches_rx: find_all_matches_rx.clone(),
6565
publish_matches: tx.clone(),
@@ -71,13 +71,16 @@ impl ProjectSearcher {
7171
input_paths_tx,
7272
sorted_search_results_tx,
7373
));
74-
scope.spawn(self.maintain_sorted_search_results())
74+
scope.spawn(self.maintain_sorted_search_results(
75+
sorted_search_results_rx,
76+
get_buffer_for_full_scan_tx,
77+
))
7578
});
7679
self.open_buffers(get_buffer_for_full_scan_rx, find_all_matches_tx, cx)
7780
.await;
7881
worker_pool.await;
79-
let limit_reached = matches_count.load(Ordering::Release) > MAX_SEARCH_RESULT_RANGES
80-
|| matched_buffer_count.load(Ordering::Release) > MAX_SEARCH_RESULT_FILES;
82+
let limit_reached = matches_count.load(Ordering::Acquire) > MAX_SEARCH_RESULT_RANGES
83+
|| matched_buffer_count.load(Ordering::Acquire) > MAX_SEARCH_RESULT_FILES;
8184
if limit_reached {
8285
_ = tx.send(SearchResult::LimitReached).await;
8386
}
@@ -165,10 +168,12 @@ struct Worker<'search> {
165168
/// - Scan ignored files
166169
/// Put another way: filter out files that can't match (without looking at file contents)
167170
input_paths_rx: Receiver<InputPath<'search>>,
168-
/// After that, figure out which paths contain at least one match (look at file contents). That's called "partial scan".
169-
find_first_match_tx: Sender<MatchingEntry>,
170-
find_first_match_rx: Receiver<MatchingEntry>,
171-
/// Of those that contain at least one match, look for rest of matches (and figure out their ranges).
171+
172+
/// After that, if the buffer is not yet loaded, we'll figure out if it contains at least one match
173+
/// based on disk contents of a buffer. This step is not performed for buffers we already have in memory.
174+
confirm_contents_will_match_tx: Sender<MatchingEntry>,
175+
confirm_contents_will_match_rx: Receiver<MatchingEntry>,
176+
/// Of those that contain at least one match (or are already in memory), look for rest of matches (and figure out their ranges).
172177
/// But wait - first, we need to go back to the main thread to open a buffer (& create an entity for it).
173178
get_buffer_for_full_scan_tx: Sender<ProjectPath>,
174179
/// Ok, we're back in background: run full scan & find all matches in a given buffer snapshot.
@@ -180,15 +185,15 @@ struct Worker<'search> {
180185
impl Worker<'_> {
181186
async fn run(self) {
182187
let mut find_all_matches = pin!(self.find_all_matches_rx.fuse());
183-
let mut find_first_match = pin!(self.find_first_match_rx.fuse());
188+
let mut find_first_match = pin!(self.confirm_contents_will_match_rx.fuse());
184189
let mut scan_path = pin!(self.input_paths_rx.fuse());
185190
let handler = RequestHandler {
186191
query: self.query,
187192
open_entries: &self.open_buffers,
188193
fs: self.fs,
189194
matched_buffer_count: self.matched_buffer_count,
190195
matches_count: self.matches_count,
191-
find_first_match_tx: &self.find_first_match_tx,
196+
confirm_contents_will_match_tx: &self.confirm_contents_will_match_tx,
192197
get_buffer_for_full_scan_tx: &self.get_buffer_for_full_scan_tx,
193198
publish_matches: &self.publish_matches,
194199
};
@@ -225,7 +230,7 @@ struct RequestHandler<'worker> {
225230
matched_buffer_count: &'worker AtomicUsize,
226231
matches_count: &'worker AtomicUsize,
227232

228-
find_first_match_tx: &'worker Sender<MatchingEntry>,
233+
confirm_contents_will_match_tx: &'worker Sender<MatchingEntry>,
229234
get_buffer_for_full_scan_tx: &'worker Sender<ProjectPath>,
230235
publish_matches: &'worker Sender<SearchResult>,
231236
}
@@ -302,15 +307,15 @@ impl RequestHandler<'_> {
302307
} = req;
303308
if entry.is_dir() && entry.is_ignored {
304309
if !settings.is_path_excluded(&entry.path) {
305-
Self::scan_ignored_dir(
306-
self.fs,
307-
&snapshot,
308-
&entry.path,
309-
self.query,
310-
&filter_tx,
311-
&output_tx,
312-
)
313-
.await?;
310+
// Self::scan_ignored_dir(
311+
// self.fs,
312+
// &snapshot,
313+
// &entry.path,
314+
// self.query,
315+
// &filter_tx,
316+
// &output_tx,
317+
// )
318+
// .await?;
314319
}
315320
return Ok(());
316321
}
@@ -332,8 +337,6 @@ impl RequestHandler<'_> {
332337
}
333338
}
334339

335-
let (mut tx, rx) = oneshot::channel();
336-
337340
if self.open_entries.contains(&entry.id) {
338341
// The buffer is already in memory and that's the version we want to scan;
339342
// hence skip the dilly-dally and look for all matches straight away.
@@ -344,7 +347,7 @@ impl RequestHandler<'_> {
344347
})
345348
.await?;
346349
} else {
347-
self.find_first_match_tx
350+
self.confirm_contents_will_match_tx
348351
.send(MatchingEntry {
349352
should_scan_tx: should_scan_tx,
350353
worktree_root: snapshot.abs_path().clone(),
@@ -356,7 +359,6 @@ impl RequestHandler<'_> {
356359
.await?;
357360
}
358361

359-
output_tx.send(rx).await?;
360362
anyhow::Ok(())
361363
})
362364
.await;

0 commit comments

Comments
 (0)