search: New old search implementation (#39956)

This is an in-progress work on changing how task scheduler affects
performance of project search. Instead of relying on tasks being
executed at a discretion of the task scheduler, we want to experiment
with having a set of "agents" that prioritize driving in-progress
project search matches to completion over pushing the whole thing to
completion. This should hopefully significantly improve throughput &
latency of project search.

Release Notes:

- Improved project search performance

---------

Co-authored-by: Smit Barmase <smit@zed.dev>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
Piotr Osiewicz
2025-10-20 16:40:02 +02:00
committed by GitHub
co-authored by Smit Barmase Smit Barmase
parent 85c2aa7325
commit 7c4fb5a899
11 changed files with 1023 additions and 389 deletions
+7 -144
View File
@@ -8,10 +8,7 @@ use std::{
use anyhow::{Context as _, Result, anyhow, bail};
use collections::{HashMap, HashSet};
use fs::{Fs, copy_recursive};
use futures::{
FutureExt, SinkExt,
future::{BoxFuture, Shared},
};
use futures::{FutureExt, SinkExt, future::Shared};
use gpui::{
App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Task, WeakEntity,
};
@@ -999,148 +996,14 @@ impl WorktreeStore {
matching_paths_rx
}
fn scan_ignored_dir<'a>(
fs: &'a Arc<dyn Fs>,
snapshot: &'a worktree::Snapshot,
path: &'a RelPath,
query: &'a SearchQuery,
filter_tx: &'a Sender<MatchingEntry>,
output_tx: &'a Sender<oneshot::Receiver<ProjectPath>>,
) -> BoxFuture<'a, Result<()>> {
async move {
let abs_path = snapshot.absolutize(path);
let Some(mut files) = fs
.read_dir(&abs_path)
.await
.with_context(|| format!("listing ignored path {abs_path:?}"))
.log_err()
else {
return Ok(());
};
let mut results = Vec::new();
while let Some(Ok(file)) = files.next().await {
let Some(metadata) = fs
.metadata(&file)
.await
.with_context(|| format!("fetching fs metadata for {abs_path:?}"))
.log_err()
.flatten()
else {
continue;
};
if metadata.is_symlink || metadata.is_fifo {
continue;
}
let relative_path = file.strip_prefix(snapshot.abs_path())?;
let relative_path = RelPath::new(&relative_path, snapshot.path_style())
.context("getting relative path")?;
results.push((relative_path.into_arc(), !metadata.is_dir))
}
results.sort_by(|(a_path, _), (b_path, _)| a_path.cmp(b_path));
for (path, is_file) in results {
if is_file {
if query.filters_path() {
let matched_path = if query.match_full_paths() {
let mut full_path = snapshot.root_name().as_std_path().to_owned();
full_path.push(path.as_std_path());
query.match_path(&full_path)
} else {
query.match_path(&path.as_std_path())
};
if !matched_path {
continue;
}
}
let (tx, rx) = oneshot::channel();
output_tx.send(rx).await?;
filter_tx
.send(MatchingEntry {
respond: tx,
worktree_root: snapshot.abs_path().clone(),
path: ProjectPath {
worktree_id: snapshot.id(),
path: path.into_arc(),
},
})
.await?;
} else {
Self::scan_ignored_dir(fs, snapshot, &path, query, filter_tx, output_tx)
.await?;
}
}
Ok(())
}
.boxed()
}
async fn find_candidate_paths(
fs: Arc<dyn Fs>,
snapshots: Vec<(worktree::Snapshot, WorktreeSettings)>,
open_entries: HashSet<ProjectEntryId>,
query: SearchQuery,
filter_tx: Sender<MatchingEntry>,
output_tx: Sender<oneshot::Receiver<ProjectPath>>,
_: Arc<dyn Fs>,
_: Vec<(worktree::Snapshot, WorktreeSettings)>,
_: HashSet<ProjectEntryId>,
_: SearchQuery,
_: Sender<MatchingEntry>,
_: Sender<oneshot::Receiver<ProjectPath>>,
) -> Result<()> {
for (snapshot, settings) in snapshots {
for entry in snapshot.entries(query.include_ignored(), 0) {
if entry.is_dir() && entry.is_ignored {
if !settings.is_path_excluded(&entry.path) {
Self::scan_ignored_dir(
&fs,
&snapshot,
&entry.path,
&query,
&filter_tx,
&output_tx,
)
.await?;
}
continue;
}
if entry.is_fifo || !entry.is_file() {
continue;
}
if query.filters_path() {
let matched_path = if query.match_full_paths() {
let mut full_path = snapshot.root_name().as_std_path().to_owned();
full_path.push(entry.path.as_std_path());
query.match_path(&full_path)
} else {
query.match_path(entry.path.as_std_path())
};
if !matched_path {
continue;
}
}
let (mut tx, rx) = oneshot::channel();
if open_entries.contains(&entry.id) {
tx.send(ProjectPath {
worktree_id: snapshot.id(),
path: entry.path.clone(),
})
.await?;
} else {
filter_tx
.send(MatchingEntry {
respond: tx,
worktree_root: snapshot.abs_path().clone(),
path: ProjectPath {
worktree_id: snapshot.id(),
path: entry.path.clone(),
},
})
.await?;
}
output_tx.send(rx).await?;
}
}
Ok(())
}