diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 7561a68222..08718870f1 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -23,6 +23,7 @@ pub struct FileFinder { latest_search_id: usize, latest_search_did_cancel: bool, latest_search_query: String, + relative_to: Option>, matches: Vec, selected: Option<(usize, Arc)>, cancel_flag: Arc, @@ -90,7 +91,11 @@ impl FileFinder { fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext) { workspace.toggle_modal(cx, |workspace, cx| { let project = workspace.project().clone(); - let finder = cx.add_view(|cx| Self::new(project, cx)); + let relative_to = workspace + .active_item(cx) + .and_then(|item| item.project_path(cx)) + .map(|project_path| project_path.path.clone()); + let finder = cx.add_view(|cx| Self::new(project, relative_to, cx)); cx.subscribe(&finder, Self::on_event).detach(); finder }); @@ -115,7 +120,11 @@ impl FileFinder { } } - pub fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { + pub fn new( + project: ModelHandle, + relative_to: Option>, + cx: &mut ViewContext, + ) -> Self { let handle = cx.weak_handle(); cx.observe(&project, Self::project_updated).detach(); Self { @@ -125,6 +134,7 @@ impl FileFinder { latest_search_id: 0, latest_search_did_cancel: false, latest_search_query: String::new(), + relative_to, matches: Vec::new(), selected: None, cancel_flag: Arc::new(AtomicBool::new(false)), @@ -137,6 +147,7 @@ impl FileFinder { } fn spawn_search(&mut self, query: String, cx: &mut ViewContext) -> Task<()> { + let relative_to = self.relative_to.clone(); let worktrees = self .project .read(cx) @@ -165,6 +176,7 @@ impl FileFinder { let matches = fuzzy::match_path_sets( candidate_sets.as_slice(), &query, + relative_to, false, 100, &cancel_flag, @@ -377,7 +389,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); let query = "hi".to_string(); finder @@ -453,7 +465,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); finder .update(cx, |f, cx| f.spawn_search("hi".into(), cx)) .await; @@ -479,7 +491,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. @@ -533,7 +545,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); // Run a search that matches two files with the same relative path. finder @@ -573,7 +585,7 @@ mod tests { Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx) }); let (_, finder) = - cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); + cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx)); finder .update(cx, |f, cx| f.spawn_search("dir".into(), cx)) .await; diff --git a/crates/fuzzy/src/matcher.rs b/crates/fuzzy/src/matcher.rs index 51ae75bac2..dafafe40a0 100644 --- a/crates/fuzzy/src/matcher.rs +++ b/crates/fuzzy/src/matcher.rs @@ -443,6 +443,7 @@ mod tests { positions: Vec::new(), path: candidate.path.clone(), path_prefix: "".into(), + distance_to_relative_ancestor: usize::MAX, }, ); diff --git a/crates/fuzzy/src/paths.rs b/crates/fuzzy/src/paths.rs index 8d9ec97d9b..da9789a23f 100644 --- a/crates/fuzzy/src/paths.rs +++ b/crates/fuzzy/src/paths.rs @@ -25,6 +25,9 @@ pub struct PathMatch { pub worktree_id: usize, pub path: Arc, pub path_prefix: Arc, + /// Number of steps removed from a shared parent with the relative path + /// Used to order closer paths first in the search list + pub distance_to_relative_ancestor: usize, } pub trait PathMatchCandidateSet<'a>: Send + Sync { @@ -78,6 +81,11 @@ impl Ord for PathMatch { .partial_cmp(&other.score) .unwrap_or(Ordering::Equal) .then_with(|| self.worktree_id.cmp(&other.worktree_id)) + .then_with(|| { + other + .distance_to_relative_ancestor + .cmp(&self.distance_to_relative_ancestor) + }) .then_with(|| self.path.cmp(&other.path)) } } @@ -85,6 +93,7 @@ impl Ord for PathMatch { pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( candidate_sets: &'a [Set], query: &str, + relative_to: Option>, smart_case: bool, max_results: usize, cancel_flag: &AtomicBool, @@ -111,6 +120,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( background .scoped(|scope| { for (segment_idx, results) in segment_results.iter_mut().enumerate() { + let relative_to = relative_to.clone(); scope.spawn(async move { let segment_start = segment_idx * segment_size; let segment_end = segment_start + segment_size; @@ -149,6 +159,10 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( positions: Vec::new(), path: candidate.path.clone(), path_prefix: candidate_set.prefix(), + distance_to_relative_ancestor: distance_to_relative_ancestor( + candidate.path.as_ref(), + &relative_to, + ), }, ); } @@ -172,3 +186,21 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>( } results } + +/// Compute the distance from a given path to some other path +/// If there is no shared path, returns usize::MAX +fn distance_to_relative_ancestor(path: &Path, relative_to: &Option>) -> usize { + let Some(relative_to) = relative_to else { + return usize::MAX; + }; + + for (path_ancestor_count, path_ancestor) in path.ancestors().enumerate() { + for (relative_ancestor_count, relative_ancestor) in relative_to.ancestors().enumerate() { + if path_ancestor == relative_ancestor { + return path_ancestor_count + relative_ancestor_count; + } + } + } + + usize::MAX +}