Rename worktree crate to project, pull in Project

Max Brunsfeld and Nathan Sobo created

Also, move the high-level fuzzy mathcing functions in
zed::fuzzy into the fuzzy crate so that project can
use them.

This required defining a 'PathMatchCandidateSet' trait
to avoid a circular dependency from fuzzy to worktree.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

Cargo.lock                        |  66 ++++++-----
crates/fuzzy/Cargo.toml           |   4 
crates/fuzzy/src/lib.rs           | 149 +++++++++++++++++++++++++++
crates/project/Cargo.toml         |   2 
crates/project/src/fs.rs          |   0 
crates/project/src/ignore.rs      |   0 
crates/project/src/lib.rs         | 115 +++++++++++++++++----
crates/project/src/worktree.rs    |   9 
crates/server/src/rpc.rs          |   3 
crates/zed/Cargo.toml             |   8 
crates/zed/src/file_finder.rs     |  11 -
crates/zed/src/fuzzy.rs           | 173 ---------------------------------
crates/zed/src/lib.rs             |   4 
crates/zed/src/project_panel.rs   |  17 +-
crates/zed/src/test.rs            |   2 
crates/zed/src/theme_selector.rs  |  11 -
crates/zed/src/workspace.rs       |  10 +
crates/zed/src/workspace/items.rs |   2 
18 files changed, 316 insertions(+), 270 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2097,6 +2097,10 @@ dependencies = [
 [[package]]
 name = "fuzzy"
 version = "0.1.0"
+dependencies = [
+ "gpui",
+ "util",
+]
 
 [[package]]
 name = "generator"
@@ -3674,6 +3678,36 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "project"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "buffer",
+ "clock",
+ "fsevent",
+ "futures",
+ "fuzzy",
+ "gpui",
+ "ignore",
+ "lazy_static",
+ "libc",
+ "log",
+ "parking_lot",
+ "postage",
+ "rand 0.8.3",
+ "rpc",
+ "rpc_client",
+ "serde 1.0.125",
+ "serde_json 1.0.64",
+ "smol",
+ "sum_tree",
+ "tempdir",
+ "toml 0.5.8",
+ "util",
+]
+
 [[package]]
 name = "prost"
 version = "0.8.0"
@@ -5975,36 +6009,6 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
-[[package]]
-name = "worktree"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "buffer",
- "clock",
- "fsevent",
- "futures",
- "fuzzy",
- "gpui",
- "ignore",
- "lazy_static",
- "libc",
- "log",
- "parking_lot",
- "postage",
- "rand 0.8.3",
- "rpc",
- "rpc_client",
- "serde 1.0.125",
- "serde_json 1.0.64",
- "smol",
- "sum_tree",
- "tempdir",
- "toml 0.5.8",
- "util",
-]
-
 [[package]]
 name = "wyz"
 version = "0.2.0"
