Extract from Workspace a Project model that stores a window's worktrees

Max Brunsfeld created

Change summary

server/src/rpc.rs      |   6 +-
zed/src/file_finder.rs |   2 
zed/src/lib.rs         |   1 
zed/src/project.rs     |  88 +++++++++++++++++++++++++++++++++++
zed/src/workspace.rs   | 110 ++++++++++++-------------------------------
5 files changed, 125 insertions(+), 82 deletions(-)

Detailed changes

server/src/rpc.rs 🔗

@@ -1157,13 +1157,13 @@ mod tests {
             );
         });
         workspace_b
-            .condition(&cx_b, |workspace, _| workspace.worktrees().len() == 1)
+            .condition(&cx_b, |workspace, cx| workspace.worktrees(cx).len() == 1)
             .await;
 
         let local_worktree_id_b = workspace_b.read_with(&cx_b, |workspace, cx| {
             let active_pane = workspace.active_pane().read(cx);
             assert!(active_pane.active_item().is_none());
-            workspace.worktrees().iter().next().unwrap().id()
+            workspace.worktrees(cx).first().unwrap().id()
         });
         workspace_b
             .update(&mut cx_b, |worktree, cx| {
@@ -1180,7 +1180,7 @@ mod tests {
             tree.as_local_mut().unwrap().unshare(cx);
         });
         workspace_b
-            .condition(&cx_b, |workspace, _| workspace.worktrees().len() == 0)
+            .condition(&cx_b, |workspace, cx| workspace.worktrees(cx).len() == 0)
             .await;
         workspace_b.read_with(&cx_b, |workspace, cx| {
             let active_pane = workspace.active_pane().read(cx);

zed/src/file_finder.rs 🔗

@@ -385,7 +385,7 @@ impl FileFinder {
             .workspace
             .upgrade(&cx)?
             .read(cx)
-            .worktrees()
+            .worktrees(cx)
             .iter()
             .map(|tree| tree.read(cx).snapshot())
             .collect::<Vec<_>>();

zed/src/lib.rs 🔗

@@ -9,6 +9,7 @@ pub mod http;
 pub mod language;
 pub mod menus;
 pub mod people_panel;
+pub mod project;
 pub mod project_browser;
 pub mod rpc;
 pub mod settings;

zed/src/project.rs 🔗

@@ -0,0 +1,88 @@
+use super::worktree::Worktree;
+use anyhow::Result;
+use gpui::{Entity, ModelContext, ModelHandle, Task};
+
+pub struct Project {
+    worktrees: Vec<ModelHandle<Worktree>>,
+}
+
+pub enum Event {}
+
+impl Project {
+    pub fn new() -> Self {
+        Self {
+            worktrees: Default::default(),
+        }
+    }
+
+    pub fn worktrees(&self) -> &[ModelHandle<Worktree>] {
+        &self.worktrees
+    }
+
+    pub fn worktree_for_id(&self, id: usize) -> Option<ModelHandle<Worktree>> {
+        self.worktrees
+            .iter()
+            .find(|worktree| worktree.id() == id)
+            .cloned()
+    }
+
+    pub fn add_worktree(&mut self, worktree: ModelHandle<Worktree>) {
+        self.worktrees.push(worktree);
+    }
+
+    pub fn share_worktree(
+        &self,
+        remote_id: u64,
+        cx: &mut ModelContext<Self>,
+    ) -> Option<Task<Result<u64>>> {
+        for worktree in &self.worktrees {
+            let task = worktree.update(cx, |worktree, cx| {
+                worktree.as_local_mut().and_then(|worktree| {
+                    if worktree.remote_id() == Some(remote_id) {
+                        Some(worktree.share(cx))
+                    } else {
+                        None
+                    }
+                })
+            });
+            if task.is_some() {
+                return task;
+            }
+        }
+        None
+    }
+
+    pub fn unshare_worktree(&mut self, remote_id: u64, cx: &mut ModelContext<Self>) {
+        for worktree in &self.worktrees {
+            if worktree.update(cx, |worktree, cx| {
+                if let Some(worktree) = worktree.as_local_mut() {
+                    if worktree.remote_id() == Some(remote_id) {
+                        worktree.unshare(cx);
+                        return true;
+                    }
+                }
+                false
+            }) {
+                break;
+            }
+        }
+    }
+
+    pub fn close_remote_worktree(&mut self, id: u64, cx: &mut ModelContext<Self>) {
+        self.worktrees.retain(|worktree| {
+            worktree.update(cx, |worktree, cx| {
+                if let Some(worktree) = worktree.as_remote_mut() {
+                    if worktree.remote_id() == id {
+                        worktree.close_all_buffers(cx);
+                        return false;
+                    }
+                }
+                true
+            })
+        });
+    }
+}
+
+impl Entity for Project {
+    type Event = Event;
+}

zed/src/workspace.rs 🔗

@@ -8,6 +8,7 @@ use crate::{
     fs::Fs,
     language::LanguageRegistry,
     people_panel::{JoinWorktree, LeaveWorktree, PeoplePanel, ShareWorktree, UnshareWorktree},
+    project::Project,
     project_browser::ProjectBrowser,
     rpc,
     settings::Settings,
@@ -34,7 +35,7 @@ pub use pane_group::*;
 use postage::{prelude::Stream, watch};
 use sidebar::{Side, Sidebar, ToggleSidebarItem};
 use std::{
-    collections::{hash_map::Entry, HashMap, HashSet},
+    collections::{hash_map::Entry, HashMap},
     future::Future,
     path::{Path, PathBuf},
     sync::Arc,
@@ -349,7 +350,7 @@ pub struct Workspace {
     right_sidebar: Sidebar,
     panes: Vec<ViewHandle<Pane>>,
     active_pane: ViewHandle<Pane>,
-    worktrees: HashSet<ModelHandle<Worktree>>,
+    project: ModelHandle<Project>,
     items: Vec<Box<dyn WeakItemHandle>>,
     loading_items: HashMap<
         (usize, Arc<Path>),
@@ -360,6 +361,8 @@ pub struct Workspace {
 
 impl Workspace {
     pub fn new(app_state: &AppState, cx: &mut ViewContext<Self>) -> Self {
+        let project = cx.add_model(|_| Project::new());
+
         let pane = cx.add_view(|_| Pane::new(app_state.settings.clone()));
         let pane_id = pane.id();
         cx.subscribe(&pane, move |me, _, event, cx| {
@@ -424,15 +427,15 @@ impl Workspace {
             fs: app_state.fs.clone(),
             left_sidebar,
             right_sidebar,
-            worktrees: Default::default(),
+            project,
             items: Default::default(),
             loading_items: Default::default(),
             _observe_current_user,
         }
     }
 
-    pub fn worktrees(&self) -> &HashSet<ModelHandle<Worktree>> {
-        &self.worktrees
+    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> &'a [ModelHandle<Worktree>] {
+        &self.project.read(cx).worktrees()
     }
 
     pub fn contains_paths(&self, paths: &[PathBuf], cx: &AppContext) -> bool {
@@ -440,7 +443,7 @@ impl Workspace {
     }
 
     pub fn contains_path(&self, path: &Path, cx: &AppContext) -> bool {
-        for worktree in &self.worktrees {
+        for worktree in self.worktrees(cx) {
             let worktree = worktree.read(cx).as_local();
             if worktree.map_or(false, |w| w.contains_abs_path(path)) {
                 return true;
@@ -451,7 +454,7 @@ impl Workspace {
 
     pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
         let futures = self
-            .worktrees
+            .worktrees(cx)
             .iter()
             .filter_map(|worktree| worktree.read(cx).as_local())
             .map(|worktree| worktree.scan_complete())
@@ -511,7 +514,7 @@ impl Workspace {
         cx.spawn(|this, mut cx| async move {
             let mut entry_id = None;
             this.read_with(&cx, |this, cx| {
-                for tree in this.worktrees.iter() {
+                for tree in this.worktrees(cx) {
                     if let Some(relative_path) = tree
                         .read(cx)
                         .as_local()
@@ -559,7 +562,9 @@ impl Workspace {
             let worktree = Worktree::open_local(rpc, path, fs, languages, &mut cx).await?;
             this.update(&mut cx, |this, cx| {
                 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
-                this.worktrees.insert(worktree.clone());
+                this.project.update(cx, |project, _| {
+                    project.add_worktree(worktree.clone());
+                });
                 cx.notify();
             });
             Ok(worktree)
@@ -616,7 +621,7 @@ impl Workspace {
 
         let (worktree_id, path) = entry.clone();
 
-        let worktree = match self.worktrees.get(&worktree_id).cloned() {
+        let worktree = match self.project.read(cx).worktree_for_id(worktree_id) {
             Some(worktree) => worktree,
             None => {
                 log::error!("worktree {} does not exist", worktree_id);
@@ -731,7 +736,7 @@ impl Workspace {
         if let Some(item) = self.active_item(cx) {
             let handle = cx.handle();
             if item.entry_id(cx.as_ref()).is_none() {
-                let worktree = self.worktrees.iter().next();
+                let worktree = self.worktrees(cx).first();
                 let start_abs_path = worktree
                     .and_then(|w| w.read(cx).as_local())
                     .map_or(Path::new(""), |w| w.abs_path())
@@ -830,22 +835,8 @@ impl Workspace {
                 rpc.authenticate_and_connect(&cx).await?;
 
                 let task = this.update(&mut cx, |this, cx| {
-                    for worktree in &this.worktrees {
-                        let task = worktree.update(cx, |worktree, cx| {
-                            worktree.as_local_mut().and_then(|worktree| {
-                                if worktree.remote_id() == Some(remote_id) {
-                                    Some(worktree.share(cx))
-                                } else {
-                                    None
-                                }
-                            })
-                        });
-
-                        if task.is_some() {
-                            return task;
-                        }
-                    }
-                    None
+                    this.project
+                        .update(cx, |project, cx| project.share_worktree(remote_id, cx))
                 });
 
                 if let Some(share_task) = task {
@@ -861,19 +852,8 @@ impl Workspace {
 
     fn unshare_worktree(&mut self, action: &UnshareWorktree, cx: &mut ViewContext<Self>) {
         let remote_id = action.0;
-        for worktree in &self.worktrees {
-            if worktree.update(cx, |worktree, cx| {
-                if let Some(worktree) = worktree.as_local_mut() {
-                    if worktree.remote_id() == Some(remote_id) {
-                        worktree.unshare(cx);
-                        return true;
-                    }
-                }
-                false
-            }) {
-                break;
-            }
-        }
+        self.project
+            .update(cx, |project, cx| project.unshare_worktree(remote_id, cx));
     }
 
     fn join_worktree(&mut self, action: &JoinWorktree, cx: &mut ViewContext<Self>) {
@@ -890,23 +870,15 @@ impl Workspace {
                     cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
                     cx.subscribe(&worktree, move |this, _, event, cx| match event {
                         worktree::Event::Closed => {
-                            this.worktrees.retain(|worktree| {
-                                worktree.update(cx, |worktree, cx| {
-                                    if let Some(worktree) = worktree.as_remote_mut() {
-                                        if worktree.remote_id() == worktree_id {
-                                            worktree.close_all_buffers(cx);
-                                            return false;
-                                        }
-                                    }
-                                    true
-                                })
+                            this.project.update(cx, |project, cx| {
+                                project.close_remote_worktree(worktree_id, cx);
                             });
-
                             cx.notify();
                         }
                     })
                     .detach();
-                    this.worktrees.insert(worktree);
+                    this.project
+                        .update(cx, |project, _| project.add_worktree(worktree));
                     cx.notify();
                 });
 
@@ -919,27 +891,9 @@ impl Workspace {
 
     fn leave_worktree(&mut self, action: &LeaveWorktree, cx: &mut ViewContext<Self>) {
         let remote_id = action.0;
-        cx.spawn(|this, mut cx| {
-            async move {
-                this.update(&mut cx, |this, cx| {
-                    this.worktrees.retain(|worktree| {
-                        worktree.update(cx, |worktree, cx| {
-                            if let Some(worktree) = worktree.as_remote_mut() {
-                                if worktree.remote_id() == remote_id {
-                                    worktree.close_all_buffers(cx);
-                                    return false;
-                                }
-                            }
-                            true
-                        })
-                    })
-                });
-
-                Ok(())
-            }
-            .log_err()
-        })
-        .detach();
+        self.project.update(cx, |project, cx| {
+            project.close_remote_worktree(remote_id, cx);
+        });
     }
 
     fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
@@ -1186,7 +1140,7 @@ pub trait WorkspaceHandle {
 impl WorkspaceHandle for ViewHandle<Workspace> {
     fn file_entries(&self, cx: &AppContext) -> Vec<(usize, Arc<Path>)> {
         self.read(cx)
-            .worktrees()
+            .worktrees(cx)
             .iter()
             .flat_map(|tree| {
                 let tree_id = tree.id();
@@ -1254,8 +1208,8 @@ mod tests {
         .await;
         assert_eq!(cx.window_ids().len(), 1);
         let workspace_1 = cx.root_view::<Workspace>(cx.window_ids()[0]).unwrap();
-        workspace_1.read_with(&cx, |workspace, _| {
-            assert_eq!(workspace.worktrees().len(), 2)
+        workspace_1.read_with(&cx, |workspace, cx| {
+            assert_eq!(workspace.worktrees(cx).len(), 2)
         });
 
         cx.update(|cx| {
@@ -1433,7 +1387,7 @@ mod tests {
         cx.read(|cx| {
             let worktree_roots = workspace
                 .read(cx)
-                .worktrees()
+                .worktrees(cx)
                 .iter()
                 .map(|w| w.read(cx).as_local().unwrap().abs_path())
                 .collect::<HashSet<_>>();
@@ -1526,7 +1480,7 @@ mod tests {
         let tree = cx.read(|cx| {
             workspace
                 .read(cx)
-                .worktrees()
+                .worktrees(cx)
                 .iter()
                 .next()
                 .unwrap()