WIP

Antonio Scandurra created

Change summary

server/src/tests.rs      |  50 ++++++-
zed/src/editor/buffer.rs |  20 ++
zed/src/file_finder.rs   |  36 +++--
zed/src/workspace.rs     | 175 ++++++++++++++++----------
zed/src/worktree.rs      | 270 ++++++++++++++++++++++-------------------
5 files changed, 330 insertions(+), 221 deletions(-)

Detailed changes

server/src/tests.rs 🔗

@@ -39,8 +39,14 @@ async fn test_share_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext)
         "a.txt": "a-contents",
         "b.txt": "b-contents",
     }));
-    let worktree_a = cx_a
-        .add_model(|cx| Worktree::local(dir.path(), lang_registry.clone(), Arc::new(RealFs), cx));
+    let worktree_a = Worktree::open_local(
+        dir.path(),
+        lang_registry.clone(),
+        Arc::new(RealFs),
+        &mut cx_a.to_async(),
+    )
+    .await
+    .unwrap();
     worktree_a
         .read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
         .await;
@@ -134,8 +140,14 @@ async fn test_propagate_saves_and_fs_changes_in_shared_worktree(
         "file1": "",
         "file2": ""
     }));
-    let worktree_a = cx_a
-        .add_model(|cx| Worktree::local(dir.path(), lang_registry.clone(), Arc::new(RealFs), cx));
+    let worktree_a = Worktree::open_local(
+        dir.path(),
+        lang_registry.clone(),
+        Arc::new(RealFs),
+        &mut cx_a.to_async(),
+    )
+    .await
+    .unwrap();
     worktree_a
         .read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
         .await;
@@ -249,8 +261,14 @@ async fn test_buffer_conflict_after_save(mut cx_a: TestAppContext, mut cx_b: Tes
     fs.save(Path::new("/a.txt"), &"a-contents".into())
         .await
         .unwrap();
-    let worktree_a =
-        cx_a.add_model(|cx| Worktree::local(Path::new("/"), lang_registry.clone(), fs.clone(), cx));
+    let worktree_a = Worktree::open_local(
+        "/".as_ref(),
+        lang_registry.clone(),
+        Arc::new(RealFs),
+        &mut cx_a.to_async(),
+    )
+    .await
+    .unwrap();
     worktree_a
         .read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
         .await;
@@ -320,8 +338,14 @@ async fn test_editing_while_guest_opens_buffer(mut cx_a: TestAppContext, mut cx_
     fs.save(Path::new("/a.txt"), &"a-contents".into())
         .await
         .unwrap();
-    let worktree_a =
-        cx_a.add_model(|cx| Worktree::local(Path::new("/"), lang_registry.clone(), fs.clone(), cx));
+    let worktree_a = Worktree::open_local(
+        "/".as_ref(),
+        lang_registry.clone(),
+        Arc::new(RealFs),
+        &mut cx_a.to_async(),
+    )
+    .await
+    .unwrap();
     worktree_a
         .read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
         .await;
@@ -373,8 +397,14 @@ async fn test_peer_disconnection(mut cx_a: TestAppContext, cx_b: TestAppContext)
         "a.txt": "a-contents",
         "b.txt": "b-contents",
     }));
-    let worktree_a = cx_a
-        .add_model(|cx| Worktree::local(dir.path(), lang_registry.clone(), Arc::new(RealFs), cx));
+    let worktree_a = Worktree::open_local(
+        dir.path(),
+        lang_registry.clone(),
+        Arc::new(RealFs),
+        &mut cx_a.to_async(),
+    )
+    .await
+    .unwrap();
     worktree_a
         .read_with(&cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
         .await;

zed/src/editor/buffer.rs 🔗

@@ -3209,8 +3209,14 @@ mod tests {
             "file2": "def",
             "file3": "ghi",
         }));
-        let tree = cx
-            .add_model(|cx| Worktree::local(dir.path(), Default::default(), Arc::new(RealFs), cx));
+        let tree = Worktree::open_local(
+            dir.path(),
+            Default::default(),
+            Arc::new(RealFs),
+            &mut cx.to_async(),
+        )
+        .await
+        .unwrap();
         tree.flush_fs_events(&cx).await;
         cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
             .await;
