From 630a326a078b31ab67036bb160a652a4bf630e3f Mon Sep 17 00:00:00 2001 From: CharlesChen0823 Date: Thu, 26 Jun 2025 00:17:41 +0800 Subject: [PATCH] file_finder: Fix create wrong file in multiple worktree (#33139) 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 --- crates/file_finder/src/file_finder.rs | 41 +++++- crates/file_finder/src/file_finder_tests.rs | 142 ++++++++++++++++++++ 2 files changed, 176 insertions(+), 7 deletions(-) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index bfdb8fc4f482f4d6f7965d4d0940eaedfe8cca7d..5096be673342f2cfa365e8806be330bfc3bd26cf 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/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::>(); + 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), })); } } diff --git a/crates/file_finder/src/file_finder_tests.rs b/crates/file_finder/src/file_finder_tests.rs index dbb6d45f916c7251e181c414d315d197aed7bd0a..db259ccef854b1d3c5c4fae3bc9ebad08e398891 100644 --- a/crates/file_finder/src/file_finder_tests.rs +++ b/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::>(); + ( + 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::(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::>(); + ( + 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::(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);