mirror of
https://github.com/zed-industries/zed.git
synced 2026-10-19 02:54:11 +00:00
path: Distinguish is_absolute and has_root in PathStyle (#65468)
## Summary
Follow-up of #64149,
Zed currently has three handcrafted, identical `is_absolute()`
functions, living in `crates/path/src/path.rs` and
`crates/util/src/paths.rs`. They read as follows:
93d7456688/crates/path/src/path.rs (L409-L420)
In fact, these handcrafted methods behave like `Path::has_root()` rather
than `Path::is_absolute()`. The difference between the two is that
`Path::has_root()` only checks whether a path has a root, whereas
`Path::is_absolute()` checks whether the path is truly absolute. On
Unix, which has a single root, `has_root()` and `is_absolute()`
coincide. Windows, however, is multi-rooted (`C:\` and `D:\` are
different roots), so a rooted path such as `\temp\path` is not absolute:
depending on the working directory, it may resolve to `C:\temp\path` or
`D:\temp\path`, which are different paths. This misleading naming can
lead to misuse of the API.
This PR does three things:
1. Deduplicates the methods, keeping only `is_absolute()` in the
`PathStyle` body.
2. Reimplements `PathStyle::has_root()` and `PathStyle::is_absolute()`
based on the standard library’s implementations; the former replaces the
old `is_absolute()`.
3. Updates some callers of the old `is_absolute()` (now `has_root()`,
per step 2) to the new `is_absolute()`, based on each call site’s
semantics and the actual behavior of the methods.
Steps 1 and 2 are pure refactors; the only functional change is in step
3. I believe this fixes some edge cases — or at least surfaces things
that need fixing — though I am not entirely certain that every
replacement is correct.
## Testing
No new tests are introduced. The refactored parts pass the existing
tests in the affected crates.
## Self-review
- [x] I've reviewed my diff for quality, security, reliability, and
performance.
- [x] UI changes follow the
[checklist](https://zed.dev/docs/development/ui-checklist).
- [ ] Tests cover the new or changed behavior.
Release Notes:
- N/A
This commit is contained in:
parent
fa1b94c6b9
commit
51a3a0e971
18 changed files with 58 additions and 98 deletions
|
|
@ -58,10 +58,7 @@ use text::Bias;
|
|||
use ui::App;
|
||||
use util::markdown::{MarkdownCodeBlock, MarkdownEscaped};
|
||||
use util::path_list::PathList;
|
||||
use util::{
|
||||
ResultExt, get_default_system_shell_preferring_bash,
|
||||
paths::{PathStyle, is_absolute},
|
||||
};
|
||||
use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Returned when the model stops because it exhausted its output token budget.
|
||||
|
|
@ -1745,10 +1742,7 @@ impl ToolCall {
|
|||
.update(cx, |project, cx| {
|
||||
if let Some(path) = project.project_path_for_absolute_path(&location.path, cx) {
|
||||
Some(project.open_buffer(path, cx))
|
||||
} else if is_absolute(
|
||||
location.path.to_string_lossy().as_ref(),
|
||||
project.path_style(cx),
|
||||
) {
|
||||
} else if project.path_style(cx).is_absolute(&location.path) {
|
||||
Some(project.open_local_buffer(&location.path, cx))
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use anyhow::{Context as _, Result, ensure};
|
||||
use diffy::{Line, Patch};
|
||||
use util::paths::{PathStyle, is_absolute};
|
||||
use std::path::Path;
|
||||
use util::paths::PathStyle;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(super) struct PatchFile {
|
||||
|
|
@ -94,8 +95,8 @@ fn patch_path(path: Option<&str>, prefix: &str) -> Result<Option<String>> {
|
|||
}
|
||||
let path = path.strip_prefix(prefix).unwrap_or(path);
|
||||
ensure!(
|
||||
is_absolute(path, PathStyle::Windows),
|
||||
"patch path must be absolute: {path}"
|
||||
PathStyle::Windows.has_root(Path::new(path)),
|
||||
"patch path must have root: {path}"
|
||||
);
|
||||
ensure!(!path.contains('\0'), "patch path contains NUL");
|
||||
Ok(Some(path.to_owned()))
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use url::Url;
|
|||
use urlencoding::decode;
|
||||
use util::{
|
||||
ResultExt,
|
||||
paths::{PathStyle, PathWithPosition, is_absolute},
|
||||
paths::{PathStyle, PathWithPosition},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
|
|
@ -94,7 +94,7 @@ impl MentionUri {
|
|||
Ok(())
|
||||
};
|
||||
|
||||
if is_absolute(input, path_style) && !input.contains("://") {
|
||||
if path_style.has_root(Path::new(input)) && !input.contains("://") {
|
||||
return parse_absolute_path(input)
|
||||
.with_context(|| format!("Invalid absolute path mention URI: {input}"));
|
||||
}
|
||||
|
|
@ -634,7 +634,7 @@ fn bare_path_target(input: &str, path_style: PathStyle) -> Option<&str> {
|
|||
.strip_prefix('`')
|
||||
.and_then(|input| input.strip_suffix('`'))
|
||||
.unwrap_or(input);
|
||||
(is_absolute(input, path_style) && !input.contains("://")).then_some(input)
|
||||
(path_style.has_root(Path::new(input)) && !input.contains("://")).then_some(input)
|
||||
}
|
||||
|
||||
fn split_path_fragment(input: &str) -> (&str, Option<&str>) {
|
||||
|
|
|
|||
|
|
@ -1394,7 +1394,7 @@ fn resolve_cd_in_worktrees(
|
|||
) -> Option<PathBuf> {
|
||||
let cd = path_style.normalize(cd);
|
||||
let cd_path = Path::new(&cd);
|
||||
let is_absolute = path_style.is_absolute(&cd);
|
||||
let is_absolute = path_style.is_absolute(cd_path);
|
||||
|
||||
worktree_roots.iter().find_map(|(root_name, abs_path)| {
|
||||
let prefix = if is_absolute {
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ pub(crate) fn project_path_for_file_link(
|
|||
Some(project_path)
|
||||
};
|
||||
let path = SanitizedPath::new(path).as_path();
|
||||
if path_style.is_absolute(path.to_str()?) {
|
||||
if path_style.is_absolute(path) {
|
||||
let project_path = project.project_path_for_absolute_path(path, cx)?;
|
||||
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
|
||||
let root = worktree.read(cx).abs_path();
|
||||
|
|
|
|||
|
|
@ -3912,19 +3912,17 @@ impl AgentCodeSpanResolver {
|
|||
worktree_abs_path: &Path,
|
||||
path_style: PathStyle,
|
||||
) -> Vec<Arc<RelPath>> {
|
||||
let path_text = path.to_string_lossy();
|
||||
let relative_path: Option<Arc<RelPath>> =
|
||||
if util::paths::is_absolute(path_text.as_ref(), path_style) {
|
||||
path_style
|
||||
.strip_prefix(path, worktree_abs_path)
|
||||
.map(std::borrow::Cow::into_owned)
|
||||
.map(Into::into)
|
||||
} else {
|
||||
RelPath::new(path, path_style)
|
||||
.ok()
|
||||
.map(std::borrow::Cow::into_owned)
|
||||
.map(Into::into)
|
||||
};
|
||||
let relative_path: Option<Arc<RelPath>> = if path_style.is_absolute(path) {
|
||||
path_style
|
||||
.strip_prefix(path, worktree_abs_path)
|
||||
.map(std::borrow::Cow::into_owned)
|
||||
.map(Into::into)
|
||||
} else {
|
||||
RelPath::new(path, path_style)
|
||||
.ok()
|
||||
.map(std::borrow::Cow::into_owned)
|
||||
.map(Into::into)
|
||||
};
|
||||
|
||||
let Some(relative_path) = relative_path else {
|
||||
return Vec::new();
|
||||
|
|
|
|||
|
|
@ -13427,7 +13427,7 @@ pub(crate) fn open_link(
|
|||
));
|
||||
break;
|
||||
}
|
||||
let paths = if path_style.is_absolute(&path.to_string_lossy())
|
||||
let paths = if path_style.has_root(&path)
|
||||
|| path.starts_with("~")
|
||||
{
|
||||
vec![path]
|
||||
|
|
@ -13625,7 +13625,7 @@ fn file_link_parts(input: &str, path_style: PathStyle) -> Option<(&str, Option<&
|
|||
let (path, fragment) = input
|
||||
.split_once('#')
|
||||
.map_or((input, None), |(path, fragment)| (path, Some(fragment)));
|
||||
if !path_style.is_absolute(path)
|
||||
if !path_style.has_root(Path::new(path))
|
||||
&& let Ok(url) = Url::parse(input)
|
||||
&& (!url.scheme().contains('.')
|
||||
|| url.path().trim_matches(':').is_empty()
|
||||
|
|
@ -13644,7 +13644,7 @@ fn file_link_candidates(
|
|||
fragment_point: Option<Point>,
|
||||
path_style: PathStyle,
|
||||
) -> Vec<(PathBuf, Option<Point>)> {
|
||||
if path_style.is_windows() && path_style.is_absolute(path) {
|
||||
if path_style.is_windows() && path_style.has_root(Path::new(path)) {
|
||||
return [
|
||||
MentionUri::parse_hyperlink(path, path_style).ok(),
|
||||
MentionUri::parse_hyperlink_literal(path, path_style),
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ use gpui::{
|
|||
Action, AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState,
|
||||
Subscription, Task, TaskExt, WeakEntity, list,
|
||||
};
|
||||
use util::{
|
||||
debug_panic,
|
||||
paths::{PathStyle, is_absolute},
|
||||
};
|
||||
use util::{debug_panic, paths::PathStyle};
|
||||
|
||||
use crate::ToggleUserFrames;
|
||||
use language::PointUtf16;
|
||||
|
|
@ -528,7 +525,8 @@ impl StackFrameList {
|
|||
.filter(|path| {
|
||||
// Since we do not know if we are debugging on the host or (a remote/WSL) target,
|
||||
// we need to check if either the path is absolute as Posix or Windows.
|
||||
is_absolute(path, PathStyle::Unix) || is_absolute(path, PathStyle::Windows)
|
||||
let path = Path::new(path);
|
||||
PathStyle::Unix.is_absolute(path) || PathStyle::Windows.is_absolute(path)
|
||||
})
|
||||
.map(|path| Arc::<Path>::from(Path::new(path)))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1571,7 +1571,7 @@ fn resolve_preview_image(
|
|||
let path = if let (Some(stripped), Some(root)) = (workspace_relative_path, workspace_directory)
|
||||
{
|
||||
client_path_style.join_path(root, stripped).ok()?
|
||||
} else if client_path_style.is_absolute(&decoded) {
|
||||
} else if client_path_style.has_root(Path::new(&decoded)) {
|
||||
PathBuf::from(decoded)
|
||||
} else {
|
||||
client_path_style.join_path(base_directory?, decoded).ok()?
|
||||
|
|
@ -1593,7 +1593,7 @@ fn resolve_project_path_for_preview_image(
|
|||
.worktree_for_id(source_project_path.worktree_id, cx)?;
|
||||
let path_style = worktree.read(cx).path_style();
|
||||
|
||||
if workspace_relative_path.is_none() && path_style.is_absolute(decoded_path) {
|
||||
if workspace_relative_path.is_none() && path_style.is_absolute(Path::new(decoded_path)) {
|
||||
return project
|
||||
.read(cx)
|
||||
.project_path_for_absolute_path(Path::new(decoded_path), cx);
|
||||
|
|
|
|||
|
|
@ -103,17 +103,15 @@ impl PathStyle {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_absolute(&self, path_like: &str) -> bool {
|
||||
path_like.starts_with('/')
|
||||
|| *self == PathStyle::Windows
|
||||
&& (path_like.starts_with('\\')
|
||||
|| path_like
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|c| c.is_ascii_alphabetic())
|
||||
&& path_like[1..]
|
||||
.strip_prefix(':')
|
||||
.is_some_and(|path| path.starts_with('/') || path.starts_with('\\')))
|
||||
pub fn is_absolute(&self, path: &Path) -> bool {
|
||||
match self {
|
||||
PathStyle::Unix => self.has_root(path),
|
||||
PathStyle::Windows => self.has_root(path) && self.components(path).prefix().is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_root(&self, path: &Path) -> bool {
|
||||
self.components(path).has_root()
|
||||
}
|
||||
|
||||
pub fn is_windows(&self) -> bool {
|
||||
|
|
@ -125,10 +123,11 @@ impl PathStyle {
|
|||
}
|
||||
|
||||
pub fn join(self, left: impl AsRef<Path>, right: impl AsRef<Path>) -> Option<String> {
|
||||
let right = right.as_ref().to_str()?;
|
||||
if is_absolute(right, self) {
|
||||
let right = right.as_ref();
|
||||
if self.has_root(right) {
|
||||
return None;
|
||||
}
|
||||
let right = right.to_str()?;
|
||||
let left = left.as_ref().to_str()?;
|
||||
if left.is_empty() {
|
||||
Some(right.into())
|
||||
|
|
@ -406,19 +405,6 @@ impl PathStyle {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_absolute(path_like: &str, path_style: PathStyle) -> bool {
|
||||
path_like.starts_with('/')
|
||||
|| path_style == PathStyle::Windows
|
||||
&& (path_like.starts_with('\\')
|
||||
|| path_like
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|c| c.is_ascii_alphabetic())
|
||||
&& path_like[1..]
|
||||
.strip_prefix(':')
|
||||
.is_some_and(|path| path.starts_with('/') || path.starts_with('\\')))
|
||||
}
|
||||
|
||||
/// Normalizes a path by resolving `.` and `..` components without
|
||||
/// requiring the path to exist on disk (unlike `canonicalize`).
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
|
|
|
|||
|
|
@ -173,6 +173,10 @@ impl<'a> Components<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn prefix(&self) -> Option<Prefix<'_>> {
|
||||
self.prefix
|
||||
}
|
||||
|
||||
fn has_prefixes(&self) -> bool {
|
||||
self.path_style.has_prefixes()
|
||||
}
|
||||
|
|
@ -248,7 +252,7 @@ impl<'a> Components<'a> {
|
|||
unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(comps.path)) }
|
||||
}
|
||||
|
||||
fn has_root(&self) -> bool {
|
||||
pub(crate) fn has_root(&self) -> bool {
|
||||
if self.has_physical_root {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use std::{
|
|||
use crate::{
|
||||
PathStyle,
|
||||
abs_path::{AbsPath, AbsPathBuf},
|
||||
is_absolute,
|
||||
};
|
||||
|
||||
/// A file system path that is guaranteed to be relative and normalized.
|
||||
|
|
@ -73,7 +72,7 @@ impl RelPath {
|
|||
path = prefix;
|
||||
}
|
||||
|
||||
if is_absolute(&path, path_style) {
|
||||
if path_style.has_root(Path::new(&path)) {
|
||||
return Err(anyhow!("absolute path not allowed: {path:?}"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10846,12 +10846,8 @@ pub fn worktrees_directory_for_repo(
|
|||
) -> Result<PathBuf> {
|
||||
// Check the original setting before trimming, since a path like "///"
|
||||
// is absolute but becomes "" after stripping trailing separators.
|
||||
// Also check for leading `/` or `\` explicitly, because on Windows
|
||||
// `Path::is_absolute()` requires a drive letter — so `/tmp/worktrees`
|
||||
// would slip through even though it's clearly not a relative path.
|
||||
if path_style.is_absolute(worktree_directory_setting)
|
||||
|| worktree_directory_setting.starts_with('\\')
|
||||
{
|
||||
// Use `has_root` here because `/tmp/worktrees` is also not a relative path
|
||||
if path_style.has_root(Path::new(worktree_directory_setting)) {
|
||||
anyhow::bail!(
|
||||
"git.worktree_directory must be a relative path, got: {worktree_directory_setting:?}"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ use toolchain_store::EmptyToolchainStore;
|
|||
use util::{
|
||||
ResultExt as _, maybe,
|
||||
path_list::PathList,
|
||||
paths::{PathStyle, SanitizedPath, is_absolute},
|
||||
paths::{PathStyle, SanitizedPath},
|
||||
rel_path::RelPath,
|
||||
};
|
||||
use worktree::{CreatedEntry, Snapshot, Traversal};
|
||||
|
|
@ -4972,7 +4972,7 @@ impl Project {
|
|||
buffer: &Entity<Buffer>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<ResolvedPath>> {
|
||||
if util::paths::is_absolute(path, self.path_style(cx)) || path.starts_with("~") {
|
||||
if self.path_style(cx).has_root(Path::new(path)) || path.starts_with("~") {
|
||||
self.resolve_abs_path(path, cx)
|
||||
} else {
|
||||
self.resolve_path_in_worktrees(path, buffer, cx)
|
||||
|
|
@ -5421,7 +5421,7 @@ impl Project {
|
|||
let path = path.as_ref();
|
||||
let worktree_store = self.worktree_store.read(cx);
|
||||
|
||||
if is_absolute(&path.to_string_lossy(), path_style) {
|
||||
if path_style.has_root(path) {
|
||||
for worktree in worktree_store.visible_worktrees(cx) {
|
||||
let worktree_abs_path = worktree.read(cx).abs_path();
|
||||
|
||||
|
|
|
|||
|
|
@ -317,10 +317,7 @@ impl TrustedWorktreesStore {
|
|||
}
|
||||
PathTrust::AbsPath(abs_path) => {
|
||||
debug_assert!(
|
||||
util::paths::is_absolute(
|
||||
&abs_path.to_string_lossy(),
|
||||
worktree_store.read(cx).path_style()
|
||||
),
|
||||
worktree_store.read(cx).path_style().is_absolute(&abs_path),
|
||||
"Cannot trust non-absolute path {abs_path:?} on path style {style:?}",
|
||||
style = worktree_store.read(cx).path_style()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -364,19 +364,6 @@ impl Display for RemotePathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_absolute(path_like: &str, path_style: PathStyle) -> bool {
|
||||
path_like.starts_with('/')
|
||||
|| path_style == PathStyle::Windows
|
||||
&& (path_like.starts_with('\\')
|
||||
|| path_like
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|c| c.is_ascii_alphabetic())
|
||||
&& path_like[1..]
|
||||
.strip_prefix(':')
|
||||
.is_some_and(|path| path.starts_with('/') || path.starts_with('\\')))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub struct NormalizeError;
|
||||
|
|
@ -1414,7 +1401,7 @@ impl UrlExt for url::Url {
|
|||
|
||||
let path = String::from_utf8(bytes).map_err(|_| ())?;
|
||||
debug_assert!(
|
||||
PathStyle::Unix.is_absolute(&path),
|
||||
PathStyle::Unix.is_absolute(Path::new(&path)),
|
||||
"to_file_path() failed to produce an absolute Path"
|
||||
);
|
||||
|
||||
|
|
@ -1485,7 +1472,7 @@ impl UrlExt for url::Url {
|
|||
);
|
||||
}
|
||||
debug_assert!(
|
||||
PathStyle::Windows.is_absolute(&string),
|
||||
PathStyle::Windows.is_absolute(Path::new(&string)),
|
||||
"to_file_path() failed to produce an absolute Path"
|
||||
);
|
||||
let path = PathBuf::from(string);
|
||||
|
|
|
|||
|
|
@ -518,7 +518,7 @@ fn validate_trust_scope(
|
|||
),
|
||||
_ => PathBuf::from(trimmed),
|
||||
};
|
||||
if !util::paths::is_absolute(&expanded.to_string_lossy(), path_style) {
|
||||
if !path_style.is_absolute(&expanded) {
|
||||
return Err("Enter an absolute folder path".into());
|
||||
}
|
||||
if !project.starts_with(&expanded) {
|
||||
|
|
|
|||
|
|
@ -5213,7 +5213,7 @@ impl Workspace {
|
|||
let project_is_local = project.read(cx).is_local();
|
||||
|
||||
// If it's an absolute path, open it directly
|
||||
if path_style.is_absolute(url_or_path) {
|
||||
if path_style.has_root(Path::new(url_or_path)) {
|
||||
open_abs_path(self, PathBuf::from(url_or_path), cx);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue