Perform path matching on Worktree snapshots

Nathan Sobo created

We're going to need something that can be moved to a background thread. Worktree used to be easy to clone, but that's no longer really true. Instead we can take a snapshot.

Change summary

zed/src/worktree.rs       | 40 +++++++++++++++++++++++++++++++++++-----
zed/src/worktree/fuzzy.rs | 20 ++++++++++----------
2 files changed, 45 insertions(+), 15 deletions(-)

Detailed changes

zed/src/worktree.rs 🔗

@@ -2,7 +2,7 @@ mod char_bag;
 mod fuzzy;
 
 use crate::{
-    editor::Snapshot,
+    editor::Snapshot as BufferSnapshot,
     sum_tree::{self, Edit, SumTree},
 };
 use anyhow::{anyhow, Result};
@@ -50,6 +50,12 @@ pub struct Worktree {
     poll_scheduled: bool,
 }
 
+pub struct Snapshot {
+    id: usize,
+    root_inode: Option<u64>,
+    entries: SumTree<Entry>,
+}
+
 #[derive(Clone)]
 pub struct FileHandle {
     worktree: ModelHandle<Worktree>,
@@ -57,7 +63,7 @@ pub struct FileHandle {
 }
 
 impl Worktree {
-    fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
+    pub fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
         let path = path.into();
         let scan_state = smol::channel::unbounded();
         let scanner = BackgroundScanner::new(path.clone(), scan_state.0);
@@ -79,6 +85,14 @@ impl Worktree {
         tree
     }
 
+    pub fn snapshot(&self) -> Snapshot {
+        Snapshot {
+            id: self.id,
+            root_inode: self.root_inode(),
+            entries: self.entries.clone(),
+        }
+    }
+
     fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
         self.scan_state = scan_state;
         self.poll_entries(ctx);
@@ -195,7 +209,12 @@ impl Worktree {
         })
     }
 
-    pub fn save<'a>(&self, ino: u64, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
+    pub fn save<'a>(
+        &self,
+        ino: u64,
+        content: BufferSnapshot,
+        ctx: &AppContext,
+    ) -> Task<Result<()>> {
         let path = self.abs_path_for_inode(ino);
         eprintln!("save to path: {:?}", path);
         ctx.background_executor().spawn(async move {
@@ -250,6 +269,16 @@ impl fmt::Debug for Worktree {
     }
 }
 
+impl Snapshot {
+    pub fn file_count(&self) -> usize {
+        self.entries.summary().file_count
+    }
+
+    pub fn root_entry(&self) -> Option<&Entry> {
+        self.root_inode.and_then(|inode| self.entries.get(&inode))
+    }
+}
+
 impl FileHandle {
     pub fn path(&self, ctx: &AppContext) -> PathBuf {
         self.worktree
@@ -262,7 +291,7 @@ impl FileHandle {
         self.worktree.read(ctx).load_file(self.inode, ctx)
     }
 
-    pub fn save<'a>(&self, content: Snapshot, ctx: &AppContext) -> Task<Result<()>> {
+    pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
         let worktree = self.worktree.read(ctx);
         worktree.save(self.inode, content, ctx)
     }
@@ -397,6 +426,7 @@ impl BackgroundScanner {
             Duration::from_millis(100),
             |events| {
                 eprintln!("events: {:?}", events);
+                true
             },
         );
 
@@ -640,7 +670,7 @@ mod tests {
             app.read(|ctx| {
                 let tree = tree.read(ctx);
                 let results = match_paths(
-                    Some(tree).into_iter(),
+                    Some(tree.snapshot()).iter(),
                     "bna",
                     false,
                     false,

zed/src/worktree/fuzzy.rs 🔗

@@ -2,7 +2,7 @@ use gpui::scoped_pool;
 
 use crate::sum_tree::SeekBias;
 
-use super::{char_bag::CharBag, Entry, FileCount, Worktree};
+use super::{char_bag::CharBag, Entry, FileCount, Snapshot, Worktree};
 
 use std::{
     cmp::{max, min, Ordering, Reverse},
@@ -71,7 +71,7 @@ impl Ord for PathMatch {
 }
 
 pub fn match_paths<'a, T>(
-    trees: T,
+    snapshots: T,
     query: &str,
     include_root_name: bool,
     include_ignored: bool,
@@ -80,7 +80,7 @@ pub fn match_paths<'a, T>(
     pool: scoped_pool::Pool,
 ) -> Vec<PathMatch>
 where
-    T: Clone + Send + Iterator<Item = &'a Worktree>,
+    T: Clone + Send + Iterator<Item = &'a Snapshot>,
 {
     let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
     let query = query.chars().collect::<Vec<_>>();
@@ -89,13 +89,13 @@ where
     let query_chars = CharBag::from(&lowercase_query[..]);
 
     let cpus = num_cpus::get();
-    let path_count: usize = trees.clone().map(Worktree::file_count).sum();
+    let path_count: usize = snapshots.clone().map(Snapshot::file_count).sum();
     let segment_size = (path_count + cpus - 1) / cpus;
     let mut segment_results = (0..cpus).map(|_| BinaryHeap::new()).collect::<Vec<_>>();
 
     pool.scoped(|scope| {
         for (segment_idx, results) in segment_results.iter_mut().enumerate() {
-            let trees = trees.clone();
+            let trees = snapshots.clone();
             scope.execute(move || {
                 let segment_start = segment_idx * segment_size;
                 let segment_end = segment_start + segment_size;
@@ -109,12 +109,12 @@ where
                 let mut best_position_matrix = Vec::new();
 
                 let mut tree_start = 0;
-                for tree in trees {
-                    let tree_end = tree_start + tree.file_count();
+                for snapshot in trees {
+                    let tree_end = tree_start + snapshot.file_count();
                     if tree_start < segment_end && segment_start < tree_end {
                         let start = max(tree_start, segment_start) - tree_start;
                         let end = min(tree_end, segment_end) - tree_start;
-                        let mut cursor = tree.entries.cursor::<_, ()>();
+                        let mut cursor = snapshot.entries.cursor::<_, ()>();
                         cursor.seek(&FileCount(start), SeekBias::Right);
                         let path_entries = cursor
                             .filter_map(|e| {
@@ -128,7 +128,7 @@ where
 
                         let skipped_prefix_len = if include_root_name {
                             0
-                        } else if let Some(Entry::Dir { name, .. }) = tree.root_entry() {
+                        } else if let Some(Entry::Dir { name, .. }) = snapshot.root_entry() {
                             let name = name.to_string_lossy();
                             if name == "/" {
                                 1
@@ -140,7 +140,7 @@ where
                         };
 
                         match_single_tree_paths(
-                            tree.id,
+                            snapshot.id,
                             skipped_prefix_len,
                             path_entries,
                             query,