WIP

Nathan Sobo and Max Brunsfeld created

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

zed/src/editor/buffer/mod.rs        |  10 +-
zed/src/editor/buffer_view.rs       |   5 
zed/src/file_finder.rs              |  27 +++---
zed/src/workspace/pane.rs           |   8 +
zed/src/workspace/workspace.rs      |  47 ++++++----
zed/src/workspace/workspace_view.rs |  69 +++++++++------
zed/src/worktree.rs                 | 134 ++++++++++++------------------
zed/src/worktree/fuzzy.rs           |  47 ++++++----
8 files changed, 176 insertions(+), 171 deletions(-)

Detailed changes

zed/src/editor/buffer/mod.rs 🔗

@@ -18,7 +18,7 @@ use crate::{
     worktree::FileHandle,
 };
 use anyhow::{anyhow, Result};
-use gpui::{AppContext, Entity, ModelContext};
+use gpui::{Entity, ModelContext};
 use lazy_static::lazy_static;
 use rand::prelude::*;
 use std::{
@@ -26,7 +26,7 @@ use std::{
     hash::BuildHasher,
     iter::{self, Iterator},
     ops::{AddAssign, Range},
-    path::PathBuf,
+    path::Path,
     str,
     sync::Arc,
     time::{Duration, Instant},
@@ -429,11 +429,11 @@ impl Buffer {
         }
     }
 
-    pub fn path(&self, app: &AppContext) -> Option<PathBuf> {
-        self.file.as_ref().map(|file| file.path(app))
+    pub fn path(&self) -> Option<&Arc<Path>> {
+        self.file.as_ref().map(|file| file.path())
     }
 
-    pub fn entry_id(&self) -> Option<(usize, u64)> {
+    pub fn entry_id(&self) -> Option<(usize, Arc<Path>)> {
         self.file.as_ref().map(|file| file.entry_id())
     }
 

zed/src/editor/buffer_view.rs 🔗

@@ -20,6 +20,7 @@ use std::{
     fmt::Write,
     iter::FromIterator,
     ops::Range,
+    path::Path,
     sync::Arc,
     time::Duration,
 };
@@ -1375,7 +1376,7 @@ impl workspace::ItemView for BufferView {
     }
 
     fn title(&self, app: &AppContext) -> std::string::String {
-        if let Some(path) = self.buffer.read(app).path(app) {
+        if let Some(path) = self.buffer.read(app).path() {
             path.file_name()
                 .expect("buffer's path is always to a file")
                 .to_string_lossy()
@@ -1385,7 +1386,7 @@ impl workspace::ItemView for BufferView {
         }
     }
 
-    fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)> {
+    fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)> {
         self.buffer.read(app).entry_id()
     }
 

zed/src/file_finder.rs 🔗

@@ -14,7 +14,7 @@ use gpui::{
     AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext,
     ViewHandle, WeakViewHandle,
 };
-use std::{cmp, path::Path};
+use std::{cmp, path::Path, sync::Arc};
 
 pub struct FileFinder {
     handle: WeakViewHandle<Self>,
@@ -44,7 +44,7 @@ pub fn init(app: &mut MutableAppContext) {
 }
 
 pub enum Event {
-    Selected(usize, u64),
+    Selected(usize, Arc<Path>),
     Dismissed,
 }
 
@@ -137,18 +137,18 @@ impl FileFinder {
         app: &AppContext,
     ) -> Option<ElementBox> {
         let tree_id = path_match.tree_id;
-        let entry_id = path_match.entry_id;
 
         self.worktree(tree_id, app).map(|_| {
-            let path = &path_match.path;
-            let file_name = Path::new(path)
+            let path = path_match.path.clone();
+            let path_string = &path_match.path_string;
+            let file_name = Path::new(&path_string)
                 .file_name()
                 .unwrap_or_default()
                 .to_string_lossy()
                 .to_string();
 
             let path_positions = path_match.positions.clone();
-            let file_name_start = path.chars().count() - file_name.chars().count();
+            let file_name_start = path_string.chars().count() - file_name.chars().count();
             let mut file_name_positions = Vec::new();
             file_name_positions.extend(path_positions.iter().filter_map(|pos| {
                 if pos >= &file_name_start {
@@ -191,7 +191,7 @@ impl FileFinder {
                                 )
                                 .with_child(
                                     Label::new(
-                                        path.into(),
+                                        path_string.into(),
                                         settings.ui_font_family,
                                         settings.ui_font_size,
                                     )
@@ -217,7 +217,7 @@ impl FileFinder {
 
             EventHandler::new(container.boxed())
                 .on_mouse_down(move |ctx| {
-                    ctx.dispatch_action("file_finder:select", (tree_id, entry_id));
+                    ctx.dispatch_action("file_finder:select", (tree_id, path.clone()));
                     true
                 })
                 .named("match")
@@ -245,8 +245,8 @@ impl FileFinder {
         ctx: &mut ViewContext<WorkspaceView>,
     ) {
         match event {
-            Event::Selected(tree_id, entry_id) => {
-                workspace_view.open_entry((*tree_id, *entry_id), ctx);
+            Event::Selected(tree_id, path) => {
+                workspace_view.open_entry((*tree_id, path.clone()), ctx);
                 workspace_view.dismiss_modal(ctx);
             }
             Event::Dismissed => {
@@ -329,13 +329,12 @@ impl FileFinder {
 
     fn confirm(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         if let Some(m) = self.matches.get(self.selected) {
-            ctx.emit(Event::Selected(m.tree_id, m.entry_id));
+            ctx.emit(Event::Selected(m.tree_id, m.path.clone()));
         }
     }
 
-    fn select(&mut self, entry: &(usize, u64), ctx: &mut ViewContext<Self>) {
-        let (tree_id, entry_id) = *entry;
-        ctx.emit(Event::Selected(tree_id, entry_id));
+    fn select(&mut self, (tree_id, path): &(usize, Arc<Path>), ctx: &mut ViewContext<Self>) {
+        ctx.emit(Event::Selected(*tree_id, path.clone()));
     }
 
     fn spawn_search(&mut self, query: String, ctx: &mut ViewContext<Self>) {

zed/src/workspace/pane.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
     keymap::Binding,
     AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext,
 };
-use std::cmp;
+use std::{cmp, path::Path, sync::Arc};
 
 pub fn init(app: &mut MutableAppContext) {
     app.add_action(
@@ -105,7 +105,11 @@ impl Pane {
         self.items.get(self.active_item).cloned()
     }
 
-    pub fn activate_entry(&mut self, entry_id: (usize, u64), ctx: &mut ViewContext<Self>) -> bool {
+    pub fn activate_entry(
+        &mut self,
+        entry_id: (usize, Arc<Path>),
+        ctx: &mut ViewContext<Self>,
+    ) -> bool {
         if let Some(index) = self.items.iter().position(|item| {
             item.entry_id(ctx.as_ref())
                 .map_or(false, |id| id == entry_id)

zed/src/workspace/workspace.rs 🔗

@@ -138,10 +138,22 @@ impl Workspace {
 
     pub fn open_entry(
         &mut self,
-        entry: (usize, u64),
+        (worktree_id, path): (usize, Arc<Path>),
         ctx: &mut ModelContext<'_, Self>,
     ) -> anyhow::Result<Pin<Box<dyn Future<Output = OpenResult> + Send>>> {
-        if let Some(item) = self.items.get(&entry).cloned() {
+        let worktree = self
+            .worktrees
+            .get(&worktree_id)
+            .cloned()
+            .ok_or_else(|| anyhow!("worktree {} does not exist", worktree_id,))?;
+
+        let inode = worktree
+            .read(ctx)
+            .inode_for_path(&path)
+            .ok_or_else(|| anyhow!("path {:?} does not exist", path))?;
+
+        let item_key = (worktree_id, inode);
+        if let Some(item) = self.items.get(&item_key).cloned() {
             return Ok(async move {
                 match item {
                     OpenedItem::Loaded(handle) => {
@@ -159,25 +171,20 @@ impl Workspace {
             .boxed());
         }
 
-        let worktree = self
-            .worktrees
-            .get(&entry.0)
-            .cloned()
-            .ok_or(anyhow!("worktree {} does not exist", entry.0,))?;
-
         let replica_id = self.replica_id;
-        let file = worktree.file(entry.1, ctx.as_ref())?;
+        let file = worktree.file(path.clone(), ctx.as_ref())?;
         let history = file.load_history(ctx.as_ref());
         let buffer = async move { Ok(Buffer::from_history(replica_id, file, history.await?)) };
 
         let (mut tx, rx) = watch::channel(None);
-        self.items.insert(entry, OpenedItem::Loading(rx));
+        self.items.insert(item_key, OpenedItem::Loading(rx));
         ctx.spawn(
             buffer,
             move |me, buffer: anyhow::Result<Buffer>, ctx| match buffer {
                 Ok(buffer) => {
                     let handle = Box::new(ctx.add_model(|_| buffer)) as Box<dyn ItemHandle>;
-                    me.items.insert(entry, OpenedItem::Loaded(handle.clone()));
+                    me.items
+                        .insert(item_key, OpenedItem::Loaded(handle.clone()));
                     ctx.spawn(
                         async move {
                             tx.update(|value| *value = Some(Ok(handle))).await;
@@ -199,7 +206,7 @@ impl Workspace {
         )
         .detach();
 
-        self.open_entry(entry, ctx)
+        self.open_entry((worktree_id, path), ctx)
     }
 
     fn on_worktree_updated(&mut self, _: ModelHandle<Worktree>, ctx: &mut ModelContext<Self>) {
@@ -213,18 +220,20 @@ impl Entity for Workspace {
 
 #[cfg(test)]
 pub trait WorkspaceHandle {
-    fn file_entries(&self, app: &AppContext) -> Vec<(usize, u64)>;
+    fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc<Path>)>;
 }
 
 #[cfg(test)]
 impl WorkspaceHandle for ModelHandle<Workspace> {
-    fn file_entries(&self, app: &AppContext) -> Vec<(usize, u64)> {
+    fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc<Path>)> {
         self.read(app)
             .worktrees()
             .iter()
             .flat_map(|tree| {
                 let tree_id = tree.id();
-                tree.read(app).files(0).map(move |f| (tree_id, f.inode()))
+                tree.read(app)
+                    .files(0)
+                    .map(move |f| (tree_id, f.path().clone()))
             })
             .collect::<Vec<_>>()
     }
@@ -253,14 +262,14 @@ mod tests {
 
             // Get the first file entry.
             let tree = app.read(|ctx| workspace.read(ctx).worktrees.iter().next().unwrap().clone());
-            let file_inode = app.read(|ctx| tree.read(ctx).files(0).next().unwrap().inode());
-            let entry = (tree.id(), file_inode);
+            let path = app.read(|ctx| tree.read(ctx).files(0).next().unwrap().path().clone());
+            let entry = (tree.id(), path);
 
             // Open the same entry twice before it finishes loading.
             let (future_1, future_2) = workspace.update(&mut app, |w, app| {
                 (
-                    w.open_entry(entry, app).unwrap(),
-                    w.open_entry(entry, app).unwrap(),
+                    w.open_entry(entry.clone(), app).unwrap(),
+                    w.open_entry(entry.clone(), app).unwrap(),
                 )
             });
 

zed/src/workspace/workspace_view.rs 🔗

@@ -6,7 +6,11 @@ use gpui::{
     ClipboardItem, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
 };
 use log::error;
-use std::{collections::HashSet, path::PathBuf};
+use std::{
+    collections::HashSet,
+    path::{Path, PathBuf},
+    sync::Arc,
+};
 
 pub fn init(app: &mut MutableAppContext) {
     app.add_action("workspace:save", WorkspaceView::save_active_item);
@@ -19,7 +23,7 @@ pub fn init(app: &mut MutableAppContext) {
 
 pub trait ItemView: View {
     fn title(&self, app: &AppContext) -> String;
-    fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)>;
+    fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)>;
     fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
     where
         Self: Sized,
@@ -42,7 +46,7 @@ pub trait ItemView: View {
 
 pub trait ItemViewHandle: Send + Sync {
     fn title(&self, app: &AppContext) -> String;
-    fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)>;
+    fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)>;
     fn boxed_clone(&self) -> Box<dyn ItemViewHandle>;
     fn clone_on_split(&self, app: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>;
     fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext);
@@ -57,7 +61,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
         self.read(app).title(app)
     }
 
-    fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)> {
+    fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)> {
         self.read(app).entry_id(app)
     }
 
@@ -124,7 +128,7 @@ pub struct WorkspaceView {
     center: PaneGroup,
     panes: Vec<ViewHandle<Pane>>,
     active_pane: ViewHandle<Pane>,
-    loading_entries: HashSet<(usize, u64)>,
+    loading_entries: HashSet<(usize, Arc<Path>)>,
 }
 
 impl WorkspaceView {
@@ -189,24 +193,23 @@ impl WorkspaceView {
         }
     }
 
-    pub fn open_entry(&mut self, entry: (usize, u64), ctx: &mut ViewContext<Self>) {
+    pub fn open_entry(&mut self, entry: (usize, Arc<Path>), ctx: &mut ViewContext<Self>) {
         if self.loading_entries.contains(&entry) {
             return;
         }
 
         if self
             .active_pane()
-            .update(ctx, |pane, ctx| pane.activate_entry(entry, ctx))
+            .update(ctx, |pane, ctx| pane.activate_entry(entry.clone(), ctx))
         {
             return;
         }
 
-        self.loading_entries.insert(entry);
+        self.loading_entries.insert(entry.clone());
 
-        match self
-            .workspace
-            .update(ctx, |workspace, ctx| workspace.open_entry(entry, ctx))
-        {
+        match self.workspace.update(ctx, |workspace, ctx| {
+            workspace.open_entry(entry.clone(), ctx)
+        }) {
             Err(error) => error!("{}", error),
             Ok(item) => {
                 let settings = self.settings.clone();
@@ -396,32 +399,35 @@ mod tests {
             app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
                 .await;
             let entries = app.read(|ctx| workspace.file_entries(ctx));
-            let file1 = entries[0];
-            let file2 = entries[1];
-            let file3 = entries[2];
+            let file1 = entries[0].clone();
+            let file2 = entries[1].clone();
+            let file3 = entries[2].clone();
 
             let (_, workspace_view) =
                 app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
             let pane = app.read(|ctx| workspace_view.read(ctx).active_pane().clone());
 
             // Open the first entry
-            workspace_view.update(&mut app, |w, ctx| w.open_entry(file1, ctx));
+            workspace_view.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx));
             pane.condition(&app, |pane, _| pane.items().len() == 1)
                 .await;
 
             // Open the second entry
-            workspace_view.update(&mut app, |w, ctx| w.open_entry(file2, ctx));
+            workspace_view.update(&mut app, |w, ctx| w.open_entry(file2.clone(), ctx));
             pane.condition(&app, |pane, _| pane.items().len() == 2)
                 .await;
             app.read(|ctx| {
                 let pane = pane.read(ctx);
-                assert_eq!(pane.active_item().unwrap().entry_id(ctx), Some(file2));
+                assert_eq!(
+                    pane.active_item().unwrap().entry_id(ctx),
+                    Some(file2.clone())
+                );
             });
 
             // Open the first entry again
-            workspace_view.update(&mut app, |w, ctx| w.open_entry(file1, ctx));
+            workspace_view.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx));
             pane.condition(&app, move |pane, ctx| {
-                pane.active_item().unwrap().entry_id(ctx) == Some(file1)
+                pane.active_item().unwrap().entry_id(ctx) == Some(file1.clone())
             })
             .await;
             app.read(|ctx| {
@@ -430,8 +436,8 @@ mod tests {
 
             // Open the third entry twice concurrently
             workspace_view.update(&mut app, |w, ctx| {
-                w.open_entry(file3, ctx);
-                w.open_entry(file3, ctx);
+                w.open_entry(file3.clone(), ctx);
+                w.open_entry(file3.clone(), ctx);
             });
             pane.condition(&app, |pane, _| pane.items().len() == 3)
                 .await;
@@ -456,18 +462,21 @@ mod tests {
             app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
                 .await;
             let entries = app.read(|ctx| workspace.file_entries(ctx));
-            let file1 = entries[0];
+            let file1 = entries[0].clone();
 
             let (window_id, workspace_view) =
                 app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
             let pane_1 = app.read(|ctx| workspace_view.read(ctx).active_pane().clone());
 
-            workspace_view.update(&mut app, |w, ctx| w.open_entry(file1, ctx));
-            pane_1
-                .condition(&app, move |pane, ctx| {
-                    pane.active_item().and_then(|i| i.entry_id(ctx)) == Some(file1)
-                })
-                .await;
+            workspace_view.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx));
+            {
+                let file1 = file1.clone();
+                pane_1
+                    .condition(&app, move |pane, ctx| {
+                        pane.active_item().and_then(|i| i.entry_id(ctx)) == Some(file1.clone())
+                    })
+                    .await;
+            }
 
             app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
             app.update(|ctx| {
@@ -475,7 +484,7 @@ mod tests {
                 assert_ne!(pane_1, pane_2);
 
                 let pane2_item = pane_2.read(ctx).active_item().unwrap();
-                assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1));
+                assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1.clone()));
 
                 ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
                 let workspace_view = workspace_view.read(ctx);

zed/src/worktree.rs 🔗

@@ -54,7 +54,7 @@ pub struct Worktree {
 #[derive(Clone)]
 pub struct FileHandle {
     worktree: ModelHandle<Worktree>,
-    inode: u64,
+    path: Arc<Path>,
 }
 
 impl Worktree {
@@ -152,25 +152,14 @@ impl Worktree {
         path.starts_with(&self.snapshot.path)
     }
 
-    pub fn has_inode(&self, inode: u64) -> bool {
-        todo!()
-        // self.snapshot.entries.get(&inode).is_some()
-    }
-
-    pub fn abs_path_for_inode(&self, ino: u64) -> Result<PathBuf> {
-        let mut result = self.snapshot.path.to_path_buf();
-        result.push(self.path_for_inode(ino, false)?);
-        Ok(result)
-    }
-
     pub fn load_history(
         &self,
-        ino: u64,
+        relative_path: &Path,
         ctx: &AppContext,
     ) -> impl Future<Output = Result<History>> {
-        let path = self.abs_path_for_inode(ino);
+        let path = self.snapshot.path.join(relative_path);
         ctx.background_executor().spawn(async move {
-            let mut file = std::fs::File::open(&path?)?;
+            let mut file = std::fs::File::open(&path)?;
             let mut base_text = String::new();
             file.read_to_string(&mut base_text)?;
             Ok(History::new(Arc::from(base_text)))
@@ -179,14 +168,14 @@ impl Worktree {
 
     pub fn save<'a>(
         &self,
-        ino: u64,
+        relative_path: &Path,
         content: BufferSnapshot,
         ctx: &AppContext,
     ) -> Task<Result<()>> {
-        let path = self.abs_path_for_inode(ino);
+        let path = self.snapshot.path.join(relative_path);
         ctx.background_executor().spawn(async move {
             let buffer_size = content.text_summary().bytes.min(10 * 1024);
-            let file = std::fs::File::create(&path?)?;
+            let file = std::fs::File::create(&path)?;
             let mut writer = std::io::BufWriter::with_capacity(buffer_size, file);
             for chunk in content.fragments() {
                 writer.write(chunk.as_bytes())?;
@@ -258,7 +247,7 @@ impl Snapshot {
         }
     }
 
-    fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
+    pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
         self.entry_for_path(path.as_ref()).map(|e| e.inode())
     }
 
@@ -288,10 +277,6 @@ impl Snapshot {
         }
     }
 
-    pub fn path_for_inode(&self, mut inode: u64, include_root: bool) -> Result<PathBuf> {
-        todo!("this method should go away")
-    }
-
     fn insert_entry(&mut self, entry: Entry) {
         if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
             self.insert_ignore_file(entry.path());
@@ -362,24 +347,21 @@ impl fmt::Debug for Snapshot {
 }
 
 impl FileHandle {
-    pub fn path(&self, ctx: &AppContext) -> PathBuf {
-        self.worktree
-            .read(ctx)
-            .path_for_inode(self.inode, false)
-            .unwrap()
+    pub fn path(&self) -> &Arc<Path> {
+        &self.path
     }
 
     pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
-        self.worktree.read(ctx).load_history(self.inode, ctx)
+        self.worktree.read(ctx).load_history(&self.path, ctx)
     }
 
     pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
         let worktree = self.worktree.read(ctx);
-        worktree.save(self.inode, content, ctx)
+        worktree.save(&self.path, content, ctx)
     }
 
-    pub fn entry_id(&self) -> (usize, u64) {
-        (self.worktree.id(), self.inode)
+    pub fn entry_id(&self) -> (usize, Arc<Path>) {
+        (self.worktree.id(), self.path.clone())
     }
 }
 
@@ -402,13 +384,20 @@ pub enum Entry {
 }
 
 impl Entry {
-    fn path(&self) -> &Arc<Path> {
+    pub fn path(&self) -> &Arc<Path> {
         match self {
             Entry::Dir { path, .. } => path,
             Entry::File { path, .. } => path,
         }
     }
 
+    pub fn inode(&self) -> u64 {
+        match self {
+            Entry::Dir { inode, .. } => *inode,
+            Entry::File { inode, .. } => *inode,
+        }
+    }
+
     fn is_ignored(&self) -> Option<bool> {
         match self {
             Entry::Dir { is_ignored, .. } => *is_ignored,
@@ -423,13 +412,6 @@ impl Entry {
         }
     }
 
-    pub fn inode(&self) -> u64 {
-        match self {
-            Entry::Dir { inode, .. } => *inode,
-            Entry::File { inode, .. } => *inode,
-        }
-    }
-
     fn is_dir(&self) -> bool {
         matches!(self, Entry::Dir { .. })
     }
@@ -683,7 +665,7 @@ impl BackgroundScanner {
             });
         } else {
             self.snapshot.lock().insert_entry(Entry::File {
-                path_entry: PathEntry::new(inode, &relative_path),
+                path_entry: PathEntry::new(inode, relative_path.clone()),
                 path: relative_path,
                 inode,
                 is_symlink,
@@ -729,7 +711,7 @@ impl BackgroundScanner {
                 });
             } else {
                 new_entries.push(Entry::File {
-                    path_entry: PathEntry::new(child_inode, &child_relative_path),
+                    path_entry: PathEntry::new(child_inode, child_relative_path.clone()),
                     path: child_relative_path,
                     inode: child_inode,
                     is_symlink: child_is_symlink,
@@ -956,11 +938,12 @@ impl BackgroundScanner {
             .is_symlink();
         let relative_path_with_root = root_path
             .parent()
-            .map_or(path, |parent| path.strip_prefix(parent).unwrap());
+            .map_or(path, |parent| path.strip_prefix(parent).unwrap())
+            .into();
 
         let entry = if metadata.file_type().is_dir() {
             Entry::Dir {
-                path: Arc::from(relative_path_with_root),
+                path: relative_path_with_root,
                 inode,
                 is_symlink,
                 pending: true,
@@ -968,8 +951,8 @@ impl BackgroundScanner {
             }
         } else {
             Entry::File {
-                path_entry: PathEntry::new(inode, relative_path_with_root),
-                path: Arc::from(relative_path_with_root),
+                path_entry: PathEntry::new(inode, relative_path_with_root.clone()),
+                path: relative_path_with_root,
                 inode,
                 is_symlink,
                 is_ignored: None,
@@ -987,19 +970,18 @@ struct ScanJob {
 }
 
 pub trait WorktreeHandle {
-    fn file(&self, entry_id: u64, app: &AppContext) -> Result<FileHandle>;
+    fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle>;
 }
 
 impl WorktreeHandle for ModelHandle<Worktree> {
-    fn file(&self, inode: u64, app: &AppContext) -> Result<FileHandle> {
-        if self.read(app).has_inode(inode) {
-            Ok(FileHandle {
+    fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle> {
+        self.read(app)
+            .entry_for_path(&path)
+            .map(|entry| FileHandle {
                 worktree: self.clone(),
-                inode,
+                path: entry.path().clone(),
             })
-        } else {
-            Err(anyhow!("entry does not exist in tree"))
-        }
+            .ok_or_else(|| anyhow!("path does not exist in tree"))
     }
 }
 
@@ -1125,14 +1107,13 @@ mod tests {
                     ctx.thread_pool().clone(),
                 )
                 .iter()
-                .map(|result| tree.path_for_inode(result.entry_id, true))
-                .collect::<Result<Vec<PathBuf>, _>>()
-                .unwrap();
+                .map(|result| result.path.clone())
+                .collect::<Vec<Arc<Path>>>();
                 assert_eq!(
                     results,
                     vec![
-                        PathBuf::from("root_link/banana/carrot/date"),
-                        PathBuf::from("root_link/banana/carrot/endive"),
+                        PathBuf::from("root_link/banana/carrot/date").into(),
+                        PathBuf::from("root_link/banana/carrot/endive").into(),
                     ]
                 );
             })
@@ -1152,25 +1133,15 @@ mod tests {
 
             let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
 
-            let file_inode = app.read(|ctx| {
-                let tree = tree.read(ctx);
-                let inode = tree.files(0).next().unwrap().inode();
-                assert_eq!(
-                    tree.path_for_inode(inode, false)
-                        .unwrap()
-                        .file_name()
-                        .unwrap(),
-                    "file1"
-                );
-                inode
-            });
-
-            tree.update(&mut app, |tree, ctx| {
-                smol::block_on(tree.save(file_inode, buffer.snapshot(), ctx.as_ref())).unwrap()
+            let path = tree.update(&mut app, |tree, ctx| {
+                let path = tree.files(0).next().unwrap().path().clone();
+                assert_eq!(path.file_name().unwrap(), "file1");
+                smol::block_on(tree.save(&path, buffer.snapshot(), ctx.as_ref())).unwrap();
+                path
             });
 
             let loaded_history = app
-                .read(|ctx| tree.read(ctx).load_history(file_inode, ctx))
+                .read(|ctx| tree.read(ctx).load_history(&path, ctx))
                 .await
                 .unwrap();
             assert_eq!(loaded_history.base_text.as_ref(), buffer.text());
@@ -1196,15 +1167,16 @@ mod tests {
             app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 2));
 
             let file2 = app.read(|ctx| {
-                let inode = tree.read(ctx).inode_for_path("b/c/file2").unwrap();
-                let file2 = tree.file(inode, ctx).unwrap();
-                assert_eq!(file2.path(ctx), Path::new("b/c/file2"));
+                let file2 = tree.file("b/c/file2", ctx).unwrap();
+                assert_eq!(file2.path().as_ref(), Path::new("b/c/file2"));
                 file2
             });
 
             std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
-            tree.condition(&app, move |_, ctx| file2.path(ctx) == Path::new("d/file2"))
-                .await;
+            tree.condition(&app, move |_, _| {
+                file2.path().as_ref() == Path::new("d/file2")
+            })
+            .await;
         });
     }
 
@@ -1513,7 +1485,7 @@ mod tests {
                 ));
                 if let Entry::File { path_entry, .. } = entry {
                     assert_eq!(
-                        String::from_iter(path_entry.path.iter()),
+                        String::from_iter(path_entry.path_chars.iter()),
                         entry.path().to_str().unwrap()
                     );
                 }

zed/src/worktree/fuzzy.rs 🔗

@@ -14,20 +14,22 @@ const MIN_DISTANCE_PENALTY: f64 = 0.2;
 #[derive(Clone, Debug)]
 pub struct PathEntry {
     pub ino: u64,
-    pub path_chars: CharBag,
-    pub path: Arc<[char]>,
+    pub char_bag: CharBag,
+    pub path_chars: Arc<[char]>,
+    pub path: Arc<Path>,
     pub lowercase_path: Arc<[char]>,
 }
 
 impl PathEntry {
-    pub fn new(ino: u64, path: &Path) -> Self {
-        let path = path.to_string_lossy();
-        let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>().into();
-        let path: Arc<[char]> = path.chars().collect::<Vec<_>>().into();
-        let path_chars = CharBag::from(path.as_ref());
+    pub fn new(ino: u64, path: Arc<Path>) -> Self {
+        let path_str = path.to_string_lossy();
+        let lowercase_path = path_str.to_lowercase().chars().collect::<Vec<_>>().into();
+        let path_chars: Arc<[char]> = path_str.chars().collect::<Vec<_>>().into();
+        let char_bag = CharBag::from(path_chars.as_ref());
 
         Self {
             ino,
+            char_bag,
             path_chars,
             path,
             lowercase_path,
@@ -39,9 +41,9 @@ impl PathEntry {
 pub struct PathMatch {
     pub score: f64,
     pub positions: Vec<usize>,
-    pub path: String,
+    pub path_string: String,
     pub tree_id: usize,
-    pub entry_id: u64,
+    pub path: Arc<Path>,
 }
 
 impl PartialEq for PathMatch {
@@ -199,7 +201,7 @@ fn match_single_tree_paths<'a>(
     best_position_matrix: &mut Vec<usize>,
 ) {
     for path_entry in path_entries {
-        if !path_entry.path_chars.is_superset(query_chars) {
+        if !path_entry.char_bag.is_superset(query_chars) {
             continue;
         }
 
@@ -212,7 +214,7 @@ fn match_single_tree_paths<'a>(
             continue;
         }
 
-        let matrix_len = query.len() * (path_entry.path.len() - skipped_prefix_len);
+        let matrix_len = query.len() * (path_entry.path_chars.len() - skipped_prefix_len);
         score_matrix.clear();
         score_matrix.resize(matrix_len, None);
         best_position_matrix.clear();
@@ -221,7 +223,7 @@ fn match_single_tree_paths<'a>(
         let score = score_match(
             &query[..],
             &lowercase_query[..],
-            &path_entry.path,
+            &path_entry.path_chars,
             &path_entry.lowercase_path,
             skipped_prefix_len,
             smart_case,
@@ -235,8 +237,12 @@ fn match_single_tree_paths<'a>(
         if score > 0.0 {
             results.push(Reverse(PathMatch {
                 tree_id: snapshot.id,
-                entry_id: path_entry.ino,
-                path: path_entry.path.iter().skip(skipped_prefix_len).collect(),
+                path_string: path_entry
+                    .path_chars
+                    .iter()
+                    .skip(skipped_prefix_len)
+                    .collect(),
+                path: path_entry.path.clone(),
                 score,
                 positions: match_positions.clone(),
             }));
@@ -496,12 +502,13 @@ mod tests {
         for (i, path) in paths.iter().enumerate() {
             let lowercase_path: Arc<[char]> =
                 path.to_lowercase().chars().collect::<Vec<_>>().into();
-            let path_chars = CharBag::from(lowercase_path.as_ref());
-            let path = path.chars().collect();
+            let char_bag = CharBag::from(lowercase_path.as_ref());
+            let path_chars = path.chars().collect();
             path_entries.push(PathEntry {
                 ino: i as u64,
+                char_bag,
                 path_chars,
-                path,
+                path: Arc::from(PathBuf::from(path)),
                 lowercase_path,
             });
         }
@@ -540,7 +547,11 @@ mod tests {
             .rev()
             .map(|result| {
                 (
-                    paths[result.0.entry_id as usize].clone(),
+                    paths
+                        .iter()
+                        .copied()
+                        .find(|p| result.0.path.as_ref() == Path::new(p))
+                        .unwrap(),
                     result.0.positions,
                 )
             })