@@ -3322,8 +3328,14 @@ mod tests {
     async fn test_file_changes_on_disk(mut cx: gpui::TestAppContext) {
         let initial_contents = "aaa\nbbbbb\nc\n";
         let dir = temp_tree(json!({ "the-file": initial_contents }));
-        let tree = cx
-            .add_model(|cx| Worktree::local(dir.path(), Default::default(), Arc::new(RealFs), cx));
+        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;
 

zed/src/file_finder.rs 🔗

@@ -476,11 +476,13 @@ mod tests {
         });
 
         let app_state = cx.read(build_app_state);
-        let (window_id, workspace) = cx.add_window(|cx| {
-            let mut workspace = Workspace::new(&app_state, cx);
-            workspace.add_worktree(tmp_dir.path(), cx);
-            workspace
-        });
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.add_worktree(tmp_dir.path(), cx)
+            })
+            .await
+            .unwrap();
         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
             .await;
         cx.dispatch_action(
@@ -543,11 +545,13 @@ mod tests {
             "hiccup": "",
         }));
         let app_state = cx.read(build_app_state);
-        let (_, workspace) = cx.add_window(|cx| {
-            let mut workspace = Workspace::new(&app_state, cx);
-            workspace.add_worktree(tmp_dir.path(), cx);
-            workspace
-        });
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.add_worktree(tmp_dir.path(), cx)
+            })
+            .await
+            .unwrap();
         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
             .await;
         let (_, finder) =
@@ -601,11 +605,13 @@ mod tests {
         fs::write(&file_path, "").unwrap();
 
         let app_state = cx.read(build_app_state);
-        let (_, workspace) = cx.add_window(|cx| {
-            let mut workspace = Workspace::new(&app_state, cx);
-            workspace.add_worktree(&file_path, cx);
-            workspace
-        });
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.add_worktree(&file_path, cx)
+            })
+            .await
+            .unwrap();
         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
             .await;
         let (_, finder) =

zed/src/workspace.rs 🔗

@@ -402,70 +402,89 @@ impl Workspace {
                 cx.spawn(|this, mut cx| {
                     let fs = fs.clone();
                     async move {
+                        let entry_id = entry_id.await?;
                         if fs.is_file(&abs_path).await {
-                            return this.update(&mut cx, |this, cx| this.open_entry(entry_id, cx));
-                        } else {
-                            None
+                            if let Some(entry) =
+                                this.update(&mut cx, |this, cx| this.open_entry(entry_id, cx))
+                            {
+                                entry.await;
+                            }
                         }
+                        Ok(())
                     }
                 })
             })
