file_finder: Fix create wrong file in multiple worktree (#33139)

CharlesChen0823 created

When open multiple worktree, using `file_finder` to create a new file
shoud respect current focused worktree.
test case:
```
project:
   worktree A
        file1
   worktree B
        file2 <-  focused
```
when focused `file2`, `ctrl-p` toggle `file_finder` to create `file3`
should exists in worktreeB.


I try add test case for `CreateNew` in file_finder, but found not
worked, if you help me, I can try add this test case.

Release Notes:

- Fixed file finder selecting wrong worktree when creating a file

Change summary

crates/file_finder/src/file_finder.rs       |  41 +++++-
crates/file_finder/src/file_finder_tests.rs | 142 +++++++++++++++++++++++
2 files changed, 176 insertions(+), 7 deletions(-)

Detailed changes

crates/file_finder/src/file_finder.rs 🔗

@@ -939,20 +939,47 @@ impl FileFinderDelegate {
                 matches.into_iter(),
                 extend_old_matches,
             );
-            let worktree = self.project.read(cx).visible_worktrees(cx).next();
-            let filename = query.raw_query.to_string();
-            let path = Path::new(&filename);
 
+            let filename = &query.raw_query;
+            let mut query_path = Path::new(filename);
             // add option of creating new file only if path is relative
-            if let Some(worktree) = worktree {
+            let available_worktree = self
+                .project
+                .read(cx)
+                .visible_worktrees(cx)
+                .filter(|worktree| !worktree.read(cx).is_single_file())
+                .collect::<Vec<_>>();
+            let worktree_count = available_worktree.len();
+            let mut expect_worktree = available_worktree.first().cloned();
+            for worktree in available_worktree {
+                let worktree_root = worktree
+                    .read(cx)
+                    .abs_path()
+                    .file_name()
+                    .map_or(String::new(), |f| f.to_string_lossy().to_string());
+                if worktree_count > 1 && query_path.starts_with(&worktree_root) {
+                    query_path = query_path
+                        .strip_prefix(&worktree_root)
+                        .unwrap_or(query_path);
+                    expect_worktree = Some(worktree);
+                    break;
+                }
+            }
+
+            if let Some(FoundPath { ref project, .. }) = self.currently_opened_path {
+                let worktree_id = project.worktree_id;
+                expect_worktree = self.project.read(cx).worktree_for_id(worktree_id, cx);
+            }
+
+            if let Some(worktree) = expect_worktree {
                 let worktree = worktree.read(cx);
-                if path.is_relative()
-                    && worktree.entry_for_path(&path).is_none()
+                if query_path.is_relative()
+                    && worktree.entry_for_path(&query_path).is_none()
                     && !filename.ends_with("/")
                 {
                     self.matches.matches.push(Match::CreateNew(ProjectPath {
                         worktree_id: worktree.id(),
-                        path: Arc::from(path),
+                        path: Arc::from(query_path),
                     }));
                 }
             }

crates/file_finder/src/file_finder_tests.rs 🔗

@@ -881,6 +881,148 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) {
     picker.update(cx, |f, _| assert_eq!(f.delegate.matches.len(), 0));
 }
 
+#[gpui::test]
+async fn test_create_file_for_multiple_worktrees(cx: &mut TestAppContext) {
+    let app_state = init_test(cx);
+    app_state
+        .fs
+        .as_fake()
+        .insert_tree(
+            path!("/roota"),
+            json!({ "the-parent-dira": { "filea": "" } }),
+        )
+        .await;
+
+    app_state
+        .fs
+        .as_fake()
+        .insert_tree(
+            path!("/rootb"),
+            json!({ "the-parent-dirb": { "fileb": "" } }),
+        )
+        .await;
+
+    let project = Project::test(
+        app_state.fs.clone(),
+        [path!("/roota").as_ref(), path!("/rootb").as_ref()],
+        cx,
+    )
+    .await;
+
+    let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
+    let (_worktree_id1, worktree_id2) = cx.read(|cx| {
+        let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
+        (
+            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize),
+            WorktreeId::from_usize(worktrees[1].entity_id().as_u64() as usize),
+        )
+    });
+
+    let b_path = ProjectPath {
+        worktree_id: worktree_id2,
+        path: Arc::from(Path::new(path!("the-parent-dirb/fileb"))),
+    };
+    workspace
+        .update_in(cx, |workspace, window, cx| {
+            workspace.open_path(b_path, None, true, window, cx)
+        })
+        .await
+        .unwrap();
+
+    let finder = open_file_picker(&workspace, cx);
+
+    finder
+        .update_in(cx, |f, window, cx| {
+            f.delegate.spawn_search(
+                test_path_position(path!("the-parent-dirb/filec")),
+                window,
+                cx,
+            )
+        })
+        .await;
+    cx.run_until_parked();
+    finder.update_in(cx, |picker, window, cx| {
+        assert_eq!(picker.delegate.matches.len(), 1);
+        picker.delegate.confirm(false, window, cx)
+    });
+    cx.run_until_parked();
+    cx.read(|cx| {
+        let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+        let project_path = active_editor.read(cx).project_path(cx);
+        assert_eq!(
+            project_path,
+            Some(ProjectPath {
+                worktree_id: worktree_id2,
+                path: Arc::from(Path::new(path!("the-parent-dirb/filec")))
+            })
+        );
+    });
+}
+
+#[gpui::test]
+async fn test_create_file_no_focused_with_multiple_worktrees(cx: &mut TestAppContext) {
+    let app_state = init_test(cx);
+    app_state
+        .fs
+        .as_fake()
+        .insert_tree(
+            path!("/roota"),
+            json!({ "the-parent-dira": { "filea": "" } }),
+        )
+        .await;
+
+    app_state
+        .fs
+        .as_fake()
+        .insert_tree(
+            path!("/rootb"),
+            json!({ "the-parent-dirb": { "fileb": "" } }),
+        )
+        .await;
+
+    let project = Project::test(
+        app_state.fs.clone(),
+        [path!("/roota").as_ref(), path!("/rootb").as_ref()],
+        cx,
+    )
+    .await;
+
+    let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
+    let (_worktree_id1, worktree_id2) = cx.read(|cx| {
+        let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
+        (
+            WorktreeId::from_usize(worktrees[0].entity_id().as_u64() as usize),
+            WorktreeId::from_usize(worktrees[1].entity_id().as_u64() as usize),
+        )
+    });
+
+    let finder = open_file_picker(&workspace, cx);
+
+    finder
+        .update_in(cx, |f, window, cx| {
+            f.delegate
+                .spawn_search(test_path_position(path!("rootb/filec")), window, cx)
+        })
+        .await;
+    cx.run_until_parked();
+    finder.update_in(cx, |picker, window, cx| {
+        assert_eq!(picker.delegate.matches.len(), 1);
+        picker.delegate.confirm(false, window, cx)
+    });
+    cx.run_until_parked();
+    cx.read(|cx| {
+        let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
+        let project_path = active_editor.read(cx).project_path(cx);
+        assert_eq!(
+            project_path,
+            Some(ProjectPath {
+                worktree_id: worktree_id2,
+                path: Arc::from(Path::new("filec"))
+            })
+        );
+    });
+}
+
 #[gpui::test]
 async fn test_path_distance_ordering(cx: &mut TestAppContext) {
     let app_state = init_test(cx);