Merge pull request #1184 from zed-industries/invisible-setting-files

Antonio Scandurra created

Don't show invisible worktrees in project and contacts panel

Change summary

crates/client/src/user.rs                   |  6 +-
crates/collab/src/integration_tests.rs      | 20 +++++--
crates/collab/src/rpc/store.rs              |  3 
crates/contacts_panel/src/contacts_panel.rs | 22 ++++---
crates/journal/src/journal.rs               |  2 
crates/rpc/proto/zed.proto                  |  2 
crates/rpc/src/rpc.rs                       |  2 
crates/workspace/src/waiting_room.rs        |  4 
crates/workspace/src/workspace.rs           | 41 ++++++++++----
crates/zed/src/zed.rs                       | 63 ++++++++++++++++++++--
10 files changed, 122 insertions(+), 43 deletions(-)

Detailed changes

crates/client/src/user.rs 🔗

@@ -45,7 +45,7 @@ pub struct Contact {
 #[derive(Clone, Debug, PartialEq)]
 pub struct ProjectMetadata {
     pub id: u64,
-    pub worktree_root_names: Vec<String>,
+    pub visible_worktree_root_names: Vec<String>,
     pub guests: BTreeSet<Arc<User>>,
 }
 
@@ -634,7 +634,7 @@ impl Contact {
             }
             projects.push(ProjectMetadata {
                 id: project.id,
-                worktree_root_names: project.worktree_root_names.clone(),
+                visible_worktree_root_names: project.visible_worktree_root_names.clone(),
                 guests,
             });
         }
@@ -648,7 +648,7 @@ impl Contact {
     pub fn non_empty_projects(&self) -> impl Iterator<Item = &ProjectMetadata> {
         self.projects
             .iter()
-            .filter(|project| !project.worktree_root_names.is_empty())
+            .filter(|project| !project.visible_worktree_root_names.is_empty())
     }
 }
 

crates/collab/src/integration_tests.rs 🔗

@@ -608,7 +608,7 @@ async fn test_offline_projects(
             store.contacts()[0].projects,
             &[ProjectMetadata {
                 id: project_id,
-                worktree_root_names: vec!["crate1".into(), "crate2".into()],
+                visible_worktree_root_names: vec!["crate1".into(), "crate2".into()],
                 guests: Default::default(),
             }]
         );
@@ -637,7 +637,7 @@ async fn test_offline_projects(
             store.contacts()[0].projects,
             &[ProjectMetadata {
                 id: project_id,
-                worktree_root_names: vec!["crate1".into(), "crate2".into()],
+                visible_worktree_root_names: vec!["crate1".into(), "crate2".into()],
                 guests: Default::default(),
             }]
         );
@@ -655,7 +655,11 @@ async fn test_offline_projects(
             store.contacts()[0].projects,
             &[ProjectMetadata {
                 id: project_id,
-                worktree_root_names: vec!["crate1".into(), "crate2".into(), "crate3".into()],
+                visible_worktree_root_names: vec![
+                    "crate1".into(),
+                    "crate2".into(),
+                    "crate3".into()
+                ],
                 guests: Default::default(),
             }]
         );
