From 79cac1340e25d38d2e303fa656263434c5edcb3e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Jun 2021 12:07:57 +0200 Subject: [PATCH] Replace `Worktree` with an enum For now this enum only contains a `Local` variant, but the next step is to add a `Remote` variant that will be constructed when joining a remote worktree. --- zed/src/editor/buffer.rs | 10 +- zed/src/workspace.rs | 39 ++++--- zed/src/worktree.rs | 217 +++++++++++++++++++++++++++------------ 3 files changed, 181 insertions(+), 85 deletions(-) diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 68ddb22b5acb8b4c7a177a1375a4fb28f19847d4..7db4d7a41733c56c79e2feb319ded7f4c9d7e8e0 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -2682,9 +2682,10 @@ mod tests { "file2": "", "file3": "", })); - let tree = cx.add_model(|cx| Worktree::new(dir.path(), cx)); + let tree = cx.add_model(|cx| Worktree::local(dir.path(), cx)); tree.flush_fs_events(&cx).await; - cx.read(|cx| tree.read(cx).scan_complete()).await; + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; let file1 = cx.update(|cx| tree.file("file1", cx)).await; let buffer1 = cx.add_model(|cx| { @@ -2793,8 +2794,9 @@ 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::new(dir.path(), cx)); - cx.read(|cx| tree.read(cx).scan_complete()).await; + let tree = cx.add_model(|cx| Worktree::local(dir.path(), cx)); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; let abs_path = dir.path().join("the-file"); let file = cx.update(|cx| tree.file("the-file", cx)).await; diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index ec9cceca4ac882f8b85db3c631590ac49747e8c3..4fda4cacd3b93fe9a336c107084797246c2b1c35 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -362,16 +362,21 @@ impl Workspace { } pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool { - self.worktrees - .iter() - .any(|worktree| worktree.read(cx).contains_abs_path(path)) + for worktree in &self.worktrees { + let worktree = worktree.read(cx).as_local(); + if worktree.map_or(false, |w| w.contains_abs_path(path)) { + return true; + } + } + false } pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future + 'static { let futures = self .worktrees .iter() - .map(|worktree| worktree.read(cx).scan_complete()) + .filter_map(|worktree| worktree.read(cx).as_local()) + .map(|worktree| worktree.scan_complete()) .collect::>(); async move { for future in futures { @@ -422,7 +427,11 @@ impl Workspace { fn file_for_path(&mut self, abs_path: &Path, cx: &mut ViewContext) -> Task { for tree in self.worktrees.iter() { - if let Ok(relative_path) = abs_path.strip_prefix(tree.read(cx).abs_path()) { + if let Some(relative_path) = tree + .read(cx) + .as_local() + .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok()) + { return tree.file(relative_path, cx.as_mut()); } } @@ -435,7 +444,7 @@ impl Workspace { path: &Path, cx: &mut ViewContext, ) -> ModelHandle { - let worktree = cx.add_model(|cx| Worktree::new(path, cx)); + let worktree = cx.add_model(|cx| Worktree::local(path, cx)); cx.observe_model(&worktree, |_, _, cx| cx.notify()); self.worktrees.insert(worktree.clone()); cx.notify(); @@ -595,11 +604,10 @@ impl Workspace { if let Some(item) = self.active_item(cx) { let handle = cx.handle(); if item.entry_id(cx.as_ref()).is_none() { - let start_path = self - .worktrees - .iter() - .next() - .map_or(Path::new(""), |h| h.read(cx).abs_path()) + let worktree = self.worktrees.iter().next(); + let start_path = worktree + .and_then(|w| w.read(cx).as_local()) + .map_or(Path::new(""), |w| w.abs_path()) .to_path_buf(); cx.prompt_for_new_path(&start_path, move |path, cx| { if let Some(path) = path { @@ -670,7 +678,10 @@ impl Workspace { let share_task = this.update(&mut cx, |this, cx| { let worktree = this.worktrees.iter().next()?; - Some(worktree.update(cx, |worktree, cx| worktree.share(rpc, connection_id, cx))) + worktree.update(cx, |worktree, cx| { + let worktree = worktree.as_local_mut()?; + Some(worktree.share(rpc, connection_id, cx)) + }) }); if let Some(share_task) = share_task { @@ -721,7 +732,7 @@ impl Workspace { cx.spawn(|_, _| async move { if let Err(e) = task.await { - log::error!("joing failed: {}", e); + log::error!("joining failed: {}", e); } }) .detach(); @@ -1096,7 +1107,7 @@ mod tests { .read(cx) .worktrees() .iter() - .map(|w| w.read(cx).abs_path()) + .map(|w| w.read(cx).as_local().unwrap().abs_path()) .collect::>(); assert_eq!( worktree_roots, diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index c2744922fd6e4140355bb4888129f91cb6d7b9ce..aaccbefe36f5a0b37e4402097e5eb78c6443136c 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -46,7 +46,74 @@ enum ScanState { Err(Arc), } -pub struct Worktree { +pub enum Worktree { + Local(LocalWorktree), +} + +impl Entity for Worktree { + type Event = (); +} + +impl Worktree { + pub fn local(path: impl Into>, cx: &mut ModelContext) -> Self { + Worktree::Local(LocalWorktree::new(path, cx)) + } + + pub fn as_local(&self) -> Option<&LocalWorktree> { + if let Worktree::Local(worktree) = self { + Some(worktree) + } else { + None + } + } + + pub fn as_local_mut(&mut self) -> Option<&mut LocalWorktree> { + if let Worktree::Local(worktree) = self { + Some(worktree) + } else { + None + } + } + + pub fn snapshot(&self) -> Snapshot { + match self { + Worktree::Local(worktree) => worktree.snapshot(), + } + } + + pub fn load_history( + &self, + path: &Path, + cx: &AppContext, + ) -> impl Future> { + match self { + Worktree::Local(worktree) => worktree.load_history(path, cx), + } + } + + pub fn save( + &self, + path: &Path, + content: Rope, + cx: &AppContext, + ) -> impl Future> { + match self { + Worktree::Local(worktree) => worktree.save(path, content, cx), + } + } +} + +impl Deref for Worktree { + type Target = Snapshot; + + fn deref(&self) -> &Self::Target { + match self { + Worktree::Local(worktree) => &worktree.snapshot, + } + } +} + +pub struct LocalWorktree { snapshot: Snapshot, background_snapshot: Arc>, handles: Arc, Weak>>>>, @@ -69,8 +136,8 @@ struct FileHandleState { mtime: SystemTime, } -impl Worktree { - pub fn new(path: impl Into>, cx: &mut ModelContext) -> Self { +impl LocalWorktree { + pub fn new(path: impl Into>, cx: &mut ModelContext) -> Self { let abs_path = path.into(); let (scan_state_tx, scan_state_rx) = smol::channel::unbounded(); let id = cx.model_id(); @@ -109,7 +176,13 @@ impl Worktree { while let Ok(scan_state) = scan_state_rx.recv().await { let alive = cx.update(|cx| { if let Some(handle) = this.upgrade(&cx) { - handle.update(cx, |this, cx| this.observe_scan_state(scan_state, cx)); + handle.update(cx, |this, cx| { + if let Worktree::Local(worktree) = this { + worktree.observe_scan_state(scan_state, cx) + } else { + unreachable!() + } + }); true } else { false @@ -137,12 +210,12 @@ impl Worktree { } } - fn observe_scan_state(&mut self, scan_state: ScanState, cx: &mut ModelContext) { + fn observe_scan_state(&mut self, scan_state: ScanState, cx: &mut ModelContext) { let _ = self.scan_state.0.blocking_send(scan_state); self.poll_entries(cx); } - fn poll_entries(&mut self, cx: &mut ModelContext) { + fn poll_entries(&mut self, cx: &mut ModelContext) { self.snapshot = self.background_snapshot.lock().clone(); cx.notify(); @@ -150,8 +223,12 @@ impl Worktree { cx.spawn(|this, mut cx| async move { smol::Timer::after(Duration::from_millis(100)).await; this.update(&mut cx, |this, cx| { - this.poll_scheduled = false; - this.poll_entries(cx); + if let Worktree::Local(worktree) = this { + worktree.poll_scheduled = false; + worktree.poll_entries(cx); + } else { + unreachable!() + } }) }) .detach(); @@ -202,7 +279,7 @@ impl Worktree { }) } - pub fn save<'a>(&self, path: &Path, content: Rope, cx: &AppContext) -> Task> { + pub fn save(&self, path: &Path, content: Rope, cx: &AppContext) -> Task> { let handles = self.handles.clone(); let path = path.to_path_buf(); let abs_path = self.absolutize(&path); @@ -229,7 +306,7 @@ impl Worktree { &mut self, client: rpc::Client, connection_id: ConnectionId, - cx: &mut ModelContext, + cx: &mut ModelContext, ) -> Task> { self.rpc = Some(client.clone()); let snapshot = self.snapshot(); @@ -259,11 +336,7 @@ impl Worktree { } } -impl Entity for Worktree { - type Event = (); -} - -impl Deref for Worktree { +impl Deref for LocalWorktree { type Target = Snapshot; fn deref(&self) -> &Self::Target { @@ -271,7 +344,7 @@ impl Deref for Worktree { } } -impl fmt::Debug for Worktree { +impl fmt::Debug for LocalWorktree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.snapshot.fmt(f) } @@ -470,7 +543,7 @@ impl FileHandle { .lock() .path .file_name() - .or_else(|| self.worktree.read(cx).abs_path().file_name()) + .or_else(|| Some(OsStr::new(self.worktree.read(cx).root_name()))) .map(Into::into) } @@ -490,7 +563,7 @@ impl FileHandle { self.worktree.read(cx).load_history(&self.path(), cx) } - pub fn save<'a>(&self, content: Rope, cx: &AppContext) -> Task> { + pub fn save(&self, content: Rope, cx: &AppContext) -> impl Future> { let worktree = self.worktree.read(cx); worktree.save(&self.path(), content, cx) } @@ -1250,47 +1323,51 @@ impl WorktreeHandle for ModelHandle { let path = Arc::from(path.as_ref()); let handle = self.clone(); let tree = self.read(cx); - let abs_path = tree.absolutize(&path); - 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 + match tree { + Worktree::Local(tree) => { + let abs_path = tree.absolutize(&path); + 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; + 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, + } + }; + + let state = Arc::new(Mutex::new(handle_state.clone())); + handles.insert(handle_state.path, Arc::downgrade(&state)); + state + } + }); + FileHandle { + worktree: handle.clone(), + state, } }) - .await; - let state = handle.read_with(&cx, |tree, _| { - let mut handles = tree.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, - } - }; - - let state = Arc::new(Mutex::new(handle_state.clone())); - handles.insert(handle_state.path, Arc::downgrade(&state)); - state - } - }); - FileHandle { - worktree: handle.clone(), - state, } - }) + } } // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that @@ -1318,7 +1395,8 @@ impl WorktreeHandle for ModelHandle { tree.condition(&cx, |tree, _| tree.entry_for_path(filename).is_none()) .await; - cx.read(|cx| tree.read(cx).scan_complete()).await; + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; } .boxed_local() } @@ -1467,9 +1545,10 @@ mod tests { ) .unwrap(); - let tree = cx.add_model(|cx| Worktree::new(root_link_path, cx)); + let tree = cx.add_model(|cx| Worktree::local(root_link_path, cx)); - cx.read(|cx| tree.read(cx).scan_complete()).await; + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; cx.read(|cx| { let tree = tree.read(cx); assert_eq!(tree.file_count(), 5); @@ -1508,8 +1587,9 @@ mod tests { "file1": "the old contents", })); - let tree = cx.add_model(|cx| Worktree::new(dir.path(), cx)); - cx.read(|cx| tree.read(cx).scan_complete()).await; + let tree = cx.add_model(|cx| Worktree::local(dir.path(), cx)); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; cx.read(|cx| assert_eq!(tree.read(cx).file_count(), 1)); let buffer = cx.add_model(|cx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), cx)); @@ -1537,8 +1617,9 @@ mod tests { "file1": "the old contents", })); - let tree = cx.add_model(|cx| Worktree::new(dir.path().join("file1"), cx)); - cx.read(|cx| tree.read(cx).scan_complete()).await; + let tree = cx.add_model(|cx| Worktree::local(dir.path().join("file1"), cx)); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; cx.read(|cx| assert_eq!(tree.read(cx).file_count(), 1)); let buffer = cx.add_model(|cx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), cx)); @@ -1569,7 +1650,7 @@ mod tests { } })); - let tree = cx.add_model(|cx| Worktree::new(dir.path(), cx)); + 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; @@ -1577,7 +1658,8 @@ mod tests { let non_existent_file = cx.update(|cx| tree.file("a/file_x", cx)).await; // After scanning, the worktree knows which files exist and which don't. - cx.read(|cx| tree.read(cx).scan_complete()).await; + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; assert!(!file2.is_deleted()); assert!(!file3.is_deleted()); assert!(!file4.is_deleted()); @@ -1636,8 +1718,9 @@ mod tests { } })); - let tree = cx.add_model(|cx| Worktree::new(dir.path(), cx)); - cx.read(|cx| tree.read(cx).scan_complete()).await; + let tree = cx.add_model(|cx| Worktree::local(dir.path(), cx)); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; tree.flush_fs_events(&cx).await; cx.read(|cx| { let tree = tree.read(cx);