-            .collect::<Vec<_>>();
+            .collect::<Vec<Task<Result<()>>>>();
         async move {
             for task in tasks {
-                if let Some(task) = task.await {
-                    task.await;
+                if let Err(error) = task.await {
+                    log::error!("error opening paths {}", error);
                 }
             }
         }
     }
 
     fn worktree_for_abs_path(
-        &mut self,
+        &self,
         abs_path: &Path,
         cx: &mut ViewContext<Self>,
-    ) -> (ModelHandle<Worktree>, PathBuf) {
-        for tree in self.worktrees.iter() {
-            if let Some(path) = tree
-                .read(cx)
-                .as_local()
-                .and_then(|tree| abs_path.strip_prefix(&tree.abs_path()).ok())
-            {
-                return (tree.clone(), path.to_path_buf());
+    ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
+        let abs_path: Arc<Path> = Arc::from(abs_path);
+        cx.spawn(|this, mut cx| async move {
+            let mut entry_id = None;
+            this.read_with(&cx, |this, cx| {
+                for tree in this.worktrees.iter() {
+                    if let Some(relative_path) = tree
+                        .read(cx)
+                        .as_local()
+                        .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
+                    {
+                        entry_id = Some((tree.clone(), relative_path.into()));
+                        break;
+                    }
+                }
+            });
+
+            if let Some(entry_id) = entry_id {
+                Ok(entry_id)
+            } else {
+                let worktree = this
+                    .update(&mut cx, |this, cx| this.add_worktree(&abs_path, cx))
+                    .await?;
+                Ok((worktree, PathBuf::new()))
             }
-        }
-        (self.add_worktree(abs_path, cx), PathBuf::new())
+        })
     }
 
     fn entry_id_for_path(
-        &mut self,
+        &self,
         abs_path: &Path,
         cx: &mut ViewContext<Self>,
-    ) -> (usize, Arc<Path>) {
-        for tree in self.worktrees.iter() {
-            if let Some(relative_path) = tree
-                .read(cx)
-                .as_local()
-                .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
-            {
-                return (tree.id(), relative_path.into());
-            }
-        }
-        let worktree = self.add_worktree(&abs_path, cx);
-        (worktree.id(), Path::new("").into())
+    ) -> Task<Result<(usize, Arc<Path>)>> {
+        let entry = self.worktree_for_abs_path(abs_path, cx);
+        cx.spawn(|_, _| async move {
+            let (worktree, path) = entry.await?;
+            Ok((worktree.id(), path.into()))
+        })
     }
 
     pub fn add_worktree(
-        &mut self,
+        &self,
         path: &Path,
         cx: &mut ViewContext<Self>,
-    ) -> ModelHandle<Worktree> {
-        let worktree =
-            cx.add_model(|cx| Worktree::local(path, self.languages.clone(), self.fs.clone(), cx));
-        cx.observe_model(&worktree, |_, _, cx| cx.notify());
-        self.worktrees.insert(worktree.clone());
-        cx.notify();
-        worktree
+    ) -> Task<Result<ModelHandle<Worktree>>> {
+        let languages = self.languages.clone();
+        let fs = self.fs.clone();
+        let path = Arc::from(path);
+        cx.spawn(|this, mut cx| async move {
+            let worktree = Worktree::open_local(path, languages, fs, &mut cx).await?;
+            this.update(&mut cx, |this, cx| {
+                cx.observe_model(&worktree, |_, _, cx| cx.notify());
+                this.worktrees.insert(worktree.clone());
+                cx.notify();
+            });
+            Ok(worktree)
+        })
     }
 
     pub fn toggle_modal<V, F>(&mut self, cx: &mut ViewContext<Self>, add_view: F)
@@ -640,12 +659,22 @@ impl Workspace {
                 cx.prompt_for_new_path(&start_abs_path, move |abs_path, cx| {
                     if let Some(abs_path) = abs_path {
                         cx.spawn(|mut cx| async move {
-                            let result = handle
-                                .update(&mut cx, |me, cx| {
-                                    let (worktree, path) = me.worktree_for_abs_path(&abs_path, cx);
-                                    item.save_as(&worktree, &path, cx.as_mut())
+                            let result = match handle
+                                .update(&mut cx, |this, cx| {
+                                    this.worktree_for_abs_path(&abs_path, cx)
                                 })
-                                .await;
+                                .await
+                            {
+                                Ok((worktree, path)) => {
+                                    handle
+                                        .update(&mut cx, |_, cx| {
+                                            item.save_as(&worktree, &path, cx.as_mut())
+                                        })
+                                        .await
+                                }
+                                Err(error) => Err(error),
+                            };
+
                             if let Err(error) = result {
                                 error!("failed to save item: {:?}, ", error);
                             }
@@ -974,11 +1003,13 @@ mod tests {
 
         let app_state = cx.read(build_app_state);
 
-        let (_, workspace) = cx.add_window(|cx| {
-            let mut workspace = Workspace::new(&app_state, cx);
-            workspace.add_worktree(dir.path(), cx);
-            workspace
-        });
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.add_worktree(dir.path(), cx)
+            })
+            .await
+            .unwrap();
 
         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
             .await;
@@ -1077,11 +1108,13 @@ mod tests {
         let mut app_state = cx.read(build_app_state);
         Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(fs);
 
-        let (_, workspace) = cx.add_window(|cx| {
-            let mut workspace = Workspace::new(&app_state, cx);
-            workspace.add_worktree("/dir1".as_ref(), cx);
-            workspace
-        });
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.add_worktree("/dir1".as_ref(), cx)
+            })
+            .await
+            .unwrap();
         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
             .await;
 
@@ -1142,11 +1175,13 @@ mod tests {
         }));
 
         let app_state = cx.read(build_app_state);
-        let (window_id, workspace) = cx.add_window(|cx| {
-            let mut workspace = Workspace::new(&app_state, cx);
-            workspace.add_worktree(dir.path(), cx);
-            workspace
-        });
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.add_worktree(dir.path(), cx)
+            })
+            .await
+            .unwrap();
         let tree = cx.read(|cx| {
             let mut trees = workspace.read(cx).worktrees().iter();
             trees.next().unwrap().clone()
@@ -1185,11 +1220,13 @@ mod tests {
     async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) {
         let dir = TempDir::new("test-new-file").unwrap();
         let app_state = cx.read(build_app_state);
-        let (_, workspace) = cx.add_window(|cx| {
-            let mut workspace = Workspace::new(&app_state, cx);
-            workspace.add_worktree(dir.path(), cx);
-            workspace
-        });
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.add_worktree(dir.path(), cx)
+            })
+            .await
+            .unwrap();
         let tree = cx.read(|cx| {
             workspace
                 .read(cx)
@@ -1305,11 +1342,13 @@ mod tests {
         }));
 
         let app_state = cx.read(build_app_state);
-        let (window_id, workspace) = cx.add_window(|cx| {
-            let mut workspace = Workspace::new(&app_state, cx);
-            workspace.add_worktree(dir.path(), cx);
-            workspace
-        });
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx));
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.add_worktree(dir.path(), cx)
+            })
+            .await
+            .unwrap();
         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
             .await;
         let entries = cx.read(|cx| workspace.file_entries(cx));

