Add only one worktree when running `zed /dir /dir/file`

Antonio Scandurra created

Change summary

crates/file_finder/src/file_finder.rs |  2 
crates/journal/src/journal.rs         |  2 
crates/workspace/src/workspace.rs     | 71 +++++++++++++++-------------
crates/zed/src/zed.rs                 | 52 +++++++++++++++++++--
4 files changed, 88 insertions(+), 39 deletions(-)

Detailed changes

crates/file_finder/src/file_finder.rs 🔗

@@ -469,7 +469,7 @@ mod tests {
         workspace
             .update(cx, |workspace, cx| {
                 workspace.open_paths(
-                    &[PathBuf::from("/root/dir1"), PathBuf::from("/root/dir2")],
+                    vec![PathBuf::from("/root/dir1"), PathBuf::from("/root/dir2")],
                     cx,
                 )
             })

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(&[entry_path], cx)
+                    workspace.open_paths(vec![entry_path], cx)
                 })
                 .await;
 

crates/workspace/src/workspace.rs 🔗

@@ -859,44 +859,49 @@ impl Workspace {
 
     pub fn open_paths(
         &mut self,
-        abs_paths: &[PathBuf],
+        mut abs_paths: Vec<PathBuf>,
         cx: &mut ViewContext<Self>,
     ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>> {
-        let entries = abs_paths
-            .iter()
-            .cloned()
-            .map(|path| self.project_path_for_path(&path, cx))
-            .collect::<Vec<_>>();
-
         let fs = self.fs.clone();
-        let tasks = abs_paths
-            .iter()
-            .cloned()
-            .zip(entries.into_iter())
-            .map(|(abs_path, project_path)| {
-                cx.spawn(|this, mut cx| {
-                    let fs = fs.clone();
-                    async move {
-                        let project_path = project_path.await.ok()?;
-                        if fs.is_file(&abs_path).await {
-                            Some(
-                                this.update(&mut cx, |this, cx| this.open_path(project_path, cx))
+
+        // Sort the paths to ensure we add worktrees for parents before their children.
+        abs_paths.sort_unstable();
+        cx.spawn(|this, mut cx| async move {
+            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(),
+                );
+            }
+
+            let tasks = abs_paths
+                .iter()
+                .cloned()
+                .zip(entries.into_iter())
+                .map(|(abs_path, project_path)| {
+                    let this = this.clone();
+                    cx.spawn(|mut cx| {
+                        let fs = fs.clone();
+                        async move {
+                            let project_path = project_path?;
+                            if fs.is_file(&abs_path).await {
+                                Some(
+                                    this.update(&mut cx, |this, cx| {
+                                        this.open_path(project_path, cx)
+                                    })
                                     .await,
-                            )
-                        } else {
-                            None
+                                )
+                            } else {
+                                None
+                            }
                         }
-                    }
+                    })
                 })
-            })
-            .collect::<Vec<_>>();
+                .collect::<Vec<_>>();
 
-        cx.foreground().spawn(async move {
-            let mut items = Vec::new();
-            for task in tasks {
-                items.push(task.await);
-            }
-            items
+            futures::future::join_all(tasks).await
         })
     }
 
@@ -2152,7 +2157,9 @@ pub fn open_paths(
         .1
     });
 
-    let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx));
+    let task = workspace.update(cx, |workspace, cx| {
+        workspace.open_paths(abs_paths.to_vec(), cx)
+    });
     cx.spawn(|_| async move {
         let items = task.await;
         (workspace, items, is_new_workspace)

crates/zed/src/zed.rs 🔗

@@ -283,7 +283,7 @@ fn open_config_file(
         workspace
             .update(&mut cx, |workspace, cx| {
                 if workspace.project().read(cx).is_local() {
-                    workspace.open_paths(&[path.to_path_buf()], cx)
+                    workspace.open_paths(vec![path.to_path_buf()], cx)
                 } else {
                     let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
                         let project = Project::local(
@@ -296,7 +296,7 @@ fn open_config_file(
                         (app_state.build_workspace)(project, &app_state, cx)
                     });
                     workspace.update(cx, |workspace, cx| {
-                        workspace.open_paths(&[path.to_path_buf()], cx)
+                        workspace.open_paths(vec![path.to_path_buf()], cx)
                     })
                 }
             })
@@ -536,8 +536,10 @@ mod tests {
         let fs = app_state.fs.as_fake();
         fs.insert_dir("/dir1").await;
         fs.insert_dir("/dir2").await;
+        fs.insert_dir("/dir3").await;
         fs.insert_file("/dir1/a.txt", "".into()).await;
         fs.insert_file("/dir2/b.txt", "".into()).await;
+        fs.insert_file("/dir3/c.txt", "".into()).await;
 
         let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
         let (_, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
@@ -553,7 +555,9 @@ mod tests {
 
         // Open a file within an existing worktree.
         cx.update(|cx| {
-            workspace.update(cx, |view, cx| view.open_paths(&["/dir1/a.txt".into()], cx))
+            workspace.update(cx, |view, cx| {
+                view.open_paths(vec!["/dir1/a.txt".into()], cx)
+            })
         })
         .await;
         cx.read(|cx| {
@@ -575,7 +579,9 @@ mod tests {
 
         // Open a file outside of any existing worktree.
         cx.update(|cx| {
-            workspace.update(cx, |view, cx| view.open_paths(&["/dir2/b.txt".into()], cx))
+            workspace.update(cx, |view, cx| {
+                view.open_paths(vec!["/dir2/b.txt".into()], cx)
+            })
         })
         .await;
         cx.read(|cx| {
@@ -606,6 +612,42 @@ mod tests {
                 "b.txt"
             );
         });
+
+        // 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)
+            })
+        })
+        .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"]
+                    .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),
+                "c.txt"
+            );
+        });
     }
 
     #[gpui::test]
@@ -627,7 +669,7 @@ mod tests {
         // Open a file within an existing worktree.
         cx.update(|cx| {
             workspace.update(cx, |view, cx| {
-                view.open_paths(&[PathBuf::from("/root/a.txt")], cx)
+                view.open_paths(vec![PathBuf::from("/root/a.txt")], cx)
             })
         })
         .await;