Show path matches inside history items matching the query

Kirill Bulatov created

Change summary

crates/file_finder/src/file_finder.rs | 58 ++++++++++++++++++++--------
1 file changed, 41 insertions(+), 17 deletions(-)

Detailed changes

crates/file_finder/src/file_finder.rs 🔗

@@ -1,4 +1,4 @@
-use collections::{HashMap, HashSet};
+use collections::HashMap;
 use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
 use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
 use gpui::{
@@ -35,13 +35,13 @@ pub struct FileFinderDelegate {
 
 #[derive(Debug, Default)]
 struct Matches {
-    history: Vec<FoundPath>,
+    history: Vec<(FoundPath, Option<PathMatch>)>,
     search: Vec<PathMatch>,
 }
 
 #[derive(Debug)]
 enum Match<'a> {
-    History(&'a FoundPath),
+    History(&'a FoundPath, Option<&'a PathMatch>),
     Search(&'a PathMatch),
 }
 
@@ -52,7 +52,9 @@ impl Matches {
 
     fn get(&self, index: usize) -> Option<Match<'_>> {
         if index < self.history.len() {
-            self.history.get(index).map(Match::History)
+            self.history
+                .get(index)
+                .map(|(path, path_match)| Match::History(path, path_match.as_ref()))
         } else {
             self.search
                 .get(index - self.history.len())
@@ -68,16 +70,25 @@ impl Matches {
         extend_old_matches: bool,
     ) {
         let matching_history_paths = matching_history_item_paths(history_items, query);
-        new_search_matches.retain(|path_match| !matching_history_paths.contains(&path_match.path));
+        new_search_matches
+            .retain(|path_match| !matching_history_paths.contains_key(&path_match.path));
         let history_items_to_show = history_items
             .iter()
-            .filter(|history_item| matching_history_paths.contains(&history_item.project.path))
-            .cloned()
+            .filter_map(|history_item| {
+                Some((
+                    history_item.clone(),
+                    Some(
+                        matching_history_paths
+                            .get(&history_item.project.path)?
+                            .clone(),
+                    ),
+                ))
+            })
             .collect::<Vec<_>>();
         self.history = history_items_to_show;
         if extend_old_matches {
             self.search
-                .retain(|path_match| !matching_history_paths.contains(&path_match.path));
+                .retain(|path_match| !matching_history_paths.contains_key(&path_match.path));
             util::extend_sorted(
                 &mut self.search,
                 new_search_matches.into_iter(),
@@ -93,7 +104,7 @@ impl Matches {
 fn matching_history_item_paths(
     history_items: &Vec<FoundPath>,
     query: &PathLikeWithPosition<FileSearchQuery>,
-) -> HashSet<Arc<Path>> {
+) -> HashMap<Arc<Path>, PathMatch> {
     let history_items_by_worktrees = history_items
         .iter()
         .map(|found_path| {
@@ -114,7 +125,7 @@ fn matching_history_item_paths(
                 candidates
             },
         );
-    let mut matching_history_paths = HashSet::default();
+    let mut matching_history_paths = HashMap::default();
     for (worktree, candidates) in history_items_by_worktrees {
         let max_results = candidates.len() + 1;
         matching_history_paths.extend(
@@ -126,7 +137,7 @@ fn matching_history_item_paths(
                 max_results,
             )
             .into_iter()
-            .map(|path_match| path_match.path),
+            .map(|path_match| (Arc::clone(&path_match.path), path_match)),
         );
     }
     matching_history_paths
@@ -358,7 +369,7 @@ impl FileFinderDelegate {
         ix: usize,
     ) -> (String, Vec<usize>, String, Vec<usize>) {
         let (file_name, file_name_positions, full_path, full_path_positions) = match path_match {
-            Match::History(found_path) => {
+            Match::History(found_path, found_path_match) => {
                 let worktree_id = found_path.project.worktree_id;
                 let project_relative_path = &found_path.project.path;
                 let has_worktree = self
@@ -390,14 +401,22 @@ impl FileFinderDelegate {
                         path = Arc::from(absolute_path.as_path());
                     }
                 }
-                self.labels_for_path_match(&PathMatch {
+
+                let mut path_match = PathMatch {
                     score: ix as f64,
                     positions: Vec::new(),
                     worktree_id: worktree_id.to_usize(),
                     path,
                     path_prefix: "".into(),
                     distance_to_relative_ancestor: usize::MAX,
-                })
+                };
+                if let Some(found_path_match) = found_path_match {
+                    path_match
+                        .positions
+                        .extend(found_path_match.positions.iter())
+                }
+
+                self.labels_for_path_match(&path_match)
             }
             Match::Search(path_match) => self.labels_for_path_match(path_match),
         };
@@ -494,6 +513,7 @@ impl PickerDelegate for FileFinderDelegate {
                                     .is_some())
                     })
                     .cloned()
+                    .map(|p| (p, None))
                     .collect(),
                 search: Vec::new(),
             };
@@ -528,7 +548,7 @@ impl PickerDelegate for FileFinderDelegate {
                         }
                     };
                     match m {
-                        Match::History(history_match) => {
+                        Match::History(history_match, _) => {
                             let worktree_id = history_match.project.worktree_id;
                             if workspace
                                 .project()
@@ -1723,7 +1743,9 @@ mod tests {
         finder.read_with(cx, |finder, _| {
             let delegate = finder.delegate();
             assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query}, it should be present and others should be filtered out");
-            assert_eq!(delegate.matches.history.first().unwrap(), &FoundPath::new(
+            let history_match = delegate.matches.history.first().unwrap();
+            assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
+            assert_eq!(history_match.0, FoundPath::new(
                 ProjectPath {
                     worktree_id,
                     path: Arc::from(Path::new("test/first.rs")),
@@ -1767,7 +1789,9 @@ mod tests {
         finder.read_with(cx, |finder, _| {
             let delegate = finder.delegate();
             assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query_again}, it should be present and others should be filtered out, even after non-matching query");
-            assert_eq!(delegate.matches.history.first().unwrap(), &FoundPath::new(
+            let history_match = delegate.matches.history.first().unwrap();
+            assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
+            assert_eq!(history_match.0, FoundPath::new(
                 ProjectPath {
                     worktree_id,
                     path: Arc::from(Path::new("test/first.rs")),