@@ -6070,6 +6074,7 @@ dependencies = [
  "num_cpus",
  "parking_lot",
  "postage",
+ "project",
  "rand 0.8.3",
  "rpc",
  "rpc_client",
@@ -6093,7 +6098,6 @@ dependencies = [
  "unindent",
  "url",
  "util",
- "worktree",
 ]
 
 [[package]]

crates/fuzzy/Cargo.toml 🔗

@@ -2,3 +2,7 @@
 name = "fuzzy"
 version = "0.1.0"
 edition = "2018"
+
+[dependencies]
+gpui = { path = "../gpui" }
+util = { path = "../util" }

crates/fuzzy/src/lib.rs 🔗

@@ -1,8 +1,9 @@
 mod char_bag;
 
+use gpui::executor;
 use std::{
     borrow::Cow,
-    cmp::Ordering,
+    cmp::{self, Ordering},
     path::Path,
     sync::atomic::{self, AtomicBool},
     sync::Arc,
@@ -58,6 +59,14 @@ pub struct StringMatchCandidate {
     pub char_bag: CharBag,
 }
 
+pub trait PathMatchCandidateSet<'a>: Send + Sync {
+    type Candidates: Iterator<Item = PathMatchCandidate<'a>>;
+    fn id(&self) -> usize;
+    fn len(&self) -> usize;
+    fn prefix(&self) -> Arc<str>;
+    fn candidates(&'a self, start: usize) -> Self::Candidates;
+}
+
 impl Match for PathMatch {
     fn score(&self) -> f64 {
         self.score
@@ -152,6 +161,140 @@ impl Ord for PathMatch {
     }
 }
 
+pub async fn match_strings(
+    candidates: &[StringMatchCandidate],
+    query: &str,
+    smart_case: bool,
+    max_results: usize,
+    cancel_flag: &AtomicBool,
+    background: Arc<executor::Background>,
+) -> Vec<StringMatch> {
+    let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
+    let query = query.chars().collect::<Vec<_>>();
+
+    let lowercase_query = &lowercase_query;
+    let query = &query;
+    let query_char_bag = CharBag::from(&lowercase_query[..]);
+
+    let num_cpus = background.num_cpus().min(candidates.len());
+    let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
+    let mut segment_results = (0..num_cpus)
+        .map(|_| Vec::with_capacity(max_results))
+        .collect::<Vec<_>>();
+
+    background
+        .scoped(|scope| {
+            for (segment_idx, results) in segment_results.iter_mut().enumerate() {
+                let cancel_flag = &cancel_flag;
+                scope.spawn(async move {
+                    let segment_start = segment_idx * segment_size;
+                    let segment_end = segment_start + segment_size;
+                    let mut matcher = Matcher::new(
+                        query,
+                        lowercase_query,
+                        query_char_bag,
+                        smart_case,
+                        max_results,
+                    );
+                    matcher.match_strings(
+                        &candidates[segment_start..segment_end],
+                        results,
+                        cancel_flag,
+                    );
+                });
+            }
+        })
+        .await;
+
+    let mut results = Vec::new();
+    for segment_result in segment_results {
+        if results.is_empty() {
+            results = segment_result;
+        } else {
+            util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(&a));
+        }
+    }
+    results
+}
+
+pub async fn match_paths<'a, Set: PathMatchCandidateSet<'a>>(
+    candidate_sets: &'a [Set],
+    query: &str,
+    smart_case: bool,
+    max_results: usize,
+    cancel_flag: &AtomicBool,
+    background: Arc<executor::Background>,
+) -> Vec<PathMatch> {
+    let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
+    if path_count == 0 {
+        return Vec::new();
+    }
+
+    let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
+    let query = query.chars().collect::<Vec<_>>();
+
+    let lowercase_query = &lowercase_query;
+    let query = &query;
+    let query_char_bag = CharBag::from(&lowercase_query[..]);
+
+    let num_cpus = background.num_cpus().min(path_count);
+    let segment_size = (path_count + num_cpus - 1) / num_cpus;
+    let mut segment_results = (0..num_cpus)
+        .map(|_| Vec::with_capacity(max_results))
+        .collect::<Vec<_>>();
+
+    background
+        .scoped(|scope| {
+            for (segment_idx, results) in segment_results.iter_mut().enumerate() {
+                scope.spawn(async move {
+                    let segment_start = segment_idx * segment_size;
+                    let segment_end = segment_start + segment_size;
+                    let mut matcher = Matcher::new(
+                        query,
+                        lowercase_query,
+                        query_char_bag,
+                        smart_case,
+                        max_results,
+                    );
+
+                    let mut tree_start = 0;
+                    for candidate_set in candidate_sets {
+                        let tree_end = tree_start + candidate_set.len();
+
+                        if tree_start < segment_end && segment_start < tree_end {
+                            let start = cmp::max(tree_start, segment_start) - tree_start;
+                            let end = cmp::min(tree_end, segment_end) - tree_start;
+                            let candidates = candidate_set.candidates(start).take(end - start);
+
+                            matcher.match_paths(
+                                candidate_set.id(),
+                                candidate_set.prefix(),
+                                candidates,
+                                results,
+                                &cancel_flag,
+                            );
+                        }
+                        if tree_end >= segment_end {
+                            break;
+                        }
+                        tree_start = tree_end;
+                    }
+                })
+            }
+        })
+        .await;
+
+    let mut results = Vec::new();
+    for segment_result in segment_results {
+        if results.is_empty() {
+            results = segment_result;
+        } else {
+            util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(&a));
+        }
+    }
+    results
+}
+
 impl<'a> Matcher<'a> {
     pub fn new(
         query: &'a [char],
@@ -194,11 +337,11 @@ impl<'a> Matcher<'a> {
         )
     }
 
-    pub fn match_paths(
+    pub fn match_paths<'c: 'a>(
         &mut self,
         tree_id: usize,
         path_prefix: Arc<str>,
-        path_entries: impl Iterator<Item = PathMatchCandidate<'a>>,
+        path_entries: impl Iterator<Item = PathMatchCandidate<'c>>,
         results: &mut Vec<PathMatch>,
         cancel_flag: &AtomicBool,
     ) {

crates/worktree/Cargo.toml → crates/project/Cargo.toml 🔗

@@ -1,5 +1,5 @@
 [package]
-name = "worktree"
+name = "project"
 version = "0.1.0"
 edition = "2018"
 

crates/zed/src/project.rs → crates/project/src/lib.rs 🔗

@@ -1,10 +1,11 @@
-use crate::{
-    fuzzy::{self, PathMatch},
-    AppState,
-};
+pub mod fs;
+mod ignore;
+mod worktree;
+
 use anyhow::Result;
 use buffer::LanguageRegistry;
 use futures::Future;
+use fuzzy::{self, PathMatch, PathMatchCandidate, PathMatchCandidateSet};
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
 use rpc_client as rpc;
 use std::{
@@ -12,7 +13,9 @@ use std::{
     sync::{atomic::AtomicBool, Arc},
 };
 use util::TryFutureExt as _;
-use worktree::{fs::Fs, Worktree};
+
+pub use fs::*;
+pub use worktree::*;
 
 pub struct Project {
     worktrees: Vec<ModelHandle<Worktree>>,
@@ -40,13 +43,13 @@ pub struct ProjectEntry {
 }
 
 impl Project {
-    pub fn new(app_state: &AppState) -> Self {
+    pub fn new(languages: Arc<LanguageRegistry>, rpc: Arc<rpc::Client>, fs: Arc<dyn Fs>) -> Self {
         Self {
             worktrees: Default::default(),
             active_entry: None,
-            languages: app_state.languages.clone(),
-            rpc: app_state.rpc.clone(),
-            fs: app_state.fs.clone(),
+            languages,
+            rpc,
+            fs,
         }
     }
 
@@ -207,18 +210,22 @@ impl Project {
         cancel_flag: &'a AtomicBool,
         cx: &AppContext,
     ) -> impl 'a + Future<Output = Vec<PathMatch>> {
-        let snapshots = self
+        let include_root_name = self.worktrees.len() > 1;
+        let candidate_sets = self
             .worktrees
             .iter()
-            .map(|worktree| worktree.read(cx).snapshot())
+            .map(|worktree| CandidateSet {
+                snapshot: worktree.read(cx).snapshot(),
+                include_ignored,
+                include_root_name,
+            })
             .collect::<Vec<_>>();
-        let background = cx.background().clone();
 
+        let background = cx.background().clone();
         async move {
             fuzzy::match_paths(
-                snapshots.as_slice(),
+                candidate_sets.as_slice(),
                 query,
-                include_ignored,
                 smart_case,
                 max_results,
                 cancel_flag,
@@ -229,6 +236,65 @@ impl Project {
     }
 }
 
+struct CandidateSet {
+    snapshot: Snapshot,
+    include_ignored: bool,
+    include_root_name: bool,
+}
+
+impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
+    type Candidates = CandidateSetIter<'a>;
+
+    fn id(&self) -> usize {
+        self.snapshot.id()
+    }
+
+    fn len(&self) -> usize {
+        if self.include_ignored {
+            self.snapshot.file_count()
+        } else {
+            self.snapshot.visible_file_count()
+        }
+    }
+
+    fn prefix(&self) -> Arc<str> {
+        if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
+            self.snapshot.root_name().into()
+        } else if self.include_root_name {
+            format!("{}/", self.snapshot.root_name()).into()
+        } else {
+            "".into()
+        }
+    }
+
+    fn candidates(&'a self, start: usize) -> Self::Candidates {
+        CandidateSetIter {
+            traversal: self.snapshot.files(self.include_ignored, start),
+        }
+    }
+}
+
+struct CandidateSetIter<'a> {
+    traversal: Traversal<'a>,
+}
+
+impl<'a> Iterator for CandidateSetIter<'a> {
+    type Item = PathMatchCandidate<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.traversal.next().map(|entry| {
+            if let EntryKind::File(char_bag) = entry.kind {
+                PathMatchCandidate {
+                    path: &entry.path,
+                    char_bag,
+                }
+            } else {
+                unreachable!()
+            }
+        })
+    }
+}
+
 impl Entity for Project {
     type Event = Event;
 }
@@ -236,16 +302,15 @@ impl Entity for Project {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::test::test_app_state;
+    use buffer::LanguageRegistry;
+    use fs::RealFs;
+    use gpui::TestAppContext;
     use serde_json::json;
     use std::{os::unix, path::PathBuf};
     use util::test::temp_tree;
-    use worktree::fs::RealFs;
 
     #[gpui::test]
     async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
-        let mut app_state = cx.update(test_app_state);
-        Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(RealFs);
         let dir = temp_tree(json!({
             "root": {
                 "apple": "",
@@ -269,7 +334,8 @@ mod tests {
         )
         .unwrap();
 
-        let project = cx.add_model(|_| Project::new(app_state.as_ref()));
+        let project = build_project(&mut cx);
+
         let tree = project
             .update(&mut cx, |project, cx| {
                 project.add_local_worktree(&root_link_path, cx)
@@ -308,8 +374,6 @@ mod tests {
 
     #[gpui::test]
     async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
-        let mut app_state = cx.update(test_app_state);
-        Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(RealFs);
         let dir = temp_tree(json!({
             "root": {
                 "dir1": {},
@@ -319,7 +383,7 @@ mod tests {
             }
         }));
 
-        let project = cx.add_model(|_| Project::new(app_state.as_ref()));
+        let project = build_project(&mut cx);
         let tree = project
             .update(&mut cx, |project, cx| {
                 project.add_local_worktree(&dir.path(), cx)
@@ -339,4 +403,11 @@ mod tests {
 
         assert!(results.is_empty());
     }
+
+    fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
+        let languages = Arc::new(LanguageRegistry::new());
+        let fs = Arc::new(RealFs);
+        let rpc = rpc::Client::new();
+        cx.add_model(|_| Project::new(languages, rpc, fs))
+    }
 }

crates/worktree/src/lib.rs → crates/project/src/worktree.rs 🔗

@@ -1,12 +1,11 @@
-pub mod fs;
-mod ignore;
-
-use self::ignore::IgnoreStack;
+use super::{
+    fs::{self, Fs},
+    ignore::IgnoreStack,
+};
 use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
 use anyhow::{anyhow, Result};
 use buffer::{self, Buffer, History, LanguageRegistry, Operation, Rope};
 use clock::ReplicaId;
-pub use fs::*;
 use futures::{Stream, StreamExt};
 use fuzzy::CharBag;
 use gpui::{

crates/server/src/rpc.rs 🔗

@@ -981,12 +981,11 @@ mod tests {
         editor::{Editor, EditorSettings, Insert},
         fs::{FakeFs, Fs as _},
         people_panel::JoinWorktree,
-        project::ProjectPath,
+        project::{ProjectPath, Worktree},
         rpc::{self, Client, Credentials, EstablishConnectionError},
         test::FakeHttpClient,
         user::UserStore,
         workspace::Workspace,
-        worktree::Worktree,
     };
 
     #[gpui::test]

crates/zed/Cargo.toml 🔗

@@ -17,10 +17,10 @@ path = "src/main.rs"
 test-support = [
     "buffer/test-support",
     "gpui/test-support",
+    "project/test-support",
+    "rpc/test-support",
     "rpc_client/test-support",
     "tempdir",
-    "worktree/test-support",
-    "rpc/test-support",
 ]
 
 [dependencies]
@@ -30,11 +30,11 @@ fsevent = { path = "../fsevent" }
 fuzzy = { path = "../fuzzy" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
+project = { path = "../project" }
 rpc = { path = "../rpc" }
 rpc_client = { path = "../rpc_client" }
 sum_tree = { path = "../sum_tree" }
 util = { path = "../util" }
-worktree = { path = "../worktree" }
 
 anyhow = "1.0.38"
 async-recursion = "0.3"
@@ -79,10 +79,10 @@ url = "2.2"
 buffer = { path = "../buffer", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
+project = { path = "../project", features = ["test-support"] }
 rpc = { path = "../rpc", features = ["test-support"] }
 rpc_client = { path = "../rpc_client", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
-worktree = { path = "../worktree", features = ["test-support"] }
 
 cargo-bundle = "0.5.0"
 env_logger = "0.8"

crates/zed/src/file_finder.rs 🔗

@@ -1,10 +1,6 @@
-use crate::{
-    fuzzy::PathMatch,
-    project::{Project, ProjectPath},
-    settings::Settings,
-    workspace::Workspace,
-};
+use crate::{settings::Settings, workspace::Workspace};
 use editor::{self, Editor, EditorSettings};
+use fuzzy::PathMatch;
 use gpui::{
     action,
     elements::*,
@@ -17,6 +13,7 @@ use gpui::{
     ViewContext, ViewHandle, WeakViewHandle,
 };
 use postage::watch;
+use project::{Project, ProjectPath};
 use std::{
     cmp,
     path::Path,
@@ -427,9 +424,9 @@ mod tests {
     use super::*;
     use crate::{test::test_app_state, workspace::Workspace};
     use editor::{self, Insert};
+    use project::fs::FakeFs;
     use serde_json::json;
     use std::path::PathBuf;
-    use worktree::fs::FakeFs;
 
     #[gpui::test]
     async fn test_matching_paths(mut cx: gpui::TestAppContext) {

crates/zed/src/fuzzy.rs 🔗

@@ -1,173 +0,0 @@
-use gpui::executor;
-use std::{
-    cmp,
-    sync::{atomic::AtomicBool, Arc},
-};
-use util;
-use worktree::{EntryKind, Snapshot};
-
-pub use fuzzy::*;
-
-pub async fn match_strings(
-    candidates: &[StringMatchCandidate],
-    query: &str,
-    smart_case: bool,
-    max_results: usize,
-    cancel_flag: &AtomicBool,
-    background: Arc<executor::Background>,
-) -> Vec<StringMatch> {
-    let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
-    let query = query.chars().collect::<Vec<_>>();
-
-    let lowercase_query = &lowercase_query;
-    let query = &query;
-    let query_char_bag = CharBag::from(&lowercase_query[..]);
-
-    let num_cpus = background.num_cpus().min(candidates.len());
-    let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
-    let mut segment_results = (0..num_cpus)
-        .map(|_| Vec::with_capacity(max_results))
-        .collect::<Vec<_>>();
-
-    background
-        .scoped(|scope| {
-            for (segment_idx, results) in segment_results.iter_mut().enumerate() {
-                let cancel_flag = &cancel_flag;
-                scope.spawn(async move {
-                    let segment_start = segment_idx * segment_size;
-                    let segment_end = segment_start + segment_size;
-                    let mut matcher = Matcher::new(
-                        query,
-                        lowercase_query,
-                        query_char_bag,
-                        smart_case,
-                        max_results,
-                    );
-                    matcher.match_strings(
-                        &candidates[segment_start..segment_end],
-                        results,
-                        cancel_flag,
-                    );
-                });
-            }
-        })
-        .await;
-
-    let mut results = Vec::new();
-    for segment_result in segment_results {
-        if results.is_empty() {
-            results = segment_result;
-        } else {
-            util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(&a));
-        }
-    }
-    results
-}
-
-pub async fn match_paths(
-    snapshots: &[Snapshot],
-    query: &str,
-    include_ignored: bool,
-    smart_case: bool,
-    max_results: usize,
-    cancel_flag: &AtomicBool,
-    background: Arc<executor::Background>,
-) -> Vec<PathMatch> {
-    let path_count: usize = if include_ignored {
-        snapshots.iter().map(Snapshot::file_count).sum()
-    } else {
-        snapshots.iter().map(Snapshot::visible_file_count).sum()
-    };
-    if path_count == 0 {
-        return Vec::new();
-    }
-
-    let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
-    let query = query.chars().collect::<Vec<_>>();
-
-    let lowercase_query = &lowercase_query;
-    let query = &query;
-    let query_char_bag = CharBag::from(&lowercase_query[..]);
-
-    let num_cpus = background.num_cpus().min(path_count);
-    let segment_size = (path_count + num_cpus - 1) / num_cpus;
-    let mut segment_results = (0..num_cpus)
-        .map(|_| Vec::with_capacity(max_results))
-        .collect::<Vec<_>>();
-
-    background
-        .scoped(|scope| {
-            for (segment_idx, results) in segment_results.iter_mut().enumerate() {
-                scope.spawn(async move {
-                    let segment_start = segment_idx * segment_size;
-                    let segment_end = segment_start + segment_size;
-                    let mut matcher = Matcher::new(
-                        query,
-                        lowercase_query,
-                        query_char_bag,
-                        smart_case,
-                        max_results,
-                    );
-
-                    let mut tree_start = 0;
-                    for snapshot in snapshots {
-                        let tree_end = if include_ignored {
-                            tree_start + snapshot.file_count()
-                        } else {
-                            tree_start + snapshot.visible_file_count()
-                        };
-
-                        if tree_start < segment_end && segment_start < tree_end {
-                            let path_prefix: Arc<str> =
-                                if snapshot.root_entry().map_or(false, |e| e.is_file()) {
-                                    snapshot.root_name().into()
-                                } else if snapshots.len() > 1 {
-                                    format!("{}/", snapshot.root_name()).into()
-                                } else {
-                                    "".into()
-                                };
-
-                            let start = cmp::max(tree_start, segment_start) - tree_start;
-                            let end = cmp::min(tree_end, segment_end) - tree_start;
-                            let paths = snapshot
-                                .files(include_ignored, start)
-                                .take(end - start)
-                                .map(|entry| {
-                                    if let EntryKind::File(char_bag) = entry.kind {
-                                        PathMatchCandidate {
-                                            path: &entry.path,
-                                            char_bag,
-                                        }
-                                    } else {
-                                        unreachable!()
-                                    }
-                                });
-
-                            matcher.match_paths(
-                                snapshot.id(),
-                                path_prefix,
-                                paths,
-                                results,
-                                &cancel_flag,
-                            );
-                        }
-                        if tree_end >= segment_end {
-                            break;
-                        }
-                        tree_start = tree_end;
-                    }
-                })
-            }
-        })
-        .await;
-
-    let mut results = Vec::new();
-    for segment_result in segment_results {
-        if results.is_empty() {
-            results = segment_result;
-        } else {
-            util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(&a));
-        }
-    }
-    results
-}

crates/zed/src/lib.rs 🔗

@@ -2,12 +2,10 @@ pub mod assets;
 pub mod channel;
 pub mod chat_panel;
 pub mod file_finder;
-mod fuzzy;
 pub mod http;
 pub mod language;
 pub mod menus;
 pub mod people_panel;
-pub mod project;
 pub mod project_panel;
 pub mod settings;
 #[cfg(any(test, feature = "test-support"))]
@@ -24,11 +22,11 @@ pub use editor;
 use gpui::{action, keymap::Binding, ModelHandle};
 use parking_lot::Mutex;
 use postage::watch;
+pub use project::{self, fs};
 pub use rpc_client as rpc;
 pub use settings::Settings;
 use std::sync::Arc;
 use util::TryFutureExt;
-pub use worktree::{self, fs};
 
 action!(About);
 action!(Quit);

crates/zed/src/project_panel.rs 🔗

@@ -20,12 +20,12 @@ use gpui::{
     ViewContext, ViewHandle, WeakViewHandle,
 };
 use postage::watch;
+use project::Worktree;
 use std::{
     collections::{hash_map, HashMap},
     ffi::OsStr,
     ops::Range,
 };
-use worktree::Worktree;
 
 pub struct ProjectPanel {
     project: ModelHandle<Project>,
@@ -288,7 +288,7 @@ impl ProjectPanel {
         &self,
         target_ix: usize,
         cx: &'a AppContext,
-    ) -> Option<(&'a Worktree, &'a worktree::Entry)> {
+    ) -> Option<(&'a Worktree, &'a project::Entry)> {
         let project = self.project.read(cx);
         let mut offset = None;
         let mut ix = 0;
@@ -309,10 +309,7 @@ impl ProjectPanel {
         })
     }
 
-    fn selected_entry<'a>(
-        &self,
-        cx: &'a AppContext,
-    ) -> Option<(&'a Worktree, &'a worktree::Entry)> {
+    fn selected_entry<'a>(&self, cx: &'a AppContext) -> Option<(&'a Worktree, &'a project::Entry)> {
         let selection = self.selection?;
         let project = self.project.read(cx);
         let worktree = project.worktree_for_id(selection.worktree_id)?.read(cx);
@@ -626,7 +623,13 @@ mod tests {
         )
         .await;
 
-        let project = cx.add_model(|_| Project::new(&app_state));
+        let project = cx.add_model(|_| {
+            Project::new(
+                app_state.languages.clone(),
+                app_state.rpc.clone(),
+                app_state.fs.clone(),
+            )
+        });
         let root1 = project
             .update(&mut cx, |project, cx| {
                 project.add_local_worktree("/root1".as_ref(), cx)

crates/zed/src/test.rs 🔗

@@ -12,9 +12,9 @@ use buffer::LanguageRegistry;
 use futures::{future::BoxFuture, Future};
 use gpui::MutableAppContext;
 use parking_lot::Mutex;
+use project::fs::FakeFs;
 use rpc_client as rpc;
 use std::{fmt, sync::Arc};
-use worktree::fs::FakeFs;
 
 #[cfg(test)]
 #[ctor::ctor]

crates/zed/src/theme_selector.rs 🔗

@@ -1,12 +1,6 @@
-use std::{cmp, sync::Arc};
-
-use crate::{
-    fuzzy::{match_strings, StringMatch, StringMatchCandidate},
-    settings::ThemeRegistry,
-    workspace::Workspace,
-    AppState, Settings,
-};
+use crate::{settings::ThemeRegistry, workspace::Workspace, AppState, Settings};
 use editor::{self, Editor, EditorSettings};
+use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
     action,
     elements::*,
@@ -16,6 +10,7 @@ use gpui::{
 };
 use parking_lot::Mutex;
 use postage::watch;
+use std::{cmp, sync::Arc};
 
 pub struct ThemeSelector {
     settings_tx: Arc<Mutex<watch::Sender<Settings>>>,

crates/zed/src/workspace.rs 🔗

@@ -32,13 +32,13 @@ use log::error;
 pub use pane::*;
 pub use pane_group::*;
 use postage::{prelude::Stream, watch};
+use project::Worktree;
 use std::{
     collections::{hash_map::Entry, HashMap},
     future::Future,
     path::{Path, PathBuf},
     sync::Arc,
 };
-use worktree::Worktree;
 
 action!(Open, Arc<AppState>);
 action!(OpenPaths, OpenParams);
@@ -376,7 +376,13 @@ pub struct Workspace {
 
 impl Workspace {
     pub fn new(app_state: &AppState, cx: &mut ViewContext<Self>) -> Self {
-        let project = cx.add_model(|_| Project::new(app_state));
+        let project = cx.add_model(|_| {
+            Project::new(
+                app_state.languages.clone(),
+                app_state.rpc.clone(),
+                app_state.fs.clone(),
+            )
+        });
         cx.observe(&project, |_, _, cx| cx.notify()).detach();
 
         let pane = cx.add_view(|_| Pane::new(app_state.settings.clone()));

crates/zed/src/workspace/items.rs 🔗

@@ -5,8 +5,8 @@ use buffer::{Buffer, File as _};
 use editor::{Editor, EditorSettings, Event};
 use gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext};
 use postage::watch;
+use project::Worktree;
 use std::path::Path;
-use worktree::Worktree;
 
 impl Item for Buffer {
     type View = Editor;