Start work on implementing ::file on remote WorktreeHandles

Max Brunsfeld created

Change summary

zed/src/editor/buffer.rs  |  8 ++--
zed/src/workspace.rs      | 56 ++++++++++++++++-----------
zed/src/worktree.rs       | 80 +++++++++++++++++++---------------------
zed/src/worktree/fuzzy.rs |  2 
4 files changed, 76 insertions(+), 70 deletions(-)

Detailed changes

zed/src/editor/buffer.rs 🔗

@@ -2687,7 +2687,7 @@ mod tests {
             cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
                 .await;
 
-            let file1 = cx.update(|cx| tree.file("file1", cx)).await;
+            let file1 = cx.update(|cx| tree.file("file1", cx)).await.unwrap();
             let buffer1 = cx.add_model(|cx| {
                 Buffer::from_history(0, History::new("abc".into()), Some(file1), None, cx)
             });
@@ -2747,7 +2747,7 @@ mod tests {
 
             // When a file is deleted, the buffer is considered dirty.
             let events = Rc::new(RefCell::new(Vec::new()));
-            let file2 = cx.update(|cx| tree.file("file2", cx)).await;
+            let file2 = cx.update(|cx| tree.file("file2", cx)).await.unwrap();
             let buffer2 = cx.add_model(|cx: &mut ModelContext<Buffer>| {
                 cx.subscribe(&cx.handle(), {
                     let events = events.clone();
@@ -2766,7 +2766,7 @@ mod tests {
 
             // When a file is already dirty when deleted, we don't emit a Dirtied event.
             let events = Rc::new(RefCell::new(Vec::new()));
-            let file3 = cx.update(|cx| tree.file("file3", cx)).await;
+            let file3 = cx.update(|cx| tree.file("file3", cx)).await.unwrap();
             let buffer3 = cx.add_model(|cx: &mut ModelContext<Buffer>| {
                 cx.subscribe(&cx.handle(), {
                     let events = events.clone();
@@ -2799,7 +2799,7 @@ mod tests {
             .await;
 
         let abs_path = dir.path().join("the-file");
-        let file = cx.update(|cx| tree.file("the-file", cx)).await;
+        let file = cx.update(|cx| tree.file("the-file", cx)).await.unwrap();
         let buffer = cx.add_model(|cx| {
             Buffer::from_history(
                 0,

zed/src/workspace.rs 🔗

@@ -10,7 +10,7 @@ use crate::{
     worktree::{FileHandle, Worktree, WorktreeHandle},
     AppState,
 };
-use anyhow::anyhow;
+use anyhow::{anyhow, Result};
 use gpui::{
     color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
     AsyncAppContext, ClipboardItem, Entity, ModelHandle, MutableAppContext, PathPromptOptions,
@@ -23,6 +23,7 @@ use postage::watch;
 use smol::prelude::*;
 use std::{
     collections::{hash_map::Entry, HashMap, HashSet},
+    convert::TryInto,
     future::Future,
     path::{Path, PathBuf},
     sync::Arc,
@@ -404,15 +405,13 @@ impl Workspace {
             .map(|(abs_path, file)| {
                 let is_file = bg.spawn(async move { abs_path.is_file() });
                 cx.spawn(|this, mut cx| async move {
-                    let file = file.await;
-                    let is_file = is_file.await;
-                    this.update(&mut cx, |this, cx| {
-                        if is_file {
-                            this.open_entry(file.entry_id(), cx)
-                        } else {
-                            None
+                    if let Ok(file) = file.await {
+                        if is_file.await {
+                            return this
+                                .update(&mut cx, |this, cx| this.open_entry(file.entry_id(), cx));
                         }
-                    })
+                    }
+                    None
                 })
             })
             .collect::<Vec<_>>();
@@ -425,7 +424,11 @@ impl Workspace {
         }
     }
 
-    fn file_for_path(&mut self, abs_path: &Path, cx: &mut ViewContext<Self>) -> Task<FileHandle> {
+    fn file_for_path(
+        &mut self,
+        abs_path: &Path,
+        cx: &mut ViewContext<Self>,
+    ) -> Task<Result<FileHandle>> {
         for tree in self.worktrees.iter() {
             if let Some(relative_path) = tree
                 .read(cx)
@@ -546,12 +549,11 @@ impl Workspace {
 
             cx.as_mut()
                 .spawn(|mut cx| async move {
-                    let file = file.await;
-                    let history = cx.read(|cx| file.load_history(cx));
-                    let history = cx.background_executor().spawn(history).await;
-
-                    *tx.borrow_mut() = Some(match history {
-                        Ok(history) => Ok(Box::new(cx.add_model(|cx| {
+                    let buffer = async move {
+                        let file = file.await?;
+                        let history = cx.read(|cx| file.load_history(cx));
+                        let history = cx.background_executor().spawn(history).await?;
+                        let buffer = cx.add_model(|cx| {
                             let language = language_registry.select_language(path);
                             Buffer::from_history(
                                 replica_id,
@@ -560,9 +562,11 @@ impl Workspace {
                                 language.cloned(),
                                 cx,
                             )
-                        }))),
-                        Err(error) => Err(Arc::new(error)),
-                    })
+                        });
+                        Ok(Box::new(buffer) as Box<dyn ItemHandle>)
+                    }
+                    .await;
+                    *tx.borrow_mut() = Some(buffer.map_err(Arc::new));
                 })
                 .detach();
         }
@@ -612,10 +616,14 @@ impl Workspace {
                 cx.prompt_for_new_path(&start_path, move |path, cx| {
                     if let Some(path) = path {
                         cx.spawn(|mut cx| async move {
-                            let file = handle
-                                .update(&mut cx, |me, cx| me.file_for_path(&path, cx))
-                                .await;
-                            if let Err(error) = cx.update(|cx| item.save(Some(file), cx)).await {
+                            let result = async move {
+                                let file = handle
+                                    .update(&mut cx, |me, cx| me.file_for_path(&path, cx))
+                                    .await?;
+                                cx.update(|cx| item.save(Some(file), cx)).await
+                            }
+                            .await;
+                            if let Err(error) = result {
                                 error!("failed to save item: {:?}, ", error);
                             }
                         })
@@ -728,6 +736,8 @@ impl Workspace {
             let worktree = open_worktree_response
                 .worktree
                 .ok_or_else(|| anyhow!("empty worktree"))?;
+
+            let worktree_id = worktree_id.try_into().unwrap();
             this.update(&mut cx, |workspace, cx| {
                 let worktree = cx.add_model(|cx| {
                     Worktree::remote(worktree_id, worktree, rpc, connection_id, cx)

zed/src/worktree.rs 🔗

@@ -30,7 +30,7 @@ use std::{
     os::unix::{ffi::OsStrExt, fs::MetadataExt},
     path::{Path, PathBuf},
     sync::{Arc, Weak},
-    time::{Duration, SystemTime, UNIX_EPOCH},
+    time::{Duration, SystemTime},
 };
 
 use self::{char_bag::CharBag, ignore::IgnoreStack};
@@ -61,7 +61,7 @@ impl Worktree {
     }
 
     pub fn remote(
-        id: u64,
+        id: usize,
         worktree: proto::Worktree,
         rpc: rpc::Client,
         connection_id: ConnectionId,
@@ -155,7 +155,7 @@ impl LocalWorktree {
     fn new(path: impl Into<Arc<Path>>, cx: &mut ModelContext<Worktree>) -> Self {
         let abs_path = path.into();
         let (scan_state_tx, scan_state_rx) = smol::channel::unbounded();
-        let id = cx.model_id() as u64;
+        let id = cx.model_id();
         let snapshot = Snapshot {
             id,
             scan_id: 0,
@@ -374,7 +374,7 @@ impl fmt::Debug for LocalWorktree {
 }
 
 pub struct RemoteWorktree {
-    id: u64,
+    id: usize,
     snapshot: Snapshot,
     handles: Arc<Mutex<HashMap<Arc<Path>, Weak<Mutex<FileHandleState>>>>>,
     rpc: rpc::Client,
@@ -383,7 +383,7 @@ pub struct RemoteWorktree {
 
 impl RemoteWorktree {
     fn new(
-        id: u64,
+        id: usize,
         worktree: proto::Worktree,
         rpc: rpc::Client,
         connection_id: ConnectionId,
@@ -434,7 +434,7 @@ impl RemoteWorktree {
 
 #[derive(Clone)]
 pub struct Snapshot {
-    id: u64,
+    id: usize,
     scan_id: usize,
     abs_path: Arc<Path>,
     root_name: String,
@@ -871,7 +871,7 @@ impl BackgroundScanner {
         snapshot: Arc<Mutex<Snapshot>>,
         handles: Arc<Mutex<HashMap<Arc<Path>, Weak<Mutex<FileHandleState>>>>>,
         notify: Sender<ScanState>,
-        worktree_id: u64,
+        worktree_id: usize,
     ) -> Self {
         let mut scanner = Self {
             root_char_bag: Default::default(),
@@ -1411,37 +1411,33 @@ impl WorktreeHandle for ModelHandle<Worktree> {
                 cx.spawn(|cx| async move {
                     let mtime = cx
                         .background_executor()
-                        .spawn(async move {
-                            if let Ok(metadata) = fs::metadata(&abs_path) {
-                                metadata.modified().unwrap()
-                            } else {
-                                UNIX_EPOCH
-                            }
-                        })
-                        .await;
+                        .spawn(async move { fs::metadata(&abs_path) })
+                        .await?
+                        .modified()?;
                     let state = handle.read_with(&cx, |tree, _| {
                         let mut handles = tree.as_local().unwrap().handles.lock();
-                        if let Some(state) = handles.get(&path).and_then(Weak::upgrade) {
-                            state
-                        } else {
-                            let handle_state = if let Some(entry) = tree.entry_for_path(&path) {
-                                FileHandleState {
-                                    path: entry.path().clone(),
-                                    is_deleted: false,
-                                    mtime,
-                                }
-                            } else {
-                                FileHandleState {
-                                    path: path.clone(),
-                                    is_deleted: !tree.path_is_pending(path),
-                                    mtime,
-                                }
-                            };
+                        handles
+                            .get(&path)
+                            .and_then(Weak::upgrade)
+                            .unwrap_or_else(|| {
+                                let handle_state = if let Some(entry) = tree.entry_for_path(&path) {
+                                    FileHandleState {
+                                        path: entry.path().clone(),
+                                        is_deleted: false,
+                                        mtime,
+                                    }
+                                } else {
+                                    FileHandleState {
+                                        path: path.clone(),
+                                        is_deleted: !tree.path_is_pending(path),
+                                        mtime,
+                                    }
+                                };
 
-                            let state = Arc::new(Mutex::new(handle_state.clone()));
-                            handles.insert(handle_state.path, Arc::downgrade(&state));
-                            state
-                        }
+                                let state = Arc::new(Mutex::new(handle_state.clone()));
+                                handles.insert(handle_state.path, Arc::downgrade(&state));
+                                state
+                            })
                     });
                     Ok(FileHandle {
                         worktree: handle.clone(),
@@ -1458,7 +1454,7 @@ impl WorktreeHandle for ModelHandle<Worktree> {
                         .request(
                             connection_id,
                             proto::OpenFile {
-                                worktree_id,
+                                worktree_id: worktree_id as u64,
                                 path: path.to_string_lossy().to_string(),
                             },
                         )
@@ -1724,7 +1720,7 @@ mod tests {
 
         let buffer = cx.add_model(|cx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), cx));
 
-        let file = cx.update(|cx| tree.file("", cx)).await;
+        let file = cx.update(|cx| tree.file("", cx)).await.unwrap();
         cx.update(|cx| {
             assert_eq!(file.path().file_name(), None);
             smol::block_on(file.save(buffer.read(cx).snapshot().text(), cx.as_ref())).unwrap();
@@ -1751,11 +1747,11 @@ mod tests {
         }));
 
         let tree = cx.add_model(|cx| Worktree::local(dir.path(), cx));
-        let file2 = cx.update(|cx| tree.file("a/file2", cx)).await;
-        let file3 = cx.update(|cx| tree.file("a/file3", cx)).await;
-        let file4 = cx.update(|cx| tree.file("b/c/file4", cx)).await;
-        let file5 = cx.update(|cx| tree.file("b/c/file5", cx)).await;
-        let non_existent_file = cx.update(|cx| tree.file("a/file_x", cx)).await;
+        let file2 = cx.update(|cx| tree.file("a/file2", cx)).await.unwrap();
+        let file3 = cx.update(|cx| tree.file("a/file3", cx)).await.unwrap();
+        let file4 = cx.update(|cx| tree.file("b/c/file4", cx)).await.unwrap();
+        let file5 = cx.update(|cx| tree.file("b/c/file5", cx)).await.unwrap();
+        let non_existent_file = cx.update(|cx| tree.file("a/file_x", cx)).await.unwrap();
 
         // After scanning, the worktree knows which files exist and which don't.
         cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())

zed/src/worktree/fuzzy.rs 🔗

@@ -22,7 +22,7 @@ pub struct MatchCandidate<'a> {
 pub struct PathMatch {
     pub score: f64,
     pub positions: Vec<usize>,
-    pub tree_id: u64,
+    pub tree_id: usize,
     pub path: Arc<Path>,
     pub include_root_name: bool,
 }