terminal_view: Improve path hyperlink navigation by considering the terminal working directory (#36962)

Dave Waggoner created

Closes #34027

Release Notes:

- Improved terminal path hyperlink navigation by considering the
terminal working directory

Change summary

crates/terminal_view/src/terminal_path_like_target.rs | 717 ++++++++++--
1 file changed, 581 insertions(+), 136 deletions(-)

Detailed changes

crates/terminal_view/src/terminal_path_like_target.rs 🔗

@@ -4,38 +4,63 @@ use editor::Editor;
 use gpui::{App, AppContext, Context, Task, WeakEntity, Window};
 use itertools::Itertools;
 use project::{Entry, Metadata};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use terminal::PathLikeTarget;
 use util::{ResultExt, debug_panic, paths::PathWithPosition};
 use workspace::{OpenOptions, OpenVisible, Workspace};
 
+/// The way we found the open target. This is important to have for test assertions.
+/// For example, remote projects never look in the file system.
+#[cfg(test)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+enum OpenTargetFoundBy {
+    WorktreeExact,
+    WorktreeScan,
+    FileSystemBackground,
+}
+
+#[cfg(test)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+enum BackgroundFsChecks {
+    Enabled,
+    Disabled,
+}
+
 #[derive(Debug, Clone)]
 enum OpenTarget {
-    Worktree(PathWithPosition, Entry),
+    Worktree(PathWithPosition, Entry, #[cfg(test)] OpenTargetFoundBy),
     File(PathWithPosition, Metadata),
 }
 
 impl OpenTarget {
     fn is_file(&self) -> bool {
         match self {
-            OpenTarget::Worktree(_, entry) => entry.is_file(),
+            OpenTarget::Worktree(_, entry, ..) => entry.is_file(),
             OpenTarget::File(_, metadata) => !metadata.is_dir,
         }
     }
 
     fn is_dir(&self) -> bool {
         match self {
-            OpenTarget::Worktree(_, entry) => entry.is_dir(),
+            OpenTarget::Worktree(_, entry, ..) => entry.is_dir(),
             OpenTarget::File(_, metadata) => metadata.is_dir,
         }
     }
 
     fn path(&self) -> &PathWithPosition {
         match self {
-            OpenTarget::Worktree(path, _) => path,
+            OpenTarget::Worktree(path, ..) => path,
             OpenTarget::File(path, _) => path,
         }
     }
+
+    #[cfg(test)]
+    fn found_by(&self) -> OpenTargetFoundBy {
+        match self {
+            OpenTarget::Worktree(.., found_by) => *found_by,
+            OpenTarget::File(..) => OpenTargetFoundBy::FileSystemBackground,
+        }
+    }
 }
 
 pub(super) fn hover_path_like_target(
@@ -44,12 +69,41 @@ pub(super) fn hover_path_like_target(
     path_like_target: &PathLikeTarget,
     cx: &mut Context<TerminalView>,
 ) -> Task<()> {
-    let file_to_open_task = possible_open_target(workspace, path_like_target, cx);
+    #[cfg(not(test))]
+    {
+        possible_hover_target(workspace, hovered_word, path_like_target, cx)
+    }
+    #[cfg(test)]
+    {
+        possible_hover_target(
+            workspace,
+            hovered_word,
+            path_like_target,
+            cx,
+            BackgroundFsChecks::Enabled,
+        )
+    }
+}
+
+fn possible_hover_target(
+    workspace: &WeakEntity<Workspace>,
+    hovered_word: HoveredWord,
+    path_like_target: &PathLikeTarget,
+    cx: &mut Context<TerminalView>,
+    #[cfg(test)] background_fs_checks: BackgroundFsChecks,
+) -> Task<()> {
+    let file_to_open_task = possible_open_target(
+        workspace,
+        path_like_target,
+        cx,
+        #[cfg(test)]
+        background_fs_checks,
+    );
     cx.spawn(async move |terminal_view, cx| {
         let file_to_open = file_to_open_task.await;
         terminal_view
             .update(cx, |terminal_view, _| match file_to_open {
-                Some(OpenTarget::File(path, _) | OpenTarget::Worktree(path, _)) => {
+                Some(OpenTarget::File(path, _) | OpenTarget::Worktree(path, ..)) => {
                     terminal_view.hover = Some(HoverTarget {
                         tooltip: path.to_string(|path| path.to_string_lossy().to_string()),
                         hovered_word,
@@ -67,6 +121,7 @@ fn possible_open_target(
     workspace: &WeakEntity<Workspace>,
     path_like_target: &PathLikeTarget,
     cx: &App,
+    #[cfg(test)] background_fs_checks: BackgroundFsChecks,
 ) -> Task<Option<OpenTarget>> {
     let Some(workspace) = workspace.upgrade() else {
         return Task::ready(None);
@@ -117,9 +172,19 @@ fn possible_open_target(
     // If we won't find paths "easily", we can traverse the entire worktree to look what ends with the potential path suffix.
     // That will be slow, though, so do the fast checks first.
     let mut worktree_paths_to_check = Vec::new();
-    for worktree in &worktree_candidates {
+    let mut is_cwd_in_worktree = false;
+    let mut open_target = None;
+    'worktree_loop: for worktree in &worktree_candidates {
         let worktree_root = worktree.read(cx).abs_path();
         let mut paths_to_check = Vec::with_capacity(potential_paths.len());
+        let relative_cwd = cwd
+            .and_then(|cwd| cwd.strip_prefix(&worktree_root).ok())
+            .and_then(|cwd_stripped| {
+                (cwd_stripped != Path::new("")).then(|| {
+                    is_cwd_in_worktree = true;
+                    cwd_stripped
+                })
+            });
 
         for path_with_position in &potential_paths {
             let path_to_check = if worktree_root.ends_with(&path_with_position.path) {
@@ -130,10 +195,13 @@ fn possible_open_target(
                 };
                 match worktree.read(cx).root_entry() {
                     Some(root_entry) => {
-                        return Task::ready(Some(OpenTarget::Worktree(
+                        open_target = Some(OpenTarget::Worktree(
                             root_path_with_position,
                             root_entry.clone(),
-                        )));
+                            #[cfg(test)]
+                            OpenTargetFoundBy::WorktreeExact,
+                        ));
+                        break 'worktree_loop;
                     }
                     None => root_path_with_position,
                 }
@@ -150,16 +218,26 @@ fn possible_open_target(
             };
 
             if path_to_check.path.is_relative()
-                && let Some(entry) = worktree.read(cx).entry_for_path(&path_to_check.path)
+                && !worktree.read(cx).is_single_file()
+                && let Some(entry) = relative_cwd
+                    .and_then(|relative_cwd| {
+                        worktree
+                            .read(cx)
+                            .entry_for_path(&relative_cwd.join(&path_to_check.path))
+                    })
+                    .or_else(|| worktree.read(cx).entry_for_path(&path_to_check.path))
             {
-                return Task::ready(Some(OpenTarget::Worktree(
+                open_target = Some(OpenTarget::Worktree(
                     PathWithPosition {
                         path: worktree_root.join(&entry.path),
                         row: path_to_check.row,
                         column: path_to_check.column,
                     },
                     entry.clone(),
-                )));
+                    #[cfg(test)]
+                    OpenTargetFoundBy::WorktreeExact,
+                ));
+                break 'worktree_loop;
             }
 
             paths_to_check.push(path_to_check);
@@ -170,101 +248,143 @@ fn possible_open_target(
         }
     }
 
+    #[cfg(not(test))]
+    let enable_background_fs_checks = workspace.read(cx).project().read(cx).is_local();
+    #[cfg(test)]
+    let enable_background_fs_checks = background_fs_checks == BackgroundFsChecks::Enabled;
+
+    if open_target.is_some() {
+        // We we want to prefer open targets found via background fs checks over worktree matches,
+        // however we can return early if either:
+        //   - This is a remote project, or
+        //   - If the terminal working directory is inside of at least one worktree
+        if !enable_background_fs_checks || is_cwd_in_worktree {
+            return Task::ready(open_target);
+        }
+    }
+
     // Before entire worktree traversal(s), make an attempt to do FS checks if available.
-    let fs_paths_to_check = if workspace.read(cx).project().read(cx).is_local() {
-        potential_paths
-            .into_iter()
-            .flat_map(|path_to_check| {
-                let mut paths_to_check = Vec::new();
-                let maybe_path = &path_to_check.path;
-                if maybe_path.starts_with("~") {
-                    if let Some(home_path) =
-                        maybe_path
-                            .strip_prefix("~")
-                            .ok()
-                            .and_then(|stripped_maybe_path| {
-                                Some(dirs::home_dir()?.join(stripped_maybe_path))
-                            })
-                    {
-                        paths_to_check.push(PathWithPosition {
-                            path: home_path,
-                            row: path_to_check.row,
-                            column: path_to_check.column,
-                        });
-                    }
-                } else {
-                    paths_to_check.push(PathWithPosition {
-                        path: maybe_path.clone(),
-                        row: path_to_check.row,
-                        column: path_to_check.column,
-                    });
-                    if maybe_path.is_relative() {
-                        if let Some(cwd) = &cwd {
+    let fs_paths_to_check =
+        if enable_background_fs_checks {
+            let fs_cwd_paths_to_check = cwd
+                .iter()
+                .flat_map(|cwd| {
+                    let mut paths_to_check = Vec::new();
+                    for path_to_check in &potential_paths {
+                        let maybe_path = &path_to_check.path;
+                        if path_to_check.path.is_relative() {
                             paths_to_check.push(PathWithPosition {
-                                path: cwd.join(maybe_path),
+                                path: cwd.join(&maybe_path),
                                 row: path_to_check.row,
                                 column: path_to_check.column,
                             });
                         }
-                        for worktree in &worktree_candidates {
-                            paths_to_check.push(PathWithPosition {
-                                path: worktree.read(cx).abs_path().join(maybe_path),
-                                row: path_to_check.row,
-                                column: path_to_check.column,
-                            });
-                        }
-                    }
-                }
-                paths_to_check
-            })
-            .collect()
-    } else {
-        Vec::new()
-    };
-
-    let worktree_check_task = cx.spawn(async move |cx| {
-        for (worktree, worktree_paths_to_check) in worktree_paths_to_check {
-            let found_entry = worktree
-                .update(cx, |worktree, _| {
-                    let worktree_root = worktree.abs_path();
-                    let traversal = worktree.traverse_from_path(true, true, false, "".as_ref());
-                    for entry in traversal {
-                        if let Some(path_in_worktree) = worktree_paths_to_check
-                            .iter()
-                            .find(|path_to_check| entry.path.ends_with(&path_to_check.path))
-                        {
-                            return Some(OpenTarget::Worktree(
-                                PathWithPosition {
-                                    path: worktree_root.join(&entry.path),
-                                    row: path_in_worktree.row,
-                                    column: path_in_worktree.column,
-                                },
-                                entry.clone(),
-                            ));
-                        }
                     }
-                    None
+                    paths_to_check
                 })
-                .ok()?;
-            if let Some(found_entry) = found_entry {
-                return Some(found_entry);
-            }
-        }
-        None
-    });
+                .collect::<Vec<_>>();
+            fs_cwd_paths_to_check
+                .into_iter()
+                .chain(
+                    potential_paths
+                        .into_iter()
+                        .flat_map(|path_to_check| {
+                            let mut paths_to_check = Vec::new();
+                            let maybe_path = &path_to_check.path;
+                            if maybe_path.starts_with("~") {
+                                if let Some(home_path) = maybe_path.strip_prefix("~").ok().and_then(
+                                    |stripped_maybe_path| {
+                                        Some(dirs::home_dir()?.join(stripped_maybe_path))
+                                    },
+                                ) {
+                                    paths_to_check.push(PathWithPosition {
+                                        path: home_path,
+                                        row: path_to_check.row,
+                                        column: path_to_check.column,
+                                    });
+                                }
+                            } else {
+                                paths_to_check.push(PathWithPosition {
+                                    path: maybe_path.clone(),
+                                    row: path_to_check.row,
+                                    column: path_to_check.column,
+                                });
+                                if maybe_path.is_relative() {
+                                    for worktree in &worktree_candidates {
+                                        if !worktree.read(cx).is_single_file() {
+                                            paths_to_check.push(PathWithPosition {
+                                                path: worktree.read(cx).abs_path().join(maybe_path),
+                                                row: path_to_check.row,
+                                                column: path_to_check.column,
+                                            });
+                                        }
+                                    }
+                                }
+                            }
+                            paths_to_check
+                        })
+                        .collect::<Vec<_>>(),
+                )
+                .collect()
+        } else {
+            Vec::new()
+        };
 
     let fs = workspace.read(cx).project().read(cx).fs().clone();
-    cx.background_spawn(async move {
+    let background_fs_checks_task = cx.background_spawn(async move {
         for mut path_to_check in fs_paths_to_check {
             if let Some(fs_path_to_check) = fs.canonicalize(&path_to_check.path).await.ok()
                 && let Some(metadata) = fs.metadata(&fs_path_to_check).await.ok().flatten()
             {
-                path_to_check.path = fs_path_to_check;
-                return Some(OpenTarget::File(path_to_check, metadata));
+                if open_target
+                    .as_ref()
+                    .map(|open_target| open_target.path().path != fs_path_to_check)
+                    .unwrap_or(true)
+                {
+                    path_to_check.path = fs_path_to_check;
+                    return Some(OpenTarget::File(path_to_check, metadata));
+                }
+
+                break;
             }
         }
 
-        worktree_check_task.await
+        open_target
+    });
+
+    cx.spawn(async move |cx| {
+        background_fs_checks_task.await.or_else(|| {
+            for (worktree, worktree_paths_to_check) in worktree_paths_to_check {
+                let found_entry = worktree
+                    .update(cx, |worktree, _| -> Option<OpenTarget> {
+                        let worktree_root = worktree.abs_path();
+                        let traversal = worktree.traverse_from_path(true, true, false, "".as_ref());
+                        for entry in traversal {
+                            if let Some(path_in_worktree) = worktree_paths_to_check
+                                .iter()
+                                .find(|path_to_check| entry.path.ends_with(&path_to_check.path))
+                            {
+                                return Some(OpenTarget::Worktree(
+                                    PathWithPosition {
+                                        path: worktree_root.join(&entry.path),
+                                        row: path_in_worktree.row,
+                                        column: path_in_worktree.column,
+                                    },
+                                    entry.clone(),
+                                    #[cfg(test)]
+                                    OpenTargetFoundBy::WorktreeScan,
+                                ));
+                            }
+                        }
+                        None
+                    })
+                    .ok()?;
+                if let Some(found_entry) = found_entry {
+                    return Some(found_entry);
+                }
+            }
+            None
+        })
     })
 }
 
@@ -275,8 +395,23 @@ pub(super) fn open_path_like_target(
     window: &mut Window,
     cx: &mut Context<TerminalView>,
 ) {
-    possibly_open_target(workspace, terminal_view, path_like_target, window, cx)
+    #[cfg(not(test))]
+    {
+        possibly_open_target(workspace, terminal_view, path_like_target, window, cx)
+            .detach_and_log_err(cx)
+    }
+    #[cfg(test)]
+    {
+        possibly_open_target(
+            workspace,
+            terminal_view,
+            path_like_target,
+            window,
+            cx,
+            BackgroundFsChecks::Enabled,
+        )
         .detach_and_log_err(cx)
+    }
 }
 
 fn possibly_open_target(
@@ -285,6 +420,7 @@ fn possibly_open_target(
     path_like_target: &PathLikeTarget,
     window: &mut Window,
     cx: &mut Context<TerminalView>,
+    #[cfg(test)] background_fs_checks: BackgroundFsChecks,
 ) -> Task<Result<Option<OpenTarget>>> {
     if terminal_view.hover.is_none() {
         return Task::ready(Ok(None));
@@ -294,7 +430,13 @@ fn possibly_open_target(
     cx.spawn_in(window, async move |terminal_view, cx| {
         let Some(open_target) = terminal_view
             .update(cx, |_, cx| {
-                possible_open_target(&workspace, &path_like_target, cx)
+                possible_open_target(
+                    &workspace,
+                    &path_like_target,
+                    cx,
+                    #[cfg(test)]
+                    background_fs_checks,
+                )
             })?
             .await
         else {
@@ -375,8 +517,11 @@ mod tests {
         app_cx: &mut TestAppContext,
         trees: impl IntoIterator<Item = (&str, serde_json::Value)>,
         worktree_roots: impl IntoIterator<Item = &str>,
-    ) -> impl AsyncFnMut(HoveredWord, PathLikeTarget) -> (Option<HoverTarget>, Option<OpenTarget>)
-    {
+    ) -> impl AsyncFnMut(
+        HoveredWord,
+        PathLikeTarget,
+        BackgroundFsChecks,
+    ) -> (Option<HoverTarget>, Option<OpenTarget>) {
         let fs = app_cx.update(AppState::test).fs.as_fake().clone();
 
         app_cx.update(|cx| {
@@ -393,10 +538,7 @@ mod tests {
 
         let project = Project::test(
             fs.clone(),
-            worktree_roots
-                .into_iter()
-                .map(Path::new)
-                .collect::<Vec<_>>(),
+            worktree_roots.into_iter().map(Path::new),
             app_cx,
         )
         .await;
@@ -424,16 +566,18 @@ mod tests {
         });
 
         async move |hovered_word: HoveredWord,
-                    path_like_target: PathLikeTarget|
+                    path_like_target: PathLikeTarget,
+                    background_fs_checks: BackgroundFsChecks|
                     -> (Option<HoverTarget>, Option<OpenTarget>) {
             let workspace_a = workspace.clone();
             terminal_view
                 .update(cx, |_, cx| {
-                    hover_path_like_target(
+                    possible_hover_target(
                         &workspace_a.downgrade(),
                         hovered_word,
                         &path_like_target,
                         cx,
+                        background_fs_checks,
                     )
                 })
                 .await;
@@ -449,6 +593,7 @@ mod tests {
                         &path_like_target,
                         window,
                         cx,
+                        background_fs_checks,
                     )
                 })
                 .await
@@ -462,10 +607,13 @@ mod tests {
         test_path_like: &mut impl AsyncFnMut(
             HoveredWord,
             PathLikeTarget,
+            BackgroundFsChecks,
         ) -> (Option<HoverTarget>, Option<OpenTarget>),
         maybe_path: &str,
         tooltip: &str,
         terminal_dir: Option<PathBuf>,
+        background_fs_checks: BackgroundFsChecks,
+        mut open_target_found_by: OpenTargetFoundBy,
         file: &str,
         line: u32,
     ) {
@@ -479,6 +627,7 @@ mod tests {
                 maybe_path: maybe_path.to_string(),
                 terminal_dir,
             },
+            background_fs_checks,
         )
         .await;
 
@@ -512,24 +661,70 @@ mod tests {
             Path::new(tooltip),
             "Open target path mismatch at {file}:{line}:"
         );
+
+        if background_fs_checks == BackgroundFsChecks::Disabled
+            && open_target_found_by == OpenTargetFoundBy::FileSystemBackground
+        {
+            open_target_found_by = OpenTargetFoundBy::WorktreeScan;
+        }
+
+        assert_eq!(
+            open_target.found_by(),
+            open_target_found_by,
+            "Open target found by mismatch at {file}:{line}:"
+        );
     }
 
-    macro_rules! none_or_some {
-        () => {
+    macro_rules! none_or_some_pathbuf {
+        (None) => {
             None
         };
-        ($some:expr) => {
-            Some($some)
+        ($cwd:literal) => {
+            Some($crate::PathBuf::from(path!($cwd)))
         };
     }
 
     macro_rules! test_path_like {
-        ($test_path_like:expr, $maybe_path:literal, $tooltip:literal $(, $cwd:literal)?) => {
+        (
+            $test_path_like:expr,
+            $maybe_path:literal,
+            $tooltip:literal,
+            $cwd:tt,
+            $found_by:expr
+        ) => {{
+            test_path_like!(
+                $test_path_like,
+                $maybe_path,
+                $tooltip,
+                $cwd,
+                BackgroundFsChecks::Enabled,
+                $found_by
+            );
+            test_path_like!(
+                $test_path_like,
+                $maybe_path,
+                $tooltip,
+                $cwd,
+                BackgroundFsChecks::Disabled,
+                $found_by
+            );
+        }};
+
+        (
+            $test_path_like:expr,
+            $maybe_path:literal,
+            $tooltip:literal,
+            $cwd:tt,
+            $background_fs_checks:path,
+            $found_by:expr
+        ) => {
             test_path_like_simple(
                 &mut $test_path_like,
                 path!($maybe_path),
                 path!($tooltip),
-                none_or_some!($($crate::PathBuf::from(path!($cwd)))?),
+                none_or_some_pathbuf!($cwd),
+                $background_fs_checks,
+                $found_by,
                 std::file!(),
                 std::line!(),
             )
@@ -537,17 +732,84 @@ mod tests {
         };
     }
 
+    // Note the arms of `test`, `test_local`, and `test_remote` should be collapsed once macro
+    // metavariable expressions (#![feature(macro_metavar_expr)]) are stabilized.
+    // See https://github.com/rust-lang/rust/issues/83527
     #[doc = "test_path_likes!(<cx>, <trees>, <worktrees>, { $(<tests>;)+ })"]
     macro_rules! test_path_likes {
         ($cx:expr, $trees:expr, $worktrees:expr, { $($tests:expr;)+ }) => { {
             let mut test_path_like = init_test($cx, $trees, $worktrees).await;
-            #[doc ="test!(<hovered maybe_path>, <expected tooltip>, <terminal cwd>)"]
+            #[doc ="test!(<hovered maybe_path>, <expected tooltip>, <terminal cwd> "]
+            #[doc ="\\[, found by \\])"]
+            #[allow(unused_macros)]
             macro_rules! test {
-                ($maybe_path:literal, $tooltip:literal) => {
-                    test_path_like!(test_path_like, $maybe_path, $tooltip)
+                ($maybe_path:literal, $tooltip:literal, $cwd:tt) => {
+                    test_path_like!(
+                        test_path_like,
+                        $maybe_path,
+                        $tooltip,
+                        $cwd,
+                        OpenTargetFoundBy::WorktreeExact
+                    )
+                };
+                ($maybe_path:literal, $tooltip:literal, $cwd:tt, $found_by:ident) => {
+                    test_path_like!(
+                        test_path_like,
+                        $maybe_path,
+                        $tooltip,
+                        $cwd,
+                        OpenTargetFoundBy::$found_by
+                    )
+                }
+            }
+            #[doc ="test_local!(<hovered maybe_path>, <expected tooltip>, <terminal cwd> "]
+            #[doc ="\\[, found by \\])"]
+            #[allow(unused_macros)]
+            macro_rules! test_local {
+                ($maybe_path:literal, $tooltip:literal, $cwd:tt) => {
+                    test_path_like!(
+                        test_path_like,
+                        $maybe_path,
+                        $tooltip,
+                        $cwd,
+                        BackgroundFsChecks::Enabled,
+                        OpenTargetFoundBy::WorktreeExact
+                    )
+                };
+                ($maybe_path:literal, $tooltip:literal, $cwd:tt, $found_by:ident) => {
+                    test_path_like!(
+                        test_path_like,
+                        $maybe_path,
+                        $tooltip,
+                        $cwd,
+                        BackgroundFsChecks::Enabled,
+                        OpenTargetFoundBy::$found_by
+                    )
+                }
+            }
+            #[doc ="test_remote!(<hovered maybe_path>, <expected tooltip>, <terminal cwd> "]
+            #[doc ="\\[, found by \\])"]
+            #[allow(unused_macros)]
+            macro_rules! test_remote {
+                ($maybe_path:literal, $tooltip:literal, $cwd:tt) => {
+                    test_path_like!(
+                        test_path_like,
+                        $maybe_path,
+                        $tooltip,
+                        $cwd,
+                        BackgroundFsChecks::Disabled,
+                        OpenTargetFoundBy::WorktreeExact
+                    )
                 };
-                ($maybe_path:literal, $tooltip:literal, $cwd:literal) => {
-                    test_path_like!(test_path_like, $maybe_path, $tooltip, $cwd)
+                ($maybe_path:literal, $tooltip:literal, $cwd:tt, $found_by:ident) => {
+                    test_path_like!(
+                        test_path_like,
+                        $maybe_path,
+                        $tooltip,
+                        $cwd,
+                        BackgroundFsChecks::Disabled,
+                        OpenTargetFoundBy::$found_by
+                    )
                 }
             }
             $($tests);+
@@ -567,8 +829,10 @@ mod tests {
             )],
             vec![path!("/test")],
             {
-                test!("lib.rs", "/test/lib.rs");
-                test!("test.rs", "/test/test.rs");
+                test!("lib.rs", "/test/lib.rs", None);
+                test!("/test/lib.rs", "/test/lib.rs", None);
+                test!("test.rs", "/test/test.rs", None);
+                test!("/test/test.rs", "/test/test.rs", None);
             }
         )
     }
@@ -596,9 +860,15 @@ mod tests {
             vec![path!("/file.txt"), path!("/test")],
             {
                 test!("file.txt", "/file.txt", "/");
+                test!("/file.txt", "/file.txt", "/");
+
                 test!("lib.rs", "/test/lib.rs", "/test");
                 test!("test.rs", "/test/test.rs", "/test");
                 test!("file.txt", "/test/file.txt", "/test");
+
+                test!("/test/lib.rs", "/test/lib.rs", "/test");
+                test!("/test/test.rs", "/test/test.rs", "/test");
+                test!("/test/file.txt", "/test/file.txt", "/test");
             }
         )
     }
@@ -649,7 +919,7 @@ mod tests {
                 )],
                 vec![path!("/dir1")],
                 {
-                    test!("C.py", "/dir1/dir 2/C.py", "/dir1");
+                    test!("C.py", "/dir1/dir 2/C.py", "/dir1", WorktreeScan);
                     test!("C.py", "/dir1/dir 2/C.py", "/dir1/dir 2");
                     test!("C.py", "/dir1/dir 3/C.py", "/dir1/dir 3");
                 }
@@ -660,7 +930,6 @@ mod tests {
         // See https://github.com/zed-industries/zed/issues/34027
         // See https://github.com/zed-industries/zed/issues/33498
         #[gpui::test]
-        #[should_panic(expected = "Tooltip mismatch")]
         async fn issue_28407_nesting(cx: &mut TestAppContext) {
             test_path_likes!(
                 cx,
@@ -669,7 +938,8 @@ mod tests {
                     json!({
                         "lib": {
                             "src": {
-                                "main.rs": ""
+                                "main.rs": "",
+                                "only_in_lib.rs": ""
                             },
                         },
                         "src": {
@@ -679,22 +949,12 @@ mod tests {
                 )],
                 vec![path!("/project")],
                 {
-                    // Failing currently
-                    test!("main.rs", "/project/src/main.rs", "/project");
                     test!("main.rs", "/project/src/main.rs", "/project/src");
-                    test!("main.rs", "/project/lib/src/main.rs", "/project/lib");
                     test!("main.rs", "/project/lib/src/main.rs", "/project/lib/src");
 
                     test!("src/main.rs", "/project/src/main.rs", "/project");
                     test!("src/main.rs", "/project/src/main.rs", "/project/src");
-                    // Failing currently
                     test!("src/main.rs", "/project/lib/src/main.rs", "/project/lib");
-                    // Failing currently
-                    test!(
-                        "src/main.rs",
-                        "/project/lib/src/main.rs",
-                        "/project/lib/src"
-                    );
 
                     test!("lib/src/main.rs", "/project/lib/src/main.rs", "/project");
                     test!(
@@ -712,11 +972,19 @@ mod tests {
                         "/project/lib/src/main.rs",
                         "/project/lib/src"
                     );
+                    test!(
+                        "src/only_in_lib.rs",
+                        "/project/lib/src/only_in_lib.rs",
+                        "/project/lib/src",
+                        WorktreeScan
+                    );
                 }
             )
         }
 
         // https://github.com/zed-industries/zed/issues/28339
+        // Note: These could all be found by WorktreeExact if we used
+        // `fs::normalize_path(&maybe_path)`
         #[gpui::test]
         async fn issue_28339(cx: &mut TestAppContext) {
             test_path_likes!(
@@ -733,32 +1001,90 @@ mod tests {
                 )],
                 vec![path!("/tmp")],
                 {
-                    test!(
+                    test_local!(
                         "foo/./bar.txt",
                         "/tmp/issue28339/foo/bar.txt",
                         "/tmp/issue28339"
                     );
-                    test!(
+                    test_local!(
+                        "foo/../foo/bar.txt",
+                        "/tmp/issue28339/foo/bar.txt",
+                        "/tmp/issue28339",
+                        FileSystemBackground
+                    );
+                    test_local!(
+                        "foo/..///foo/bar.txt",
+                        "/tmp/issue28339/foo/bar.txt",
+                        "/tmp/issue28339",
+                        FileSystemBackground
+                    );
+                    test_local!(
+                        "issue28339/../issue28339/foo/../foo/bar.txt",
+                        "/tmp/issue28339/foo/bar.txt",
+                        "/tmp/issue28339",
+                        FileSystemBackground
+                    );
+                    test_local!(
+                        "./bar.txt",
+                        "/tmp/issue28339/foo/bar.txt",
+                        "/tmp/issue28339/foo"
+                    );
+                    test_local!(
+                        "../foo/bar.txt",
+                        "/tmp/issue28339/foo/bar.txt",
+                        "/tmp/issue28339/foo",
+                        FileSystemBackground
+                    );
+                }
+            )
+        }
+
+        // https://github.com/zed-industries/zed/issues/28339
+        // Note: These could all be found by WorktreeExact if we used
+        // `fs::normalize_path(&maybe_path)`
+        #[gpui::test]
+        #[should_panic(expected = "Hover target should not be `None`")]
+        async fn issue_28339_remote(cx: &mut TestAppContext) {
+            test_path_likes!(
+                cx,
+                vec![(
+                    path!("/tmp"),
+                    json!({
+                        "issue28339": {
+                            "foo": {
+                                "bar.txt": ""
+                            },
+                        },
+                    }),
+                )],
+                vec![path!("/tmp")],
+                {
+                    test_remote!(
+                        "foo/./bar.txt",
+                        "/tmp/issue28339/foo/bar.txt",
+                        "/tmp/issue28339"
+                    );
+                    test_remote!(
                         "foo/../foo/bar.txt",
                         "/tmp/issue28339/foo/bar.txt",
                         "/tmp/issue28339"
                     );
-                    test!(
+                    test_remote!(
                         "foo/..///foo/bar.txt",
                         "/tmp/issue28339/foo/bar.txt",
                         "/tmp/issue28339"
                     );
-                    test!(
+                    test_remote!(
                         "issue28339/../issue28339/foo/../foo/bar.txt",
                         "/tmp/issue28339/foo/bar.txt",
                         "/tmp/issue28339"
                     );
-                    test!(
+                    test_remote!(
                         "./bar.txt",
                         "/tmp/issue28339/foo/bar.txt",
                         "/tmp/issue28339/foo"
                     );
-                    test!(
+                    test_remote!(
                         "../foo/bar.txt",
                         "/tmp/issue28339/foo/bar.txt",
                         "/tmp/issue28339/foo"
@@ -769,7 +1095,6 @@ mod tests {
 
         // https://github.com/zed-industries/zed/issues/34027
         #[gpui::test]
-        #[should_panic(expected = "Tooltip mismatch")]
         async fn issue_34027(cx: &mut TestAppContext) {
             test_path_likes!(
                 cx,
@@ -796,8 +1121,128 @@ mod tests {
 
         // https://github.com/zed-industries/zed/issues/34027
         #[gpui::test]
-        #[should_panic(expected = "Tooltip mismatch")]
-        async fn issue_34027_non_worktree_file(cx: &mut TestAppContext) {
+        async fn issue_34027_siblings(cx: &mut TestAppContext) {
+            test_path_likes!(
+                cx,
+                vec![(
+                    path!("/test"),
+                    json!({
+                        "sub1": {
+                            "file.txt": "",
+                        },
+                        "sub2": {
+                            "file.txt": "",
+                        }
+                    }),
+                ),],
+                vec![path!("/test")],
+                {
+                    test!("file.txt", "/test/sub1/file.txt", "/test/sub1");
+                    test!("file.txt", "/test/sub2/file.txt", "/test/sub2");
+                    test!("sub1/file.txt", "/test/sub1/file.txt", "/test/sub1");
+                    test!("sub2/file.txt", "/test/sub2/file.txt", "/test/sub2");
+                    test!("sub1/file.txt", "/test/sub1/file.txt", "/test/sub2");
+                    test!("sub2/file.txt", "/test/sub2/file.txt", "/test/sub1");
+                }
+            )
+        }
+
+        // https://github.com/zed-industries/zed/issues/34027
+        #[gpui::test]
+        async fn issue_34027_nesting(cx: &mut TestAppContext) {
+            test_path_likes!(
+                cx,
+                vec![(
+                    path!("/test"),
+                    json!({
+                        "sub1": {
+                            "file.txt": "",
+                            "subsub1": {
+                                "file.txt": "",
+                            }
+                        },
+                        "sub2": {
+                            "file.txt": "",
+                            "subsub1": {
+                                "file.txt": "",
+                            }
+                        }
+                    }),
+                ),],
+                vec![path!("/test")],
+                {
+                    test!(
+                        "file.txt",
+                        "/test/sub1/subsub1/file.txt",
+                        "/test/sub1/subsub1"
+                    );
+                    test!(
+                        "file.txt",
+                        "/test/sub2/subsub1/file.txt",
+                        "/test/sub2/subsub1"
+                    );
+                    test!(
+                        "subsub1/file.txt",
+                        "/test/sub1/subsub1/file.txt",
+                        "/test",
+                        WorktreeScan
+                    );
+                    test!(
+                        "subsub1/file.txt",
+                        "/test/sub1/subsub1/file.txt",
+                        "/test",
+                        WorktreeScan
+                    );
+                    test!(
+                        "subsub1/file.txt",
+                        "/test/sub1/subsub1/file.txt",
+                        "/test/sub1"
+                    );
+                    test!(
+                        "subsub1/file.txt",
+                        "/test/sub2/subsub1/file.txt",
+                        "/test/sub2"
+                    );
+                    test!(
+                        "subsub1/file.txt",
+                        "/test/sub1/subsub1/file.txt",
+                        "/test/sub1/subsub1",
+                        WorktreeScan
+                    );
+                }
+            )
+        }
+
+        // https://github.com/zed-industries/zed/issues/34027
+        #[gpui::test]
+        async fn issue_34027_non_worktree_local_file(cx: &mut TestAppContext) {
+            test_path_likes!(
+                cx,
+                vec![
+                    (
+                        path!("/"),
+                        json!({
+                            "file.txt": "",
+                        }),
+                    ),
+                    (
+                        path!("/test"),
+                        json!({
+                            "file.txt": "",
+                        }),
+                    ),
+                ],
+                vec![path!("/test")],
+                {
+                    // Note: Opening a non-worktree file adds that file as a single file worktree.
+                    test_local!("file.txt", "/file.txt", "/", FileSystemBackground);
+                }
+            )
+        }
+
+        // https://github.com/zed-industries/zed/issues/34027
+        #[gpui::test]
+        async fn issue_34027_non_worktree_remote_file(cx: &mut TestAppContext) {
             test_path_likes!(
                 cx,
                 vec![