diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index cdefa6b593..67077f24ea 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -4,7 +4,7 @@ use gpui::{ RenderContext, Task, View, ViewContext, ViewHandle, }; use picker::{Picker, PickerDelegate}; -use project::{Project, ProjectPath, WorktreeId}; +use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId}; use settings::Settings; use std::{ path::Path, @@ -134,17 +134,40 @@ impl FileFinder { } fn spawn_search(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + let worktrees = self + .project + .read(cx) + .visible_worktrees(cx) + .collect::>(); + let include_root_name = worktrees.len() > 1; + let candidate_sets = worktrees + .into_iter() + .map(|worktree| { + let worktree = worktree.read(cx); + PathMatchCandidateSet { + snapshot: worktree.snapshot(), + include_ignored: worktree + .root_entry() + .map_or(false, |entry| entry.is_ignored), + include_root_name, + } + }) + .collect::>(); + let search_id = util::post_inc(&mut self.search_count); self.cancel_flag.store(true, atomic::Ordering::Relaxed); self.cancel_flag = Arc::new(AtomicBool::new(false)); let cancel_flag = self.cancel_flag.clone(); - let project = self.project.clone(); cx.spawn(|this, mut cx| async move { - let matches = project - .read_with(&cx, |project, cx| { - project.match_paths(&query, false, false, 100, cancel_flag.as_ref(), cx) - }) - .await; + let matches = fuzzy::match_paths( + candidate_sets.as_slice(), + &query, + false, + 100, + &cancel_flag, + cx.background(), + ) + .await; let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed); this.update(&mut cx, |this, cx| { this.set_matches(search_id, did_cancel, query, matches, cx) @@ -475,4 +498,34 @@ mod tests { assert_eq!(f.selected_index(), 0); }); } + + #[gpui::test] + async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) { + let app_state = cx.update(AppState::test); + app_state + .fs + .as_fake() + .insert_tree( + "/root", + json!({ + "dir1": {}, + "dir2": { + "dir3": {} + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, finder) = + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + finder + .update(cx, |f, cx| f.spawn_search("dir".into(), cx)) + .await; + cx.read(|cx| { + let finder = finder.read(cx); + assert_eq!(finder.matches.len(), 0); + }); + } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0ac3064e56..5bfb3a9453 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -13,7 +13,6 @@ use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore}; use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet}; use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt}; -use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet}; use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle, @@ -58,7 +57,7 @@ use std::{ rc::Rc, str, sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}, + atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, time::Instant, @@ -5678,43 +5677,6 @@ impl Project { }) } - pub fn match_paths<'a>( - &self, - query: &'a str, - include_ignored: bool, - smart_case: bool, - max_results: usize, - cancel_flag: &'a AtomicBool, - cx: &AppContext, - ) -> impl 'a + Future> { - let worktrees = self - .worktrees(cx) - .filter(|worktree| worktree.read(cx).is_visible()) - .collect::>(); - let include_root_name = worktrees.len() > 1; - let candidate_sets = worktrees - .into_iter() - .map(|worktree| CandidateSet { - snapshot: worktree.read(cx).snapshot(), - include_ignored, - include_root_name, - }) - .collect::>(); - - let background = cx.background().clone(); - async move { - fuzzy::match_paths( - candidate_sets.as_slice(), - query, - smart_case, - max_results, - cancel_flag, - background, - ) - .await - } - } - fn edits_from_lsp( &mut self, buffer: &ModelHandle, @@ -5942,14 +5904,14 @@ impl OpenBuffer { } } -struct CandidateSet { - snapshot: Snapshot, - include_ignored: bool, - include_root_name: bool, +pub struct PathMatchCandidateSet { + pub snapshot: Snapshot, + pub include_ignored: bool, + pub include_root_name: bool, } -impl<'a> PathMatchCandidateSet<'a> for CandidateSet { - type Candidates = CandidateSetIter<'a>; +impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet { + type Candidates = PathMatchCandidateSetIter<'a>; fn id(&self) -> usize { self.snapshot.id().to_usize() @@ -5974,23 +5936,23 @@ impl<'a> PathMatchCandidateSet<'a> for CandidateSet { } fn candidates(&'a self, start: usize) -> Self::Candidates { - CandidateSetIter { + PathMatchCandidateSetIter { traversal: self.snapshot.files(self.include_ignored, start), } } } -struct CandidateSetIter<'a> { +pub struct PathMatchCandidateSetIter<'a> { traversal: Traversal<'a>, } -impl<'a> Iterator for CandidateSetIter<'a> { - type Item = PathMatchCandidate<'a>; +impl<'a> Iterator for PathMatchCandidateSetIter<'a> { + type Item = fuzzy::PathMatchCandidate<'a>; fn next(&mut self) -> Option { self.traversal.next().map(|entry| { if let EntryKind::File(char_bag) = entry.kind { - PathMatchCandidate { + fuzzy::PathMatchCandidate { path: &entry.path, char_bag, } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 00f7bb8c94..9a65bab0c0 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -8,12 +8,12 @@ use language::{ }; use lsp::Url; use serde_json::json; -use std::{cell::RefCell, os::unix, path::PathBuf, rc::Rc, task::Poll}; +use std::{cell::RefCell, os::unix, rc::Rc, task::Poll}; use unindent::Unindent as _; use util::{assert_set_eq, test::temp_tree}; #[gpui::test] -async fn test_populate_and_search(cx: &mut gpui::TestAppContext) { +async fn test_symlinks(cx: &mut gpui::TestAppContext) { let dir = temp_tree(json!({ "root": { "apple": "", @@ -38,7 +38,6 @@ async fn test_populate_and_search(cx: &mut gpui::TestAppContext) { .unwrap(); let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; - project.read_with(cx, |project, cx| { let tree = project.worktrees(cx).next().unwrap().read(cx); assert_eq!(tree.file_count(), 5); @@ -47,23 +46,6 @@ async fn test_populate_and_search(cx: &mut gpui::TestAppContext) { tree.inode_for_path("finnochio/grape") ); }); - - let cancel_flag = Default::default(); - let results = project - .read_with(cx, |project, cx| { - project.match_paths("bna", false, false, 10, &cancel_flag, cx) - }) - .await; - assert_eq!( - results - .into_iter() - .map(|result| result.path) - .collect::>>(), - vec![ - PathBuf::from("banana/carrot/date").into(), - PathBuf::from("banana/carrot/endive").into(), - ] - ); } #[gpui::test] @@ -1645,28 +1627,6 @@ fn chunks_with_diagnostics( chunks } -#[gpui::test] -async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) { - let dir = temp_tree(json!({ - "root": { - "dir1": {}, - "dir2": { - "dir3": {} - } - } - })); - - let project = Project::test(Arc::new(RealFs), [dir.path()], cx).await; - let cancel_flag = Default::default(); - let results = project - .read_with(cx, |project, cx| { - project.match_paths("dir", false, false, 10, &cancel_flag, cx) - }) - .await; - - assert!(results.is_empty()); -} - #[gpui::test(iterations = 10)] async fn test_definition(cx: &mut gpui::TestAppContext) { let mut language = Language::new(