project: Support resolving paths with worktree names prefixed (#50692)

Lukas Wirth created

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/editor/src/hover_links.rs | 63 ++++++++++++++++++++++++++++++++++
crates/project/src/project.rs    | 33 +++++++++++++++--
crates/worktree/src/worktree.rs  |  4 ++
3 files changed, 95 insertions(+), 5 deletions(-)

Detailed changes

crates/editor/src/hover_links.rs 🔗

@@ -1889,6 +1889,69 @@ mod tests {
         });
     }
 
+    #[gpui::test]
+    async fn test_hover_filenames_with_worktree_prefix(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |_| {});
+        let mut cx = EditorLspTestContext::new_rust(
+            lsp::ServerCapabilities {
+                ..Default::default()
+            },
+            cx,
+        )
+        .await;
+
+        let fs = cx.update_workspace(|workspace, _, cx| workspace.project().read(cx).fs().clone());
+        fs.as_fake()
+            .insert_file(
+                path!("/root/dir/file2.rs"),
+                "This is file2.rs".as_bytes().to_vec(),
+            )
+            .await;
+
+        #[cfg(not(target_os = "windows"))]
+        cx.set_state(indoc! {"
+            Go to root/dir/file2.rs if you want.ˇ
+        "});
+        #[cfg(target_os = "windows")]
+        cx.set_state(indoc! {"
+            Go to root/dir/file2.rs if you want.ˇ
+        "});
+
+        let screen_coord = cx.pixel_position(indoc! {"
+            Go to root/diˇr/file2.rs if you want.
+        "});
+
+        cx.simulate_mouse_move(screen_coord, None, Modifiers::secondary_key());
+        cx.assert_editor_text_highlights(
+            HighlightKey::HoveredLinkState,
+            indoc! {"
+            Go to «root/dir/file2.rsˇ» if you want.
+        "},
+        );
+
+        cx.simulate_click(screen_coord, Modifiers::secondary_key());
+
+        cx.update_workspace(|workspace, _, cx| assert_eq!(workspace.items(cx).count(), 2));
+        cx.update_workspace(|workspace, _, cx| {
+            let active_editor = workspace.active_item_as::<Editor>(cx).unwrap();
+
+            let buffer = active_editor
+                .read(cx)
+                .buffer()
+                .read(cx)
+                .as_singleton()
+                .unwrap();
+
+            let file = buffer.read(cx).file().unwrap();
+            let file_path = file.as_local().unwrap().abs_path(cx);
+
+            assert_eq!(
+                file_path,
+                std::path::PathBuf::from(path!("/root/dir/file2.rs"))
+            );
+        });
+    }
+
     #[gpui::test]
     async fn test_hover_directories(cx: &mut gpui::TestAppContext) {
         init_test(cx, |_| {});

crates/project/src/project.rs 🔗

@@ -4590,24 +4590,38 @@ impl Project {
         let worktrees_with_ids: Vec<_> = self
             .worktrees(cx)
             .map(|worktree| {
-                let id = worktree.read(cx).id();
-                (worktree, id)
+                let read = worktree.read(cx);
+                let id = read.id();
+                (
+                    worktree,
+                    id,
+                    read.is_visible().then(|| read.root_name_arc()),
+                )
             })
             .collect();
 
         cx.spawn(async move |_, cx| {
             if let Some(buffer_worktree_id) = buffer_worktree_id
-                && let Some((worktree, _)) = worktrees_with_ids
+                && let Some((worktree, _, root_name)) = worktrees_with_ids
                     .iter()
-                    .find(|(_, id)| *id == buffer_worktree_id)
+                    .find(|(_, id, _)| *id == buffer_worktree_id)
             {
                 for candidate in candidates.iter() {
                     if let Some(path) = Self::resolve_path_in_worktree(worktree, candidate, cx) {
                         return Some(path);
                     }
+                    if let Some(root_name) = root_name {
+                        if let Ok(candidate) = candidate.strip_prefix(root_name) {
+                            if let Some(path) =
+                                Self::resolve_path_in_worktree(worktree, candidate, cx)
+                            {
+                                return Some(path);
+                            }
+                        }
+                    }
                 }
             }
-            for (worktree, id) in worktrees_with_ids {
+            for (worktree, id, root_name) in worktrees_with_ids {
                 if Some(id) == buffer_worktree_id {
                     continue;
                 }
@@ -4615,6 +4629,15 @@ impl Project {
                     if let Some(path) = Self::resolve_path_in_worktree(&worktree, candidate, cx) {
                         return Some(path);
                     }
+                    if let Some(root_name) = &root_name {
+                        if let Ok(candidate) = candidate.strip_prefix(root_name) {
+                            if let Some(path) =
+                                Self::resolve_path_in_worktree(&worktree, candidate, cx)
+                            {
+                                return Some(path);
+                            }
+                        }
+                    }
                 }
             }
             None

crates/worktree/src/worktree.rs 🔗

@@ -2466,6 +2466,10 @@ impl Snapshot {
         &self.root_name
     }
 
+    pub fn root_name_arc(&self) -> Arc<RelPath> {
+        self.root_name.clone()
+    }
+
     pub fn root_name_str(&self) -> &str {
         self.root_name.as_unix_str()
     }