Lookup relative paths in a worktree more robustly (#29274)

Attempt to lookup exact relative paths before full worktree traversal,
only do the full traversal if all other methods fail.

Closes https://github.com/zed-industries/zed/issues/28407

Release Notes:

- Fixed wrong paths opening when cmd-clicking in the terminal
This commit is contained in:
Kirill Bulatov 2025-04-23 16:13:28 +03:00 committed by GitHub
parent 76a78b550b
commit d5f3fbdc88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1020,7 +1020,7 @@ fn possible_open_target(
workspace: &WeakEntity<Workspace>,
cwd: &Option<PathBuf>,
maybe_path: &str,
cx: &mut Context<TerminalView>,
cx: &App,
) -> Task<Option<OpenTarget>> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(None);
@ -1061,15 +1061,22 @@ fn possible_open_target(
});
}
}
potential_paths.insert(0, original_path);
potential_paths.insert(1, path_with_position);
let insert_both_paths = original_path != path_with_position;
potential_paths.insert(0, original_path);
if insert_both_paths {
potential_paths.insert(1, path_with_position);
}
// 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 worktree_root = worktree.read(cx).abs_path();
let mut paths_to_check = Vec::with_capacity(potential_paths.len());
for path_with_position in &potential_paths {
if worktree_root.ends_with(&path_with_position.path) {
let path_to_check = if worktree_root.ends_with(&path_with_position.path) {
let root_path_with_posiition = PathWithPosition {
path: worktree_root.to_path_buf(),
row: path_with_position.row,
@ -1082,10 +1089,10 @@ fn possible_open_target(
root_entry.clone(),
)));
}
None => paths_to_check.push(root_path_with_posiition),
None => root_path_with_posiition,
}
} else {
paths_to_check.push(PathWithPosition {
PathWithPosition {
path: path_with_position
.path
.strip_prefix(&worktree_root)
@ -1093,89 +1100,120 @@ fn possible_open_target(
.to_owned(),
row: path_with_position.row,
column: path_with_position.column,
});
}
};
}
let mut traversal = worktree
.read(cx)
.traverse_from_path(true, true, false, "".as_ref());
while let Some(entry) = traversal.next() {
if let Some(path_in_worktree) = paths_to_check
.iter()
.find(|path_to_check| entry.path.ends_with(&path_to_check.path))
{
if let Some(entry) = worktree.read(cx).entry_for_path(&path_to_check.path) {
return Task::ready(Some(OpenTarget::Worktree(
PathWithPosition {
path: worktree_root.join(&entry.path),
row: path_in_worktree.row,
column: path_in_worktree.column,
row: path_to_check.row,
column: path_to_check.column,
},
entry.clone(),
)));
} else {
paths_to_check.push(path_to_check);
}
}
if !paths_to_check.is_empty() {
worktree_paths_to_check.push((worktree.clone(), paths_to_check));
}
}
if !workspace.read(cx).project().read(cx).is_local() {
return Task::ready(None);
}
let fs = workspace.read(cx).project().read(cx).fs().clone();
let paths_to_check = 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))
})
{
// 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: home_path,
path: maybe_path.clone(),
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 {
paths_to_check.push(PathWithPosition {
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,
});
if maybe_path.is_relative() {
if let Some(cwd) = &cwd {
paths_to_check.push(PathWithPosition {
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::<Vec<_>>();
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 mut traversal = worktree.traverse_from_path(true, true, false, "".as_ref());
while let Some(entry) = traversal.next() {
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
})
.ok()?;
if let Some(found_entry) = found_entry {
return Some(found_entry);
}
}
None
});
let fs = workspace.read(cx).project().read(cx).fs().clone();
cx.background_spawn(async move {
for path_to_check in paths_to_check {
for path_to_check in fs_paths_to_check {
if let Some(metadata) = fs.metadata(&path_to_check.path).await.ok().flatten() {
return Some(OpenTarget::File(path_to_check, metadata));
}
}
None
worktree_check_task.await
})
}