@@ -695,12 +699,16 @@ async fn test_offline_projects(
             &[
                 ProjectMetadata {
                     id: project_id,
-                    worktree_root_names: vec!["crate1".into(), "crate2".into(), "crate3".into()],
+                    visible_worktree_root_names: vec![
+                        "crate1".into(),
+                        "crate2".into(),
+                        "crate3".into()
+                    ],
                     guests: Default::default(),
                 },
                 ProjectMetadata {
                     id: project2_id,
-                    worktree_root_names: vec!["crate3".into()],
+                    visible_worktree_root_names: vec!["crate3".into()],
                     guests: Default::default(),
                 }
             ]
@@ -3515,7 +3523,7 @@ async fn test_contacts(
                     .iter()
                     .map(|p| {
                         (
-                            p.worktree_root_names[0].as_str(),
+                            p.visible_worktree_root_names[0].as_str(),
                             p.guests.iter().map(|p| p.github_login.as_str()).collect(),
                         )
                     })

crates/collab/src/rpc/store.rs 🔗

@@ -277,9 +277,10 @@ impl Store {
                 if project.host_user_id == user_id {
                     metadata.push(proto::ProjectMetadata {
                         id: project_id,
-                        worktree_root_names: project
+                        visible_worktree_root_names: project
                             .worktrees
                             .values()
+                            .filter(|worktree| worktree.visible)
                             .map(|worktree| worktree.root_name.clone())
                             .collect(),
                         guests: project

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -462,7 +462,7 @@ impl ContactsPanel {
                 )
                 .with_child(
                     Label::new(
-                        project.worktree_root_names.join(", "),
+                        project.visible_worktree_root_names.join(", "),
                         row.name.text.clone(),
                     )
                     .aligned()
@@ -847,7 +847,7 @@ impl ContactsPanel {
                                                     p.read(cx).remote_id() == Some(project.id)
                                                 })
                                                 .map(|ix| open_projects.remove(ix).downgrade());
-                                            if project.worktree_root_names.is_empty() {
+                                            if project.visible_worktree_root_names.is_empty() {
                                                 None
                                             } else {
                                                 Some(ContactEntry::ContactProject(
@@ -872,7 +872,7 @@ impl ContactsPanel {
                                 self.entries.extend(
                                     contact.projects.iter().enumerate().filter_map(
                                         |(ix, project)| {
-                                            if project.worktree_root_names.is_empty() {
+                                            if project.visible_worktree_root_names.is_empty() {
                                                 None
                                             } else {
                                                 Some(ContactEntry::ContactProject(
@@ -1295,7 +1295,7 @@ mod tests {
                     should_notify: false,
                     projects: vec![proto::ProjectMetadata {
                         id: 101,
-                        worktree_root_names: vec!["dir1".to_string()],
+                        visible_worktree_root_names: vec!["dir1".to_string()],
                         guests: vec![2],
                     }],
                 },
@@ -1305,7 +1305,7 @@ mod tests {
                     should_notify: false,
                     projects: vec![proto::ProjectMetadata {
                         id: 102,
-                        worktree_root_names: vec!["dir2".to_string()],
+                        visible_worktree_root_names: vec!["dir2".to_string()],
                         guests: vec![2],
                     }],
                 },
@@ -1321,7 +1321,7 @@ mod tests {
                     should_notify: false,
                     projects: vec![proto::ProjectMetadata {
                         id: 103,
-                        worktree_root_names: vec!["dir3".to_string()],
+                        visible_worktree_root_names: vec!["dir3".to_string()],
                         guests: vec![3],
                     }],
                 },
@@ -1425,12 +1425,12 @@ mod tests {
                 projects: vec![
                     proto::ProjectMetadata {
                         id: 103,
-                        worktree_root_names: vec!["dir3".to_string()],
+                        visible_worktree_root_names: vec!["dir3".to_string()],
                         guests: vec![3],
                     },
                     proto::ProjectMetadata {
                         id: 200,
-                        worktree_root_names: vec!["private_dir".to_string()],
+                        visible_worktree_root_names: vec!["private_dir".to_string()],
                         guests: vec![3],
                     },
                 ],
@@ -1489,7 +1489,7 @@ mod tests {
                 should_notify: false,
                 projects: vec![proto::ProjectMetadata {
                     id: 103,
-                    worktree_root_names: vec!["dir3".to_string()],
+                    visible_worktree_root_names: vec!["dir3".to_string()],
                     guests: vec![3],
                 }],
             }],
@@ -1611,7 +1611,9 @@ mod tests {
                         .map(|project| project.read(cx));
                     format!(
                         "    {}{}",
-                        contact.projects[*project_ix].worktree_root_names.join(", "),
+                        contact.projects[*project_ix]
+                            .visible_worktree_root_names
+                            .join(", "),
                         if project.map_or(true, |project| project.is_online()) {
                             ""
                         } else {

crates/journal/src/journal.rs 🔗

@@ -49,7 +49,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
 
             let opened = workspace
                 .update(&mut cx, |workspace, cx| {
-                    workspace.open_paths(vec![entry_path], cx)
+                    workspace.open_paths(vec![entry_path], true, cx)
                 })
                 .await;
 

crates/rpc/proto/zed.proto 🔗

@@ -946,7 +946,7 @@ message Contact {
 
 message ProjectMetadata {
     uint64 id = 1;
-    repeated string worktree_root_names = 3;
+    repeated string visible_worktree_root_names = 3;
     repeated uint64 guests = 4;
 }
 

crates/rpc/src/rpc.rs 🔗

@@ -6,4 +6,4 @@ pub use conn::Connection;
 pub use peer::*;
 mod macros;
 
-pub const PROTOCOL_VERSION: u32 = 23;
+pub const PROTOCOL_VERSION: u32 = 24;

crates/workspace/src/waiting_room.rs 🔗

@@ -137,7 +137,7 @@ impl WaitingRoom {
                                                 login,
                                                 humanize_list(
                                                     &contact.projects[project_index]
-                                                        .worktree_root_names
+                                                        .visible_worktree_root_names
                                                 )
                                             )
                                         }
@@ -166,7 +166,7 @@ impl WaitingRoom {
             message: format!(
                 "Asking to join @{}'s copy of {}...",
                 contact.user.github_login,
-                humanize_list(&contact.projects[project_index].worktree_root_names)
+                humanize_list(&contact.projects[project_index].visible_worktree_root_names)
             ),
             waiting: true,
             client,

crates/workspace/src/workspace.rs 🔗

@@ -885,6 +885,13 @@ impl Workspace {
         self.project.read(cx).worktrees(cx)
     }
 
+    pub fn visible_worktrees<'a>(
+        &self,
+        cx: &'a AppContext,
+    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
+        self.project.read(cx).visible_worktrees(cx)
+    }
+
     pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
         let futures = self
             .worktrees(cx)
@@ -974,6 +981,7 @@ impl Workspace {
     pub fn open_paths(
         &mut self,
         mut abs_paths: Vec<PathBuf>,
+        visible: bool,
         cx: &mut ViewContext<Self>,
     ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
         let fs = self.fs.clone();
@@ -984,9 +992,11 @@ impl Workspace {
             let mut entries = Vec::new();
             for path in &abs_paths {
                 entries.push(
-                    this.update(&mut cx, |this, cx| this.project_path_for_path(path, cx))
-                        .await
-                        .ok(),
+                    this.update(&mut cx, |this, cx| {
+                        this.project_path_for_path(path, visible, cx)
+                    })
+                    .await
+                    .log_err(),
                 );
             }
 
@@ -999,7 +1009,7 @@ impl Workspace {
                     cx.spawn(|mut cx| {
                         let fs = fs.clone();
                         async move {
-                            let project_path = project_path?;
+                            let (_worktree, project_path) = project_path?;
                             if fs.is_file(&abs_path).await {
                                 Some(
                                     this.update(&mut cx, |this, cx| {
@@ -1028,7 +1038,7 @@ impl Workspace {
         cx.spawn(|this, mut cx| async move {
             if let Some(paths) = paths.recv().await.flatten() {
                 let results = this
-                    .update(&mut cx, |this, cx| this.open_paths(paths, cx))
+                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
                     .await;
                 for result in results {
                     if let Some(result) = result {
@@ -1063,17 +1073,22 @@ impl Workspace {
     fn project_path_for_path(
         &self,
         abs_path: &Path,
+        visible: bool,
         cx: &mut ViewContext<Self>,
-    ) -> Task<Result<ProjectPath>> {
+    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
         let entry = self.project().update(cx, |project, cx| {
-            project.find_or_create_local_worktree(abs_path, true, cx)
+            project.find_or_create_local_worktree(abs_path, visible, cx)
         });
         cx.spawn(|_, cx| async move {
             let (worktree, path) = entry.await?;
-            Ok(ProjectPath {
-                worktree_id: worktree.read_with(&cx, |t, _| t.id()),
-                path: path.into(),
-            })
+            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
+            Ok((
+                worktree,
+                ProjectPath {
+                    worktree_id,
+                    path: path.into(),
+                },
+            ))
         })
     }
 
@@ -2444,7 +2459,9 @@ pub fn open_paths(
         };
 
         let items = workspace
-            .update(&mut cx, |workspace, cx| workspace.open_paths(abs_paths, cx))
+            .update(&mut cx, |workspace, cx| {
+                workspace.open_paths(abs_paths, true, cx)
+            })
             .await;
 
         if let Some(project) = new_project {

crates/zed/src/zed.rs 🔗

@@ -322,7 +322,7 @@ fn open_config_file(
         workspace
             .update(&mut cx, |workspace, cx| {
                 if workspace.project().read(cx).is_local() {
-                    workspace.open_paths(vec![path.to_path_buf()], cx)
+                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
                 } else {
                     let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
                         let mut workspace = Workspace::new(
@@ -341,7 +341,7 @@ fn open_config_file(
                         workspace
                     });
                     workspace.update(cx, |workspace, cx| {
-                        workspace.open_paths(vec![path.to_path_buf()], cx)
+                        workspace.open_paths(vec![path.to_path_buf()], false, cx)
                     })
                 }
             })
@@ -576,6 +576,7 @@ mod tests {
         fs.insert_file("/dir1/a.txt", "".into()).await;
         fs.insert_file("/dir2/b.txt", "".into()).await;
         fs.insert_file("/dir3/c.txt", "".into()).await;
+        fs.insert_file("/d.txt", "".into()).await;
 
         let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
@@ -583,7 +584,7 @@ mod tests {
         // Open a file within an existing worktree.
         cx.update(|cx| {
             workspace.update(cx, |view, cx| {
-                view.open_paths(vec!["/dir1/a.txt".into()], cx)
+                view.open_paths(vec!["/dir1/a.txt".into()], true, cx)
             })
         })
         .await;
@@ -607,7 +608,7 @@ mod tests {
         // Open a file outside of any existing worktree.
         cx.update(|cx| {
             workspace.update(cx, |view, cx| {
-                view.open_paths(vec!["/dir2/b.txt".into()], cx)
+                view.open_paths(vec!["/dir2/b.txt".into()], true, cx)
             })
         })
         .await;
@@ -643,7 +644,7 @@ mod tests {
         // Ensure opening a directory and one of its children only adds one worktree.
         cx.update(|cx| {
             workspace.update(cx, |view, cx| {
-                view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], cx)
+                view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], true, cx)
             })
         })
         .await;
@@ -675,6 +676,56 @@ mod tests {
                 "c.txt"
             );
         });
+
+        // Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree.
+        cx.update(|cx| {
+            workspace.update(cx, |view, cx| {
+                view.open_paths(vec!["/d.txt".into()], false, cx)
+            })
+        })
+        .await;
+        cx.read(|cx| {
+            let worktree_roots = workspace
+                .read(cx)
+                .worktrees(cx)
+                .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
+                .collect::<HashSet<_>>();
+            assert_eq!(
+                worktree_roots,
+                vec!["/dir1", "/dir2/b.txt", "/dir3", "/d.txt"]
+                    .into_iter()
+                    .map(Path::new)
+                    .collect(),
+            );
+
+            let visible_worktree_roots = workspace
+                .read(cx)
+                .visible_worktrees(cx)
+                .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
+                .collect::<HashSet<_>>();
+            assert_eq!(
+                visible_worktree_roots,
+                vec!["/dir1", "/dir2/b.txt", "/dir3"]
+                    .into_iter()
+                    .map(Path::new)
+                    .collect(),
+            );
+
+            assert_eq!(
+                workspace
+                    .read(cx)
+                    .active_pane()
+                    .read(cx)
+                    .active_item()
+                    .unwrap()
+                    .to_any()
+                    .downcast::<Editor>()
+                    .unwrap()
+                    .read(cx)
+                    .title(cx),
+                "d.txt"
+            );
+        });
     }
 
     #[gpui::test]
@@ -692,7 +743,7 @@ mod tests {
         // Open a file within an existing worktree.
         cx.update(|cx| {
             workspace.update(cx, |view, cx| {
-                view.open_paths(vec![PathBuf::from("/root/a.txt")], cx)
+                view.open_paths(vec![PathBuf::from("/root/a.txt")], true, cx)
             })
         })
         .await;