zed/src/worktree.rs 🔗

@@ -541,23 +541,26 @@ impl Entity for Worktree {
 }
 
 impl Worktree {
-    pub fn local(
+    pub async fn open_local(
         path: impl Into<Arc<Path>>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> Self {
-        let (mut tree, scan_states_tx) = LocalWorktree::new(path, languages, fs.clone(), cx);
-        let abs_path = tree.snapshot.abs_path.clone();
-        let background_snapshot = tree.background_snapshot.clone();
-        let thread_pool = cx.thread_pool().clone();
-        tree._background_scanner_task = Some(cx.background().spawn(async move {
-            let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
-            let scanner =
-                BackgroundScanner::new(background_snapshot, scan_states_tx, fs, thread_pool);
-            scanner.run(events).await;
-        }));
-        Worktree::Local(tree)
+        cx: &mut AsyncAppContext,
+    ) -> Result<ModelHandle<Self>> {
+        let (tree, scan_states_tx) = LocalWorktree::new(path, languages, fs.clone(), cx).await?;
+        tree.update(cx, |tree, cx| {
+            let tree = tree.as_local_mut().unwrap();
+            let abs_path = tree.snapshot.abs_path.clone();
+            let background_snapshot = tree.background_snapshot.clone();
+            let thread_pool = cx.thread_pool().clone();
+            tree._background_scanner_task = Some(cx.background().spawn(async move {
+                let events = fs.watch(&abs_path, Duration::from_millis(100)).await;
+                let scanner =
+                    BackgroundScanner::new(background_snapshot, scan_states_tx, fs, thread_pool);
+                scanner.run(events).await;
+            }));
+        });
+        Ok(tree)
     }
 
     pub async fn open_remote(
@@ -1016,75 +1019,99 @@ pub struct LocalWorktree {
 }
 
 impl LocalWorktree {
-    fn new(
+    async fn new(
         path: impl Into<Arc<Path>>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
-        cx: &mut ModelContext<Worktree>,
-    ) -> (Self, Sender<ScanState>) {
+        cx: &mut AsyncAppContext,
+    ) -> Result<(ModelHandle<Worktree>, Sender<ScanState>)> {
         let abs_path = path.into();
+        let path: Arc<Path> = Arc::from(Path::new(""));
+        let next_entry_id = AtomicUsize::new(0);
+
+        // After determining whether the root entry is a file or a directory, populate the
+        // snapshot's "root name", which will be used for the purpose of fuzzy matching.
+        let mut root_name = abs_path
+            .file_name()
+            .map_or(String::new(), |f| f.to_string_lossy().to_string());
+        let root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
+        let entry = fs
+            .entry(root_char_bag, &next_entry_id, path.clone(), &abs_path)
+            .await?
+            .ok_or_else(|| anyhow!("root entry does not exist"))?;
+        let is_dir = entry.is_dir();
+        if is_dir {
+            root_name.push('/');
+        }
+
         let (scan_states_tx, scan_states_rx) = smol::channel::unbounded();
         let (mut last_scan_state_tx, last_scan_state_rx) = watch::channel_with(ScanState::Scanning);
-        let id = cx.model_id();
-        let snapshot = Snapshot {
-            id,
-            scan_id: 0,
-            abs_path,
-            root_name: Default::default(),
-            root_char_bag: Default::default(),
-            ignores: Default::default(),
-            entries_by_path: Default::default(),
-            entries_by_id: Default::default(),
-            removed_entry_ids: Default::default(),
-            next_entry_id: Default::default(),
-        };
-
-        let tree = Self {
-            snapshot: snapshot.clone(),
-            background_snapshot: Arc::new(Mutex::new(snapshot)),
-            snapshots_to_send_tx: None,
-            last_scan_state_rx,
-            _background_scanner_task: None,
-            poll_scheduled: false,
-            open_buffers: Default::default(),
-            shared_buffers: Default::default(),
-            peers: Default::default(),
-            rpc: None,
-            languages,
-            fs,
-        };
+        let tree = cx.add_model(move |cx: &mut ModelContext<Worktree>| {
+            let mut snapshot = Snapshot {
+                id: cx.model_id(),
+                scan_id: 0,
+                abs_path,
+                root_name,
+                root_char_bag,
+                ignores: Default::default(),
+                entries_by_path: Default::default(),
+                entries_by_id: Default::default(),
+                removed_entry_ids: Default::default(),
+                next_entry_id: Arc::new(next_entry_id),
+            };
+            snapshot.insert_entry(entry);
+
+            let tree = Self {
+                snapshot: snapshot.clone(),
+                background_snapshot: Arc::new(Mutex::new(snapshot)),
+                snapshots_to_send_tx: None,
+                last_scan_state_rx,
+                _background_scanner_task: None,
+                poll_scheduled: false,
+                open_buffers: Default::default(),
+                shared_buffers: Default::default(),
+                peers: Default::default(),
+                rpc: None,
+                languages,
+                fs,
+            };
 
-        cx.spawn_weak(|this, mut cx| async move {
-            while let Ok(scan_state) = scan_states_rx.recv().await {
-                if let Some(handle) = cx.read(|cx| this.upgrade(&cx)) {
-                    let to_send = handle.update(&mut cx, |this, cx| {
-                        last_scan_state_tx.blocking_send(scan_state).ok();
-                        this.poll_snapshot(cx);
-                        let tree = this.as_local_mut().unwrap();
-                        if !tree.is_scanning() {
-                            if let Some(snapshots_to_send_tx) = tree.snapshots_to_send_tx.clone() {
-                                Some((tree.snapshot(), snapshots_to_send_tx))
+            cx.spawn_weak(|this, mut cx| async move {
+                while let Ok(scan_state) = scan_states_rx.recv().await {
+                    if let Some(handle) = cx.read(|cx| this.upgrade(&cx)) {
+                        let to_send = handle.update(&mut cx, |this, cx| {
+                            last_scan_state_tx.blocking_send(scan_state).ok();
+                            this.poll_snapshot(cx);
+                            let tree = this.as_local_mut().unwrap();
+                            if !tree.is_scanning() {
+                                if let Some(snapshots_to_send_tx) =
+                                    tree.snapshots_to_send_tx.clone()
+                                {
+                                    Some((tree.snapshot(), snapshots_to_send_tx))
+                                } else {
+                                    None
+                                }
                             } else {
                                 None
                             }
-                        } else {
-                            None
-                        }
-                    });
+                        });
 
-                    if let Some((snapshot, snapshots_to_send_tx)) = to_send {
-                        if let Err(err) = snapshots_to_send_tx.send(snapshot).await {
-                            log::error!("error submitting snapshot to send {}", err);
+                        if let Some((snapshot, snapshots_to_send_tx)) = to_send {
+                            if let Err(err) = snapshots_to_send_tx.send(snapshot).await {
+                                log::error!("error submitting snapshot to send {}", err);
+                            }
                         }
+                    } else {
+                        break;
                     }
-                } else {
-                    break;
                 }
-            }
-        })
-        .detach();
+            })
+            .detach();
+
+            Worktree::Local(tree)
+        });
 
-        (tree, scan_states_tx)
+        Ok((tree, scan_states_tx))
     }
 
     pub fn open_buffer(
@@ -1890,9 +1917,8 @@ impl File {
     /// Returns the last component of this handle's absolute path. If this handle refers to the root
     /// of its worktree, then this method will return the name of the worktree itself.
     pub fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option<OsString> {
-        self.path
-            .file_name()
-            .or_else(|| Some(OsStr::new(self.worktree.read(cx).root_name())))
+        dbg!(self.path.file_name())
+            .or_else(|| Some(OsStr::new(dbg!(self.worktree.read(cx).root_name()))))
             .map(Into::into)
     }
 
@@ -2242,40 +2268,19 @@ impl BackgroundScanner {
     }
 
     async fn scan_dirs(&mut self) -> Result<()> {
+        let root_char_bag;
         let next_entry_id;
+        let is_dir;
         {
-            let mut snapshot = self.snapshot.lock();
-            snapshot.scan_id += 1;
+            let snapshot = self.snapshot.lock();
+            root_char_bag = snapshot.root_char_bag;
             next_entry_id = snapshot.next_entry_id.clone();
+            is_dir = snapshot.root_entry().is_dir();
         }
 
-        let path: Arc<Path> = Arc::from(Path::new(""));
-        let abs_path = self.abs_path();
-
-        // After determining whether the root entry is a file or a directory, populate the
-        // snapshot's "root name", which will be used for the purpose of fuzzy matching.
-        let mut root_name = abs_path
-            .file_name()
-            .map_or(String::new(), |f| f.to_string_lossy().to_string());
-        let root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
-        let entry = self
-            .fs
-            .entry(root_char_bag, &next_entry_id, path.clone(), &abs_path)
-            .await?
-            .ok_or_else(|| anyhow!("root entry does not exist"))?;
-        let is_dir = entry.is_dir();
-        if is_dir {
-            root_name.push('/');
-        }
-
-        {
-            let mut snapshot = self.snapshot.lock();
-            snapshot.root_name = root_name;
-            snapshot.root_char_bag = root_char_bag;
-        }
-
-        self.snapshot.lock().insert_entry(entry);
         if is_dir {
+            let path: Arc<Path> = Arc::from(Path::new(""));
+            let abs_path = self.abs_path();
             let (tx, rx) = channel::unbounded();
             tx.send(ScanJob {
                 abs_path: abs_path.to_path_buf(),
@@ -2983,7 +2988,7 @@ mod tests {
     use std::{env, fmt::Write, os::unix, time::SystemTime};
 
     #[gpui::test]
-    async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
+    async fn test_populate_and_search(cx: gpui::TestAppContext) {
         let dir = temp_tree(json!({
             "root": {
                 "apple": "",
@@ -3007,9 +3012,14 @@ mod tests {
         )
         .unwrap();
 
-        let tree = cx.add_model(|cx| {
-            Worktree::local(root_link_path, Default::default(), Arc::new(RealFs), cx)
-        });
+        let tree = Worktree::open_local(
+            root_link_path,
+            Default::default(),
+            Arc::new(RealFs),
+            &mut cx.to_async(),
+        )
+        .await
+        .unwrap();
 
         cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
             .await;
@@ -3055,14 +3065,14 @@ mod tests {
         let dir = temp_tree(json!({
             "file1": "the old contents",
         }));
-        let tree = cx.add_model(|cx| {
-            Worktree::local(
-                dir.path(),
-                app_state.languages.clone(),
-                Arc::new(RealFs),
-                cx,
-            )
-        });
+        let tree = Worktree::open_local(
+            dir.path(),
+            app_state.languages.clone(),
+            Arc::new(RealFs),
+            &mut cx.to_async(),
+        )
+        .await
+        .unwrap();
         let buffer = tree
             .update(&mut cx, |tree, cx| tree.open_buffer("file1", cx))
             .await
@@ -3085,14 +3095,14 @@ mod tests {
         }));
         let file_path = dir.path().join("file1");
 
-        let tree = cx.add_model(|cx| {
-            Worktree::local(
-                file_path.clone(),
-                app_state.languages.clone(),
-                Arc::new(RealFs),
-                cx,
-            )
-        });
+        let tree = Worktree::open_local(
+            file_path.clone(),
+            app_state.languages.clone(),
+            Arc::new(RealFs),
+            &mut cx.to_async(),
+        )
+        .await
+        .unwrap();
         cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
             .await;
         cx.read(|cx| assert_eq!(tree.read(cx).file_count(), 1));
@@ -3127,8 +3137,14 @@ mod tests {
             }
         }));
 
-        let tree = cx
-            .add_model(|cx| Worktree::local(dir.path(), Default::default(), Arc::new(RealFs), cx));
+        let tree = Worktree::open_local(
+            dir.path(),
+            Default::default(),
+            Arc::new(RealFs),
+            &mut cx.to_async(),
+        )
+        .await
+        .unwrap();
 
         let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
             let buffer = tree.update(cx, |tree, cx| tree.open_buffer(path, cx));
@@ -3263,7 +3279,7 @@ mod tests {
     }
 
     #[gpui::test]
-    async fn test_rescan_with_gitignore(mut cx: gpui::TestAppContext) {
+    async fn test_rescan_with_gitignore(cx: gpui::TestAppContext) {
         let dir = temp_tree(json!({
             ".git": {},
             ".gitignore": "ignored-dir\n",
@@ -3275,8 +3291,14 @@ mod tests {
             }
         }));
 
-        let tree = cx
-            .add_model(|cx| Worktree::local(dir.path(), Default::default(), Arc::new(RealFs), cx));
+        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;
         tree.flush_fs_events(&cx).await;