Fix panic when fuzzy-matching on a `Worktree` that contains no files

Antonio Scandurra created

As part of our work on the deterministic executor, in c4e37dc we fixed a
bug that occurred when there were more CPUs than paths to fuzzy-match
on.

However, that introduced another bug that caused Zed to panic when
trying to calculate how many paths should be fuzzy-matched by each
available worker thread for worktrees that didn't contain any file.

This commit bails out early in `fuzzy::match_paths` if the vector of
paths to search is empty.

Change summary

zed/src/worktree.rs       | 49 +++++++++++++++++++++++++++++++++++++++++
zed/src/worktree/fuzzy.rs | 15 +++++++-----
2 files changed, 58 insertions(+), 6 deletions(-)

Detailed changes

zed/src/worktree.rs 🔗

@@ -2641,6 +2641,55 @@ mod tests {
         );
     }
 
+    #[gpui::test]
+    async fn test_search_worktree_without_files(cx: gpui::TestAppContext) {
+        let dir = temp_tree(json!({
+            "root": {
+                "dir1": {},
+                "dir2": {
+                    "dir3": {}
+                }
+            }
+        }));
+        let tree = Worktree::open_local(
+            dir.path(),
+            Default::default(),
+            Arc::new(RealFs),
+            &mut cx.to_async(),
+        )
+        .await
+        .unwrap();
+
+        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+            .await;
+        let snapshot = cx.read(|cx| {
+            let tree = tree.read(cx);
+            assert_eq!(tree.file_count(), 0);
+            tree.snapshot()
+        });
+        let results = cx
+            .read(|cx| {
+                match_paths(
+                    Some(&snapshot).into_iter(),
+                    "dir",
+                    false,
+                    false,
+                    false,
+                    10,
+                    Default::default(),
+                    cx.background().clone(),
+                )
+            })
+            .await;
+        assert_eq!(
+            results
+                .into_iter()
+                .map(|result| result.path)
+                .collect::<Vec<Arc<Path>>>(),
+            vec![]
+        );
+    }
+
     #[gpui::test]
     async fn test_save_file(mut cx: gpui::TestAppContext) {
         let app_state = cx.read(build_app_state);

zed/src/worktree/fuzzy.rs 🔗

@@ -64,6 +64,15 @@ pub async fn match_paths<'a, T>(
 where
     T: Clone + Send + Iterator<Item = &'a Snapshot> + 'a,
 {
+    let path_count: usize = if include_ignored {
+        snapshots.clone().map(Snapshot::file_count).sum()
+    } else {
+        snapshots.clone().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<_>>();
 
@@ -71,12 +80,6 @@ where
     let query = &query;
     let query_chars = CharBag::from(&lowercase_query[..]);
 
-    let path_count: usize = if include_ignored {
-        snapshots.clone().map(Snapshot::file_count).sum()
-    } else {
-        snapshots.clone().map(Snapshot::visible_file_count).sum()
-    };
-
     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)