mirror of
https://github.com/zed-industries/zed.git
synced 2026-10-20 03:24:42 +00:00
Split out RelPath into a separate crate (#61029)
This is necessary to remove some `util` dependencies from crates, as well as better sharing for our projects. This also includes the WIP AbsPath abstraction as well as some bug fixes from internal tooling. Release Notes: - N/A or Added/Fixed/Improved ...
This commit is contained in:
parent
febe242c0b
commit
f181a2f47b
93 changed files with 1458 additions and 839 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -6320,6 +6320,7 @@ dependencies = [
|
|||
"log",
|
||||
"lsp",
|
||||
"parking_lot",
|
||||
"path",
|
||||
"pretty_assertions",
|
||||
"proto",
|
||||
"semver",
|
||||
|
|
@ -6909,6 +6910,7 @@ dependencies = [
|
|||
"log",
|
||||
"notify 9.0.0-rc.4",
|
||||
"parking_lot",
|
||||
"path",
|
||||
"paths",
|
||||
"proto",
|
||||
"rope",
|
||||
|
|
@ -9755,13 +9757,13 @@ dependencies = [
|
|||
"log",
|
||||
"lsp-types",
|
||||
"parking_lot",
|
||||
"path",
|
||||
"regex",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml 0.8.23",
|
||||
"tree-sitter",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -12706,6 +12708,16 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||
|
||||
[[package]]
|
||||
name = "path"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"serde",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
|
|
@ -14041,6 +14053,7 @@ dependencies = [
|
|||
"markdown",
|
||||
"node_runtime",
|
||||
"parking_lot",
|
||||
"path",
|
||||
"paths",
|
||||
"percent-encoding",
|
||||
"postage",
|
||||
|
|
@ -19955,6 +19968,7 @@ dependencies = [
|
|||
"log",
|
||||
"mach2 0.5.0",
|
||||
"nix 0.29.0",
|
||||
"path",
|
||||
"percent-encoding",
|
||||
"pretty_assertions",
|
||||
"rand 0.9.4",
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ members = [
|
|||
"crates/outline",
|
||||
"crates/outline_panel",
|
||||
"crates/panel",
|
||||
"crates/path",
|
||||
"crates/paths",
|
||||
"crates/picker",
|
||||
"crates/picker_preview",
|
||||
|
|
@ -414,6 +415,7 @@ outline = { path = "crates/outline" }
|
|||
outline_panel = { path = "crates/outline_panel" }
|
||||
panel = { path = "crates/panel" }
|
||||
paths = { path = "crates/paths" }
|
||||
path = { path = "crates/path" }
|
||||
perf = { path = "tooling/perf" }
|
||||
picker = { path = "crates/picker" }
|
||||
picker_preview = { path = "crates/picker_preview" }
|
||||
|
|
|
|||
|
|
@ -1095,8 +1095,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_posix_paths_are_not_rewritten_as_windows_drives() {
|
||||
let parsed =
|
||||
MentionUri::parse_hyperlink("/c/Projects/AGENTS.md", PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse_hyperlink("/c/Projects/AGENTS.md", PathStyle::Unix).unwrap();
|
||||
match parsed {
|
||||
MentionUri::File { abs_path } => {
|
||||
assert_eq!(abs_path, PathBuf::from("/c/Projects/AGENTS.md"));
|
||||
|
|
@ -1107,7 +1106,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_hyperlink_percent_escapes_are_decoded() {
|
||||
let parsed = MentionUri::parse_hyperlink("/tmp/a%20b.rs", PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse_hyperlink("/tmp/a%20b.rs", PathStyle::Unix).unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
MentionUri::File {
|
||||
|
|
@ -1126,15 +1125,14 @@ mod tests {
|
|||
);
|
||||
|
||||
// Separator escapes stay encoded (no introduced path traversal).
|
||||
let parsed = MentionUri::parse_hyperlink("/tmp/a%2Fb.rs", PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse_hyperlink("/tmp/a%2Fb.rs", PathStyle::Unix).unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
MentionUri::File {
|
||||
abs_path: PathBuf::from("/tmp/a%2Fb.rs")
|
||||
}
|
||||
);
|
||||
let parsed =
|
||||
MentionUri::parse_hyperlink("/tmp/..%2F..%2Fsecret", PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse_hyperlink("/tmp/..%2F..%2Fsecret", PathStyle::Unix).unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
MentionUri::File {
|
||||
|
|
@ -1145,7 +1143,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_parse_keeps_bare_path_targets_verbatim() {
|
||||
let parsed = MentionUri::parse("/tmp/a%20b.rs", PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse("/tmp/a%20b.rs", PathStyle::Unix).unwrap();
|
||||
assert_eq!(
|
||||
parsed,
|
||||
MentionUri::File {
|
||||
|
|
@ -1165,7 +1163,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_hyperlink_literal_keeps_percent_escapes() {
|
||||
let literal =
|
||||
MentionUri::parse_hyperlink_literal("/tmp/a%20b.rs", PathStyle::Posix).unwrap();
|
||||
MentionUri::parse_hyperlink_literal("/tmp/a%20b.rs", PathStyle::Unix).unwrap();
|
||||
assert_eq!(
|
||||
literal,
|
||||
MentionUri::File {
|
||||
|
|
@ -1175,7 +1173,7 @@ mod tests {
|
|||
|
||||
// Line suffixes still parse.
|
||||
let literal =
|
||||
MentionUri::parse_hyperlink_literal("/tmp/a%20b.rs:42", PathStyle::Posix).unwrap();
|
||||
MentionUri::parse_hyperlink_literal("/tmp/a%20b.rs:42", PathStyle::Unix).unwrap();
|
||||
assert_eq!(
|
||||
literal,
|
||||
MentionUri::Selection {
|
||||
|
|
@ -1200,27 +1198,27 @@ mod tests {
|
|||
fn test_parse_hyperlink_literal_returns_none_when_unambiguous() {
|
||||
// No percent escapes: identical to `parse_hyperlink`.
|
||||
assert_eq!(
|
||||
MentionUri::parse_hyperlink_literal("/tmp/a b.rs", PathStyle::Posix),
|
||||
MentionUri::parse_hyperlink_literal("/tmp/a b.rs", PathStyle::Unix),
|
||||
None
|
||||
);
|
||||
// Invalid escape sequences are also left alone by `parse_hyperlink`.
|
||||
assert_eq!(
|
||||
MentionUri::parse_hyperlink_literal("/tmp/100%_done.txt", PathStyle::Posix),
|
||||
MentionUri::parse_hyperlink_literal("/tmp/100%_done.txt", PathStyle::Unix),
|
||||
None
|
||||
);
|
||||
// Separator escapes are never decoded, so they're not ambiguous.
|
||||
assert_eq!(
|
||||
MentionUri::parse_hyperlink_literal("/tmp/a%2Fb.rs", PathStyle::Posix),
|
||||
MentionUri::parse_hyperlink_literal("/tmp/a%2Fb.rs", PathStyle::Unix),
|
||||
None
|
||||
);
|
||||
// URLs are spec-encoded, not ambiguous.
|
||||
assert_eq!(
|
||||
MentionUri::parse_hyperlink_literal("file:///tmp/a%20b.rs", PathStyle::Posix),
|
||||
MentionUri::parse_hyperlink_literal("file:///tmp/a%20b.rs", PathStyle::Unix),
|
||||
None
|
||||
);
|
||||
// Relative paths are not bare-path mentions.
|
||||
assert_eq!(
|
||||
MentionUri::parse_hyperlink_literal("tmp/a%20b.rs", PathStyle::Posix),
|
||||
MentionUri::parse_hyperlink_literal("tmp/a%20b.rs", PathStyle::Unix),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
|
@ -1476,7 +1474,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_absolute_file_path_with_row() {
|
||||
let file_path = "/path/to/file.rs:42";
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Unix).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Selection {
|
||||
abs_path: path,
|
||||
|
|
@ -1494,7 +1492,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_absolute_file_path_with_row_and_column() {
|
||||
let file_path = "/path/to/file.rs:42:5";
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Unix).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Selection {
|
||||
abs_path: path,
|
||||
|
|
@ -1506,7 +1504,7 @@ mod tests {
|
|||
assert_eq!(line_range.end(), &41);
|
||||
assert_eq!(column, &Some(4));
|
||||
|
||||
let parsed_again = MentionUri::parse(parsed.to_uri().as_ref(), PathStyle::Posix)
|
||||
let parsed_again = MentionUri::parse(parsed.to_uri().as_ref(), PathStyle::Unix)
|
||||
.expect("selection URI with column should parse");
|
||||
assert_eq!(parsed_again, parsed.clone());
|
||||
}
|
||||
|
|
@ -1517,7 +1515,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_absolute_file_path_with_fragment_line() {
|
||||
let file_path = "/path/to/file.rs#L42";
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Unix).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Selection {
|
||||
abs_path: path,
|
||||
|
|
@ -1589,7 +1587,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_backticked_absolute_file_path() {
|
||||
let file_path = "`/path/to/file.rs`";
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Unix).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::File { abs_path } => {
|
||||
assert_eq!(abs_path, Path::new("/path/to/file.rs"));
|
||||
|
|
@ -1601,7 +1599,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_parse_backticked_absolute_file_path_with_fragment_line() {
|
||||
let file_path = "`/path/to/file.rs#L42`";
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Posix).unwrap();
|
||||
let parsed = MentionUri::parse(file_path, PathStyle::Unix).unwrap();
|
||||
match &parsed {
|
||||
MentionUri::Selection {
|
||||
abs_path: path,
|
||||
|
|
|
|||
|
|
@ -446,18 +446,22 @@ impl gpui::EventEmitter<SkillLoadingIssuesUpdated> for NativeAgent {}
|
|||
static RULES_FILE_REL_PATHS: LazyLock<Vec<Arc<RelPath>>> = LazyLock::new(|| {
|
||||
RULES_FILE_NAMES
|
||||
.iter()
|
||||
.filter_map(|name| RelPath::unix(name).ok().map(|path| path.into_arc()))
|
||||
.filter_map(|name| {
|
||||
RelPath::from_unix_str(name)
|
||||
.ok()
|
||||
.map(|path| path.into_arc())
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
static AGENTS_PREFIX: LazyLock<Option<Arc<RelPath>>> = LazyLock::new(|| {
|
||||
RelPath::unix(AGENTS_DIR_NAME)
|
||||
RelPath::from_unix_str(AGENTS_DIR_NAME)
|
||||
.ok()
|
||||
.map(|path| path.into_arc())
|
||||
});
|
||||
|
||||
static SKILLS_PREFIX: LazyLock<Option<Arc<RelPath>>> = LazyLock::new(|| {
|
||||
RelPath::unix(project_skills_relative_path())
|
||||
RelPath::from_unix_str(project_skills_relative_path())
|
||||
.ok()
|
||||
.map(|path| path.into_arc())
|
||||
});
|
||||
|
|
@ -492,7 +496,7 @@ async fn expand_project_skills_directories(
|
|||
worktree: &Entity<Worktree>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
let agents_dir = RelPath::unix(AGENTS_DIR_NAME)?;
|
||||
let agents_dir = RelPath::from_unix_str(AGENTS_DIR_NAME)?;
|
||||
let Some(skills_prefix) = SKILLS_PREFIX.as_ref() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
|
@ -518,7 +522,7 @@ fn project_skill_files_from_worktree(worktree: &Worktree) -> Vec<ProjectSkillFil
|
|||
let Some(skills_prefix) = SKILLS_PREFIX.as_ref() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Ok(skill_file_name) = RelPath::unix(SKILL_FILE_NAME) else {
|
||||
let Ok(skill_file_name) = RelPath::from_unix_str(SKILL_FILE_NAME) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
|
|
@ -538,7 +542,7 @@ fn project_skill_files_from_worktree(worktree: &Worktree) -> Vec<ProjectSkillFil
|
|||
|
||||
skill_files.push(ProjectSkillFile {
|
||||
display_path: worktree.absolutize(&relative_path),
|
||||
relative_path,
|
||||
relative_path: relative_path.into(),
|
||||
size: skill_file.size,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ mod tests {
|
|||
root_name: "my-project".to_string(),
|
||||
abs_path: std::path::Path::new("/tmp/my-project").into(),
|
||||
rules_file: Some(RulesFileContext {
|
||||
path_in_worktree: RelPath::unix("AGENTS.md").unwrap().into(),
|
||||
path_in_worktree: RelPath::from_unix_str("AGENTS.md").unwrap().into(),
|
||||
text: "project-specific guidance".to_string(),
|
||||
project_entry_id: 1,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1245,11 +1245,11 @@ fn resolve_path(
|
|||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.and_then(|file_name| RelPath::unix(file_name).ok())
|
||||
.and_then(|file_name| RelPath::from_unix_str(file_name).ok())
|
||||
.ok_or_else(|| "Can't create file: invalid filename".to_string())?;
|
||||
|
||||
let new_file_path = parent_project_path.map(|parent| ProjectPath {
|
||||
path: parent.path.join(file_name),
|
||||
path: parent.path.join(file_name).into(),
|
||||
..parent
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ fn project_agents_md_path(
|
|||
require_existing_file: bool,
|
||||
cx: &App,
|
||||
) -> Option<PathBuf> {
|
||||
let rel_path = util::rel_path::RelPath::unix("AGENTS.md").ok()?;
|
||||
let rel_path = util::rel_path::RelPath::from_unix_str("AGENTS.md").ok()?;
|
||||
project
|
||||
.read(cx)
|
||||
.visible_worktrees(cx)
|
||||
|
|
|
|||
|
|
@ -1248,8 +1248,7 @@ fn full_mention_for_directory(
|
|||
|(worktree_path, full_path): (Arc<RelPath>, String)| {
|
||||
let rel_path = worktree_path
|
||||
.strip_prefix(&directory_path)
|
||||
.log_err()
|
||||
.map_or_else(|| worktree_path.clone(), |rel_path| rel_path.into());
|
||||
.map_or_else(|_| worktree_path.clone(), |rel_path| rel_path.into());
|
||||
|
||||
let open_task = project.update(cx, |project, cx| {
|
||||
project.buffer_store().update(cx, |buffer_store, cx| {
|
||||
|
|
|
|||
|
|
@ -1077,7 +1077,7 @@ mod tests {
|
|||
.enumerate()
|
||||
.filter_map(|(i, path)| {
|
||||
Some((
|
||||
Arc::from(RelPath::unix(path).ok()?),
|
||||
Arc::from(RelPath::from_unix_str(path).ok()?),
|
||||
ProjectEntryId::from_proto(i as u64 + 1),
|
||||
PathChange::Added,
|
||||
))
|
||||
|
|
|
|||
|
|
@ -961,7 +961,7 @@ impl Database {
|
|||
let path_style = if project.windows_paths {
|
||||
PathStyle::Windows
|
||||
} else {
|
||||
PathStyle::Posix
|
||||
PathStyle::Unix
|
||||
};
|
||||
let features: Vec<String> = serde_json::from_str(&project.features).unwrap_or_default();
|
||||
|
||||
|
|
|
|||
|
|
@ -448,7 +448,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
|||
.choose(rng)
|
||||
.unwrap();
|
||||
if entry.path.as_ref().is_empty() {
|
||||
worktree.root_name().into()
|
||||
worktree.root_name().to_rel_path_buf()
|
||||
} else {
|
||||
worktree.root_name().join(&entry.path)
|
||||
}
|
||||
|
|
@ -1524,7 +1524,7 @@ fn buffer_for_full_path(
|
|||
else {
|
||||
return false;
|
||||
};
|
||||
worktree.read(cx).root_name().join(&file.path()).as_ref() == full_path
|
||||
worktree.read(cx).root_name().join(&file.path()) == *full_path
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
|
|
|
|||
|
|
@ -1090,14 +1090,14 @@ impl DebugPanel {
|
|||
directory_in_worktree: dir,
|
||||
..
|
||||
} => {
|
||||
let relative_path = if dir.ends_with(RelPath::unix(".vscode").unwrap()) {
|
||||
dir.join(RelPath::unix("launch.json").unwrap())
|
||||
let relative_path = if dir.ends_with(RelPath::from_unix_str(".vscode").unwrap()) {
|
||||
dir.join(RelPath::from_unix_str("launch.json").unwrap())
|
||||
} else {
|
||||
dir.join(RelPath::unix("debug.json").unwrap())
|
||||
dir.join(RelPath::from_unix_str("debug.json").unwrap())
|
||||
};
|
||||
ProjectPath {
|
||||
worktree_id: id,
|
||||
path: relative_path,
|
||||
path: relative_path.into(),
|
||||
}
|
||||
}
|
||||
_ => return self.save_scenario(scenario, worktree_id, window, cx),
|
||||
|
|
|
|||
|
|
@ -1069,10 +1069,10 @@ impl DebugDelegate {
|
|||
|
||||
match path.components().next_back() {
|
||||
Some(".zed") => {
|
||||
path.push(RelPath::unix("debug.json").unwrap());
|
||||
path.push(RelPath::from_unix_str("debug.json").unwrap());
|
||||
}
|
||||
Some(".vscode") => {
|
||||
path.push(RelPath::unix("launch.json").unwrap());
|
||||
path.push(RelPath::from_unix_str("launch.json").unwrap());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -1165,7 +1165,7 @@ impl DebugDelegate {
|
|||
id: _,
|
||||
directory_in_worktree: dir,
|
||||
id_base: _,
|
||||
} => dir.ends_with(RelPath::unix(".zed").unwrap()),
|
||||
} => dir.ends_with(RelPath::from_unix_str(".zed").unwrap()),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
|
|
@ -1186,7 +1186,8 @@ impl DebugDelegate {
|
|||
id_base: _,
|
||||
} => {
|
||||
!(hide_vscode
|
||||
&& dir.ends_with(RelPath::unix(".vscode").unwrap()))
|
||||
&& dir
|
||||
.ends_with(RelPath::from_unix_str(".vscode").unwrap()))
|
||||
}
|
||||
_ => true,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -524,7 +524,7 @@ 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::Posix) || is_absolute(path, PathStyle::Windows)
|
||||
is_absolute(path, PathStyle::Unix) || is_absolute(path, PathStyle::Windows)
|
||||
})
|
||||
.map(|path| Arc::<Path>::from(Path::new(path)))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -172,13 +172,13 @@ pub fn find_devcontainer_configs(workspace: &Workspace, cx: &gpui::App) -> Vec<D
|
|||
pub fn find_configs_in_snapshot(snapshot: &Snapshot) -> Vec<DevContainerConfig> {
|
||||
let mut configs = Vec::new();
|
||||
|
||||
let devcontainer_dir_path = RelPath::unix(".devcontainer").expect("valid path");
|
||||
let devcontainer_dir_path = RelPath::from_unix_str(".devcontainer").expect("valid path");
|
||||
|
||||
if let Some(devcontainer_entry) = snapshot.entry_for_path(devcontainer_dir_path) {
|
||||
if devcontainer_entry.is_dir() {
|
||||
log::debug!("find_configs_in_snapshot: Scanning .devcontainer directory");
|
||||
let devcontainer_json_path =
|
||||
RelPath::unix(".devcontainer/devcontainer.json").expect("valid path");
|
||||
RelPath::from_unix_str(".devcontainer/devcontainer.json").expect("valid path");
|
||||
for entry in snapshot.child_entries(devcontainer_dir_path) {
|
||||
log::debug!(
|
||||
"find_configs_in_snapshot: Found entry: {:?}, is_file: {}, is_dir: {}",
|
||||
|
|
@ -199,7 +199,7 @@ pub fn find_configs_in_snapshot(snapshot: &Snapshot) -> Vec<DevContainerConfig>
|
|||
|
||||
let config_json_path =
|
||||
format!("{}/devcontainer.json", entry.path.as_unix_str());
|
||||
if let Ok(rel_config_path) = RelPath::unix(&config_json_path) {
|
||||
if let Ok(rel_config_path) = RelPath::from_unix_str(&config_json_path) {
|
||||
if snapshot.entry_for_path(rel_config_path).is_some() {
|
||||
log::debug!(
|
||||
"find_configs_in_snapshot: Found config in subfolder: {}",
|
||||
|
|
@ -223,7 +223,7 @@ pub fn find_configs_in_snapshot(snapshot: &Snapshot) -> Vec<DevContainerConfig>
|
|||
|
||||
// Always include `.devcontainer.json` so the user can pick it from the UI
|
||||
// even when `.devcontainer/devcontainer.json` also exists.
|
||||
let root_config_path = RelPath::unix(".devcontainer.json").expect("valid path");
|
||||
let root_config_path = RelPath::from_unix_str(".devcontainer.json").expect("valid path");
|
||||
if snapshot
|
||||
.entry_for_path(root_config_path)
|
||||
.is_some_and(|entry| entry.is_file())
|
||||
|
|
@ -400,7 +400,7 @@ pub(crate) async fn apply_devcontainer_template(
|
|||
log::error!("Can't create relative path: {e}");
|
||||
DevContainerError::FilesystemError
|
||||
})?;
|
||||
let rel_path = RelPath::unix(relative_path)
|
||||
let rel_path = RelPath::from_unix_str(relative_path)
|
||||
.map_err(|e| {
|
||||
log::error!("Can't create relative path: {e}");
|
||||
DevContainerError::FilesystemError
|
||||
|
|
|
|||
|
|
@ -1579,11 +1579,12 @@ fn dispatch_apply_templates(
|
|||
};
|
||||
|
||||
if files.project_files.contains(&Arc::from(
|
||||
RelPath::unix(".devcontainer/devcontainer.json").unwrap(),
|
||||
RelPath::from_unix_str(".devcontainer/devcontainer.json").unwrap(),
|
||||
)) {
|
||||
let Some(workspace_task) = workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
let Ok(path) = RelPath::unix(".devcontainer/devcontainer.json") else {
|
||||
let Ok(path) = RelPath::from_unix_str(".devcontainer/devcontainer.json")
|
||||
else {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"Couldn't create path for .devcontainer/devcontainer.json"
|
||||
)));
|
||||
|
|
|
|||
|
|
@ -309,12 +309,12 @@ pub async fn refresh_worktree_entries(
|
|||
) -> Result<()> {
|
||||
let mut rel_paths = Vec::new();
|
||||
for path in paths {
|
||||
if let Ok(rel_path) = RelPath::new(path, PathStyle::Posix) {
|
||||
if let Ok(rel_path) = RelPath::new(path, PathStyle::Unix) {
|
||||
rel_paths.push(rel_path.into_arc());
|
||||
}
|
||||
|
||||
let path_without_root: PathBuf = path.components().skip(1).collect();
|
||||
if let Ok(rel_path) = RelPath::new(&path_without_root, PathStyle::Posix) {
|
||||
if let Ok(rel_path) = RelPath::new(&path_without_root, PathStyle::Unix) {
|
||||
rel_paths.push(rel_path.into_arc());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ impl RelatedExcerptStore {
|
|||
.path
|
||||
.strip_prefix(worktree.root_name().as_unix_str())
|
||||
.ok()?;
|
||||
let relative_path = RelPath::new(relative_path, PathStyle::Posix).ok()?;
|
||||
let relative_path = RelPath::new(relative_path, PathStyle::Unix).ok()?;
|
||||
let project_path = ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: relative_path.into_owned().into(),
|
||||
|
|
|
|||
|
|
@ -652,7 +652,7 @@ async fn collect_git_log_context(
|
|||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let Ok(related_path) = RelPath::new(&related_path, PathStyle::Posix) else {
|
||||
let Ok(related_path) = RelPath::new(&related_path, PathStyle::Unix) else {
|
||||
continue;
|
||||
};
|
||||
let project_path = ProjectPath {
|
||||
|
|
|
|||
|
|
@ -740,9 +740,9 @@ fn unused_image_path(
|
|||
let mut filename = format!("image.{extension}");
|
||||
let mut counter = 1u32;
|
||||
loop {
|
||||
let candidate = dir_rel_path.join(RelPath::unix(&filename).ok()?);
|
||||
let candidate = dir_rel_path.join(RelPath::from_unix_str(&filename).ok()?);
|
||||
if !exists(&candidate) {
|
||||
return Some((filename, candidate));
|
||||
return Some((filename, candidate.into()));
|
||||
}
|
||||
filename = format!("image_{counter}.{extension}");
|
||||
counter += 1;
|
||||
|
|
|
|||
|
|
@ -38855,7 +38855,7 @@ fn test_hunk_key(file_path: &str) -> DiffHunkKey {
|
|||
file_path: if file_path.is_empty() {
|
||||
Arc::from(util::rel_path::RelPath::empty())
|
||||
} else {
|
||||
Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
|
||||
Arc::from(util::rel_path::RelPath::from_unix_str(file_path).unwrap())
|
||||
},
|
||||
hunk_start_anchor: Anchor::Min,
|
||||
}
|
||||
|
|
@ -38867,7 +38867,7 @@ fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
|
|||
file_path: if file_path.is_empty() {
|
||||
Arc::from(util::rel_path::RelPath::empty())
|
||||
} else {
|
||||
Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
|
||||
Arc::from(util::rel_path::RelPath::from_unix_str(file_path).unwrap())
|
||||
},
|
||||
hunk_start_anchor: anchor,
|
||||
}
|
||||
|
|
@ -39306,11 +39306,11 @@ fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
|
|||
// Create two different hunk keys (simulating two different files)
|
||||
let anchor = snapshot.anchor_before(Point::new(0, 0));
|
||||
let key1 = DiffHunkKey {
|
||||
file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
|
||||
file_path: Arc::from(util::rel_path::RelPath::from_unix_str("file1.rs").unwrap()),
|
||||
hunk_start_anchor: anchor,
|
||||
};
|
||||
let key2 = DiffHunkKey {
|
||||
file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
|
||||
file_path: Arc::from(util::rel_path::RelPath::from_unix_str("file2.rs").unwrap()),
|
||||
hunk_start_anchor: anchor,
|
||||
};
|
||||
|
||||
|
|
@ -39378,11 +39378,11 @@ fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
|
|||
|
||||
// Create two keys with the same file path and anchor
|
||||
let key1 = DiffHunkKey {
|
||||
file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
|
||||
file_path: Arc::from(util::rel_path::RelPath::from_unix_str("file.rs").unwrap()),
|
||||
hunk_start_anchor: anchor,
|
||||
};
|
||||
let key2 = DiffHunkKey {
|
||||
file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
|
||||
file_path: Arc::from(util::rel_path::RelPath::from_unix_str("file.rs").unwrap()),
|
||||
hunk_start_anchor: anchor,
|
||||
};
|
||||
|
||||
|
|
@ -39399,7 +39399,7 @@ fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
|
|||
|
||||
// Create a key with different file path
|
||||
let different_file_key = DiffHunkKey {
|
||||
file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
|
||||
file_path: Arc::from(util::rel_path::RelPath::from_unix_str("other.rs").unwrap()),
|
||||
hunk_start_anchor: anchor,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2221,14 +2221,14 @@ fn restore_serialized_buffer_contents(
|
|||
fn serialize_path_key(path_key: &PathKey) -> proto::PathKey {
|
||||
proto::PathKey {
|
||||
sort_prefix: path_key.sort_prefix,
|
||||
path: path_key.path.to_proto(),
|
||||
path: path_key.path.as_unix_str().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_path_key(path_key: proto::PathKey) -> Option<PathKey> {
|
||||
Some(PathKey {
|
||||
sort_prefix: path_key.sort_prefix,
|
||||
path: RelPath::from_proto(&path_key.path).ok()?,
|
||||
path: RelPath::from_unix_str(&path_key.path).ok()?.into(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ language.workspace = true
|
|||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
parking_lot.workspace = true
|
||||
path.workspace = true
|
||||
proto.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ pub trait Extension: Send + Sync + 'static {
|
|||
|
||||
/// Returns a path relative to this extension's working directory.
|
||||
fn path_from_extension(&self, path: &Path) -> PathBuf {
|
||||
util::normalize_path(&self.work_dir().join(path))
|
||||
path::normalize_path(&self.work_dir().join(path))
|
||||
}
|
||||
|
||||
async fn language_server_command(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use futures::{
|
|||
use heck::ToSnakeCase;
|
||||
use http_client::{self, AsyncBody, HttpClient};
|
||||
use language::LanguageConfig;
|
||||
use path::PathExt;
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
|
|
@ -20,7 +21,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{ResultExt, command::Stdio, rel_path::PathExt};
|
||||
use util::{ResultExt, command::Stdio};
|
||||
use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _};
|
||||
use wasmparser::Parser;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -11,7 +12,8 @@ use language::LanguageName;
|
|||
use lsp::LanguageServerName;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::rel_path::{PathExt, RelPathBuf};
|
||||
use util::paths::PathStyle;
|
||||
use util::rel_path::{RelPath, RelPathBuf};
|
||||
|
||||
use crate::ExtensionCapability;
|
||||
|
||||
|
|
@ -211,9 +213,12 @@ pub fn build_debug_adapter_schema_path(
|
|||
) -> anyhow::Result<RelPathBuf> {
|
||||
match &meta.schema_path {
|
||||
Some(path) => Ok(path.clone()),
|
||||
None => Path::new("debug_adapter_schemas")
|
||||
.join(Path::new(adapter_name.as_ref()).with_extension("json"))
|
||||
.to_rel_path_buf(),
|
||||
None => RelPath::new(
|
||||
&Path::new("debug_adapter_schemas")
|
||||
.join(Path::new(adapter_name.as_ref()).with_extension("json")),
|
||||
PathStyle::local(),
|
||||
)
|
||||
.map(Cow::into_owned),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ use std::{
|
|||
};
|
||||
use task::TaskTemplates;
|
||||
use url::Url;
|
||||
use util::{ResultExt, paths::RemotePathBuf, rel_path::PathExt};
|
||||
use util::{PathExt, ResultExt, paths::RemotePathBuf};
|
||||
use wasm_host::{
|
||||
WasmExtension, WasmHost,
|
||||
wit::{is_supported_wasm_api_version, wasm_api_version_range},
|
||||
|
|
|
|||
|
|
@ -432,7 +432,7 @@ impl ExtensionImports for WasmState {
|
|||
self.on_main_thread(|cx| {
|
||||
async move {
|
||||
let path = location.as_ref().and_then(|location| {
|
||||
RelPath::new(Path::new(&location.path), PathStyle::Posix).ok()
|
||||
RelPath::new(Path::new(&location.path), PathStyle::Unix).ok()
|
||||
});
|
||||
let location = path
|
||||
.as_ref()
|
||||
|
|
|
|||
|
|
@ -595,7 +595,7 @@ impl HostWorktree for WasmState {
|
|||
) -> wasmtime::Result<Result<String, String>> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate
|
||||
.read_text_file(&RelPath::new(Path::new(&path), PathStyle::Posix)?)
|
||||
.read_text_file(&RelPath::new(Path::new(&path), PathStyle::Unix)?)
|
||||
.await
|
||||
.map_err(|error| error.to_string()))
|
||||
}
|
||||
|
|
@ -948,7 +948,7 @@ impl ExtensionImports for WasmState {
|
|||
self.on_main_thread(|cx| {
|
||||
async move {
|
||||
let path = location.as_ref().and_then(|location| {
|
||||
RelPath::new(Path::new(&location.path), PathStyle::Posix).ok()
|
||||
RelPath::new(Path::new(&location.path), PathStyle::Unix).ok()
|
||||
});
|
||||
let location = path
|
||||
.as_ref()
|
||||
|
|
|
|||
|
|
@ -1272,7 +1272,11 @@ impl FileFinderDelegate {
|
|||
let full_path = if should_hide_root_in_entry_path(&worktree_store, cx) {
|
||||
entry_path.project.path.clone()
|
||||
} else {
|
||||
worktree.read(cx).root_name().join(&entry_path.project.path)
|
||||
worktree
|
||||
.read(cx)
|
||||
.root_name()
|
||||
.join(&entry_path.project.path)
|
||||
.into()
|
||||
};
|
||||
let mut components = full_path.components();
|
||||
let filename = components.next_back().unwrap_or("");
|
||||
|
|
@ -1841,7 +1845,7 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
.all(|worktree| {
|
||||
worktree
|
||||
.read(cx)
|
||||
.entry_for_path(RelPath::unix(prefix.split_at(1).0).unwrap())
|
||||
.entry_for_path(RelPath::from_unix_str(prefix.split_at(1).0).unwrap())
|
||||
.is_none_or(|entry| !entry.is_dir())
|
||||
})
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4584,7 +4584,7 @@ fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries
|
|||
if let Some(path_match) = path_match.as_ref() {
|
||||
search_entries
|
||||
.history
|
||||
.push(path_match.0.path_prefix.join(&path_match.0.path));
|
||||
.push(path_match.0.path_prefix.join(&path_match.0.path).into());
|
||||
} else {
|
||||
// This occurs when the query is empty and we show history matches
|
||||
// that are outside the project.
|
||||
|
|
@ -4597,7 +4597,7 @@ fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries
|
|||
Match::Search(path_match) => {
|
||||
search_entries
|
||||
.search
|
||||
.push(path_match.0.path_prefix.join(&path_match.0.path));
|
||||
.push(path_match.0.path_prefix.join(&path_match.0.path).into());
|
||||
search_entries.search_matches.push(path_match.0.clone());
|
||||
}
|
||||
Match::CreateNew(_) => {}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ tempfile.workspace = true
|
|||
text.workspace = true
|
||||
time.workspace = true
|
||||
util.workspace = true
|
||||
path.workspace = true
|
||||
is_executable = "1.0.5"
|
||||
notify = "9.0.0-rc.4"
|
||||
trash = { git = "https://github.com/zed-industries/trash-rs", rev = "47761739192828a66b11a94ba5420b82d63c03c5" }
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ use git::{
|
|||
status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus},
|
||||
};
|
||||
#[cfg(feature = "test-support")]
|
||||
use util::normalize_path;
|
||||
use path::normalize_path;
|
||||
|
||||
#[cfg(feature = "test-support")]
|
||||
use smol::io::AsyncReadExt;
|
||||
|
|
|
|||
|
|
@ -233,7 +233,11 @@ fn generate_nucleo_path_candidates(
|
|||
paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
fuzzy_nucleo::PathMatchCandidate::new(RelPath::unix(path).unwrap(), false, None)
|
||||
fuzzy_nucleo::PathMatchCandidate::new(
|
||||
RelPath::from_unix_str(path).unwrap(),
|
||||
false,
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -245,7 +249,7 @@ fn generate_fuzzy_path_candidates(
|
|||
.iter()
|
||||
.map(|path| fuzzy::PathMatchCandidate {
|
||||
is_dir: false,
|
||||
path: RelPath::unix(path).unwrap(),
|
||||
path: RelPath::from_unix_str(path).unwrap(),
|
||||
char_bag: CharBag::from(path.as_str()),
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -302,7 +306,7 @@ fn bench_path_matching(criterion: &mut Criterion) {
|
|||
query,
|
||||
case,
|
||||
size,
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
)
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
|
|
@ -325,7 +329,7 @@ fn bench_path_matching(criterion: &mut Criterion) {
|
|||
query,
|
||||
false,
|
||||
size,
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
)
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
|
|
|
|||
|
|
@ -1517,7 +1517,7 @@ impl GitRepository for RealGitRepository {
|
|||
let change = change?;
|
||||
let path = change.path;
|
||||
// git-show outputs `/`-delimited paths even on Windows.
|
||||
let Some(rel_path) = RelPath::unix(path).log_err() else {
|
||||
let Some(rel_path) = RelPath::from_unix_str(path).log_err() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
@ -3803,7 +3803,7 @@ impl std::fmt::Debug for RepoPath {
|
|||
|
||||
impl RepoPath {
|
||||
pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> Result<Self> {
|
||||
let rel_path = RelPath::unix(s.as_ref())?;
|
||||
let rel_path = RelPath::from_unix_str(s.as_ref())?;
|
||||
Ok(Self::from_rel_path(rel_path))
|
||||
}
|
||||
|
||||
|
|
@ -3813,7 +3813,7 @@ impl RepoPath {
|
|||
}
|
||||
|
||||
pub fn from_proto(proto: &str) -> Result<Self> {
|
||||
let rel_path = RelPath::from_proto(proto)?;
|
||||
let rel_path = RelPath::from_unix_str(proto)?.into();
|
||||
Ok(Self(rel_path))
|
||||
}
|
||||
|
||||
|
|
@ -3832,7 +3832,7 @@ impl RepoPath {
|
|||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn repo_path<S: AsRef<str> + ?Sized>(s: &S) -> RepoPath {
|
||||
RepoPath(RelPath::unix(s.as_ref()).unwrap().into())
|
||||
RepoPath(RelPath::from_unix_str(s.as_ref()).unwrap().into())
|
||||
}
|
||||
|
||||
impl AsRef<Arc<RelPath>> for RepoPath {
|
||||
|
|
|
|||
|
|
@ -454,7 +454,7 @@ impl FromStr for GitStatus {
|
|||
let status = entry.as_bytes()[0..2].try_into().unwrap();
|
||||
let status = FileStatus::from_bytes(status).log_err()?;
|
||||
// git-status outputs `/`-delimited repo paths, even on Windows.
|
||||
let path = RepoPath::from_rel_path(RelPath::unix(path).log_err()?);
|
||||
let path = RepoPath::from_rel_path(RelPath::from_unix_str(path).log_err()?);
|
||||
Some((path, status))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -544,7 +544,7 @@ impl FromStr for TreeDiff {
|
|||
let mut fields = s.split('\0');
|
||||
let mut parsed = HashMap::default();
|
||||
while let Some((status, path)) = fields.next().zip(fields.next()) {
|
||||
let path = RepoPath::from_rel_path(RelPath::unix(path)?);
|
||||
let path = RepoPath::from_rel_path(RelPath::from_unix_str(path)?);
|
||||
|
||||
let mut fields = status.split(" ").skip(2);
|
||||
let old_sha = fields
|
||||
|
|
|
|||
|
|
@ -1007,7 +1007,7 @@ fn name_sort_path(repo_path: &RelPath) -> Arc<RelPath> {
|
|||
return repo_path.into_arc();
|
||||
};
|
||||
let synthetic = format!("{}/{}", file_name, repo_path.as_unix_str());
|
||||
RelPath::unix(&synthetic)
|
||||
RelPath::from_unix_str(&synthetic)
|
||||
.map(|path| path.into_arc())
|
||||
.unwrap_or_else(|_| repo_path.into_arc())
|
||||
}
|
||||
|
|
@ -1032,7 +1032,7 @@ fn tree_sort_path(repo_path: &RelPath) -> Arc<RelPath> {
|
|||
}
|
||||
synthetic.push_str(component);
|
||||
}
|
||||
RelPath::unix(&synthetic)
|
||||
RelPath::from_unix_str(&synthetic)
|
||||
.map(|path| path.into_arc())
|
||||
.unwrap_or_else(|_| repo_path.into_arc())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3232,7 +3232,7 @@ impl GitPanel {
|
|||
|
||||
let worktree_snapshot = worktree.read(cx).snapshot();
|
||||
for rules_name in RULES_FILE_NAMES {
|
||||
if let Ok(rel_path) = RelPath::unix(rules_name) {
|
||||
if let Ok(rel_path) = RelPath::from_unix_str(rules_name) {
|
||||
if let Some(entry) = worktree_snapshot.entry_for_path(rel_path) {
|
||||
if entry.is_file() {
|
||||
return Some(ProjectPath {
|
||||
|
|
@ -11485,7 +11485,7 @@ mod tests {
|
|||
cx.read(|cx| project.read(cx).worktrees(cx).next().unwrap().read(cx).id());
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: RelPath::unix("src/a/foo.rs").unwrap().into_arc(),
|
||||
path: RelPath::from_unix_str("src/a/foo.rs").unwrap().into_arc(),
|
||||
};
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ fn register_entry(
|
|||
RelPath::new(rel, PathStyle::local())
|
||||
.map(|r| r.into_owned().into())
|
||||
.unwrap_or_else(|_| {
|
||||
RelPath::new(Path::new(MultiBuffer::DEFAULT_TITLE), PathStyle::Posix)
|
||||
RelPath::new(Path::new(MultiBuffer::DEFAULT_TITLE), PathStyle::Unix)
|
||||
.unwrap()
|
||||
.into_owned()
|
||||
.into()
|
||||
|
|
@ -105,10 +105,10 @@ fn register_entry(
|
|||
.new_path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.and_then(|s| RelPath::new(Path::new(s), PathStyle::Posix).ok())
|
||||
.and_then(|s| RelPath::new(Path::new(s), PathStyle::Unix).ok())
|
||||
.map(|r| r.into_owned().into())
|
||||
.unwrap_or_else(|| {
|
||||
RelPath::new(Path::new(MultiBuffer::DEFAULT_TITLE), PathStyle::Posix)
|
||||
RelPath::new(Path::new(MultiBuffer::DEFAULT_TITLE), PathStyle::Unix)
|
||||
.unwrap()
|
||||
.into_owned()
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -586,7 +586,7 @@ impl Item for ImageView {
|
|||
}
|
||||
|
||||
fn breadcrumbs_text_for_image(project: &Project, image: &ImageItem, cx: &App) -> String {
|
||||
let mut path = image.file.path().clone();
|
||||
let mut path = image.file.path().to_rel_path_buf();
|
||||
if project.visible_worktrees(cx).count() > 1
|
||||
&& let Some(worktree) = project.worktree_for_id(image.project_path(cx).worktree_id, cx)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ pub use language_core::{
|
|||
LanguageId, LanguageMatcher, OrderedListConfig, OutlineConfig, Override, OverrideConfig,
|
||||
OverrideEntry, PromptResponseContext, RedactionConfig, RunnableCapture, RunnableConfig,
|
||||
SoftWrap, Symbol, TaskListConfig, TextObject, TextObjectConfig, ToLspPosition,
|
||||
WrapCharactersConfig, auto_indent_using_last_non_empty_line_default, deserialize_regex,
|
||||
deserialize_regex_vec, regex_json_schema, regex_vec_json_schema, serialize_regex,
|
||||
WrapCharactersConfig, default_true, deserialize_regex, deserialize_regex_vec,
|
||||
regex_json_schema, regex_vec_json_schema, serialize_regex,
|
||||
};
|
||||
pub use language_registry::{
|
||||
LanguageName, LanguageServerStatusUpdate, LoadedLanguage, ServerHealth,
|
||||
|
|
|
|||
|
|
@ -11,17 +11,17 @@ path = "src/language_core.rs"
|
|||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
gpui_shared_string.workspace = true
|
||||
gpui_util.workspace = true
|
||||
log.workspace = true
|
||||
lsp-types.workspace = true
|
||||
parking_lot.workspace = true
|
||||
path.workspace = true
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
toml.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
util.workspace = true
|
||||
gpui_util.workspace = true
|
||||
lsp-types.workspace = true
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ use regex::Regex;
|
|||
use schemars::{JsonSchema, SchemaGenerator, json_schema};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
||||
use std::{num::NonZeroU32, path::Path, sync::Arc};
|
||||
use util::serde::default_true;
|
||||
|
||||
/// Controls the soft-wrapping behavior in the editor.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
|
|
@ -46,7 +45,7 @@ pub struct LanguageConfig {
|
|||
pub brackets: BracketPairConfig,
|
||||
/// If set to true, auto indentation uses last non empty line to determine
|
||||
/// the indentation level for a new line.
|
||||
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
|
||||
#[serde(default = "default_true")]
|
||||
pub auto_indent_using_last_non_empty_line: bool,
|
||||
// Whether indentation of pasted content should be adjusted based on the context.
|
||||
#[serde(default)]
|
||||
|
|
@ -166,7 +165,7 @@ impl Default for LanguageConfig {
|
|||
grammar: None,
|
||||
matcher: LanguageMatcher::default(),
|
||||
brackets: Default::default(),
|
||||
auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
|
||||
auto_indent_using_last_non_empty_line: default_true(),
|
||||
auto_indent_on_paste: None,
|
||||
increase_indent_pattern: Default::default(),
|
||||
decrease_indent_pattern: Default::default(),
|
||||
|
|
@ -480,7 +479,7 @@ pub struct WrapCharactersConfig {
|
|||
pub end_suffix: String,
|
||||
}
|
||||
|
||||
pub fn auto_indent_using_last_non_empty_line_default() -> bool {
|
||||
pub fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ pub use highlight_map::{HighlightId, HighlightMap};
|
|||
pub use language_config::{
|
||||
BlockCommentConfig, BracketPair, BracketPairConfig, BracketPairContent, DecreaseIndentConfig,
|
||||
JsxTagAutoCloseConfig, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
|
||||
OrderedListConfig, Override, SoftWrap, TaskListConfig, WrapCharactersConfig,
|
||||
auto_indent_using_last_non_empty_line_default, deserialize_regex, deserialize_regex_vec,
|
||||
regex_json_schema, regex_vec_json_schema, serialize_regex,
|
||||
OrderedListConfig, Override, SoftWrap, TaskListConfig, WrapCharactersConfig, default_true,
|
||||
deserialize_regex, deserialize_regex_vec, regex_json_schema, regex_vec_json_schema,
|
||||
serialize_regex,
|
||||
};
|
||||
|
||||
pub mod code_label;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use gpui_shared_string::SharedString;
|
||||
use util::rel_path::RelPath;
|
||||
use path::rel_path::RelPath;
|
||||
|
||||
use crate::{LanguageName, ManifestName};
|
||||
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ impl LspAdapter for EsLintLspAdapter {
|
|||
let file_path = requested_file_path
|
||||
.as_ref()
|
||||
.and_then(|abs_path| abs_path.strip_prefix(worktree_root).ok())
|
||||
.and_then(|p| RelPath::unix(&p).ok().map(ToOwned::to_owned))
|
||||
.and_then(|p| RelPath::from_unix_str(&p).ok().map(ToOwned::to_owned))
|
||||
.unwrap_or_else(|| RelPath::empty().to_owned());
|
||||
let override_options = cx.update(|cx| {
|
||||
language_server_settings_for(
|
||||
|
|
|
|||
|
|
@ -52,8 +52,12 @@ impl ContextProvider for JsonTaskProvider {
|
|||
let Some(file) = project::File::from_dyn(file).cloned() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let is_package_json = file.path.ends_with(RelPath::unix("package.json").unwrap());
|
||||
let is_composer_json = file.path.ends_with(RelPath::unix("composer.json").unwrap());
|
||||
let is_package_json = file
|
||||
.path
|
||||
.ends_with(RelPath::from_unix_str("package.json").unwrap());
|
||||
let is_composer_json = file
|
||||
.path
|
||||
.ends_with(RelPath::from_unix_str("composer.json").unwrap());
|
||||
if !is_package_json && !is_composer_json {
|
||||
return Task::ready(None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,14 +90,14 @@ impl ManifestProvider for PyprojectTomlManifestProvider {
|
|||
let mut outermost_workspace_root = None;
|
||||
|
||||
for path in path.ancestors().take(depth) {
|
||||
let pyproject_path = path.join(RelPath::unix("pyproject.toml").unwrap());
|
||||
let pyproject_path = path.join(RelPath::from_unix_str("pyproject.toml").unwrap());
|
||||
if delegate.exists(&pyproject_path, Some(false)) {
|
||||
if innermost_pyproject.is_none() {
|
||||
innermost_pyproject = Some(Arc::from(path));
|
||||
}
|
||||
|
||||
let has_lockfile = WORKSPACE_LOCKFILES.iter().any(|lockfile| {
|
||||
let lockfile_path = path.join(RelPath::unix(lockfile).unwrap());
|
||||
let lockfile_path = path.join(RelPath::from_unix_str(lockfile).unwrap());
|
||||
delegate.exists(&lockfile_path, Some(false))
|
||||
});
|
||||
if has_lockfile {
|
||||
|
|
@ -1114,7 +1114,7 @@ impl PythonContextProvider {
|
|||
|
||||
fn python_module_name_from_relative_path(relative_path: &str) -> Option<String> {
|
||||
let rel_path = RelPath::new(relative_path.as_ref(), PathStyle::local()).ok()?;
|
||||
let path_with_dots = rel_path.display(PathStyle::Posix).replace('/', ".");
|
||||
let path_with_dots = rel_path.display(PathStyle::Unix).replace('/', ".");
|
||||
Some(
|
||||
path_with_dots
|
||||
.strip_suffix(".py")
|
||||
|
|
@ -3265,7 +3265,7 @@ mod tests {
|
|||
});
|
||||
let provider = PyprojectTomlManifestProvider;
|
||||
provider.search(ManifestQuery {
|
||||
path: RelPath::unix(query_path).unwrap().into(),
|
||||
path: RelPath::from_unix_str(query_path).unwrap().into(),
|
||||
depth: 10,
|
||||
delegate,
|
||||
})
|
||||
|
|
@ -3274,7 +3274,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_simple_project_no_lockfile() {
|
||||
let result = search(&["project/pyproject.toml"], "project/src/main.py");
|
||||
assert_eq!(result.as_deref(), RelPath::unix("project").ok());
|
||||
assert_eq!(result.as_deref(), RelPath::from_unix_str("project").ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -3287,7 +3287,7 @@ mod tests {
|
|||
],
|
||||
"packages/subproject/src/main.py",
|
||||
);
|
||||
assert_eq!(result.as_deref(), RelPath::unix("").ok());
|
||||
assert_eq!(result.as_deref(), RelPath::from_unix_str("").ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -3296,7 +3296,7 @@ mod tests {
|
|||
&["pyproject.toml", "poetry.lock", "libs/mylib/pyproject.toml"],
|
||||
"libs/mylib/src/main.py",
|
||||
);
|
||||
assert_eq!(result.as_deref(), RelPath::unix("").ok());
|
||||
assert_eq!(result.as_deref(), RelPath::from_unix_str("").ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -3309,7 +3309,7 @@ mod tests {
|
|||
],
|
||||
"packages/mypackage/src/main.py",
|
||||
);
|
||||
assert_eq!(result.as_deref(), RelPath::unix("").ok());
|
||||
assert_eq!(result.as_deref(), RelPath::from_unix_str("").ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -3318,13 +3318,19 @@ mod tests {
|
|||
&["project-a/pyproject.toml", "project-b/pyproject.toml"],
|
||||
"project-a/src/main.py",
|
||||
);
|
||||
assert_eq!(result_a.as_deref(), RelPath::unix("project-a").ok());
|
||||
assert_eq!(
|
||||
result_a.as_deref(),
|
||||
RelPath::from_unix_str("project-a").ok()
|
||||
);
|
||||
|
||||
let result_b = search(
|
||||
&["project-a/pyproject.toml", "project-b/pyproject.toml"],
|
||||
"project-b/src/main.py",
|
||||
);
|
||||
assert_eq!(result_b.as_deref(), RelPath::unix("project-b").ok());
|
||||
assert_eq!(
|
||||
result_b.as_deref(),
|
||||
RelPath::from_unix_str("project-b").ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -3345,7 +3351,7 @@ mod tests {
|
|||
],
|
||||
"packages/sub/src/main.py",
|
||||
);
|
||||
assert_eq!(result.as_deref(), RelPath::unix("").ok());
|
||||
assert_eq!(result.as_deref(), RelPath::from_unix_str("").ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -3360,11 +3366,16 @@ mod tests {
|
|||
// "deep/nested/src/main.py", "deep/nested/src", and "deep/nested"
|
||||
// It won't reach "deep" or root ""
|
||||
let result = provider.search(ManifestQuery {
|
||||
path: RelPath::unix("deep/nested/src/main.py").unwrap().into(),
|
||||
path: RelPath::from_unix_str("deep/nested/src/main.py")
|
||||
.unwrap()
|
||||
.into(),
|
||||
depth: 3,
|
||||
delegate,
|
||||
});
|
||||
assert_eq!(result.as_deref(), RelPath::unix("deep/nested").ok());
|
||||
assert_eq!(
|
||||
result.as_deref(),
|
||||
RelPath::from_unix_str("deep/nested").ok()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ impl ManifestProvider for CargoManifestProvider {
|
|||
) -> Option<Arc<RelPath>> {
|
||||
let mut outermost_cargo_toml = None;
|
||||
for path in path.ancestors().take(depth) {
|
||||
let p = path.join(RelPath::unix("Cargo.toml").unwrap());
|
||||
let p = path.join(RelPath::from_unix_str("Cargo.toml").unwrap());
|
||||
if delegate.exists(&p, Some(false)) {
|
||||
outermost_cargo_toml = Some(Arc::from(path));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -625,7 +625,9 @@ impl TypeScriptLspAdapter {
|
|||
|
||||
async fn tsdk_path(&self, adapter: &Arc<dyn LspAdapterDelegate>) -> Option<&'static str> {
|
||||
let is_yarn = adapter
|
||||
.read_text_file(RelPath::unix(".yarn/sdks/typescript/lib/typescript.js").unwrap())
|
||||
.read_text_file(
|
||||
RelPath::from_unix_str(".yarn/sdks/typescript/lib/typescript.js").unwrap(),
|
||||
)
|
||||
.await
|
||||
.is_ok();
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ impl PathKey {
|
|||
} else {
|
||||
Self {
|
||||
sort_prefix: None,
|
||||
path: RelPath::unix(&buffer.entity_id().to_string())
|
||||
path: RelPath::from_unix_str(&buffer.entity_id().to_string())
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ impl OpenPathDelegate {
|
|||
cancel_flag: Arc::new(AtomicBool::new(false)),
|
||||
should_dismiss: true,
|
||||
prompt_root: match path_style {
|
||||
PathStyle::Posix => "/".to_string(),
|
||||
PathStyle::Unix => "/".to_string(),
|
||||
PathStyle::Windows => "C:\\".to_string(),
|
||||
},
|
||||
path_style,
|
||||
|
|
@ -158,7 +158,7 @@ impl OpenPathDelegate {
|
|||
|
||||
fn current_dir(&self) -> &'static str {
|
||||
match self.path_style {
|
||||
PathStyle::Posix => "./",
|
||||
PathStyle::Unix => "./",
|
||||
PathStyle::Windows => ".\\",
|
||||
}
|
||||
}
|
||||
|
|
@ -929,7 +929,7 @@ fn path_candidates(
|
|||
|
||||
fn get_dir_and_suffix(query: String, path_style: PathStyle) -> (String, String) {
|
||||
match path_style {
|
||||
PathStyle::Posix => {
|
||||
PathStyle::Unix => {
|
||||
let (mut dir, suffix) = if let Some(index) = query.rfind('/') {
|
||||
(query[..index].to_string(), query[index + 1..].to_string())
|
||||
} else {
|
||||
|
|
@ -1028,39 +1028,39 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_get_dir_and_suffix_with_posix_style() {
|
||||
let (dir, suffix) = get_dir_and_suffix("".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/");
|
||||
assert_eq!(suffix, "");
|
||||
|
||||
let (dir, suffix) = get_dir_and_suffix("/".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("/".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/");
|
||||
assert_eq!(suffix, "");
|
||||
|
||||
let (dir, suffix) = get_dir_and_suffix("/Use".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("/Use".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/");
|
||||
assert_eq!(suffix, "Use");
|
||||
|
||||
let (dir, suffix) = get_dir_and_suffix("/Users/Junkui/Docum".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("/Users/Junkui/Docum".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/Users/Junkui/");
|
||||
assert_eq!(suffix, "Docum");
|
||||
|
||||
let (dir, suffix) = get_dir_and_suffix("/Users/Junkui/Documents".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("/Users/Junkui/Documents".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/Users/Junkui/");
|
||||
assert_eq!(suffix, "Documents");
|
||||
|
||||
let (dir, suffix) = get_dir_and_suffix("/Users/Junkui/Documents/".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("/Users/Junkui/Documents/".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/Users/Junkui/Documents/");
|
||||
assert_eq!(suffix, "");
|
||||
|
||||
let (dir, suffix) = get_dir_and_suffix("/root/.".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("/root/.".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/root/");
|
||||
assert_eq!(suffix, ".");
|
||||
|
||||
let (dir, suffix) = get_dir_and_suffix("/root/..".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("/root/..".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/root/");
|
||||
assert_eq!(suffix, "..");
|
||||
|
||||
let (dir, suffix) = get_dir_and_suffix("/root/.hidden".into(), PathStyle::Posix);
|
||||
let (dir, suffix) = get_dir_and_suffix("/root/.hidden".into(), PathStyle::Unix);
|
||||
assert_eq!(dir, "/root/");
|
||||
assert_eq!(suffix, ".hidden");
|
||||
}
|
||||
|
|
|
|||
23
crates/path/Cargo.toml
Normal file
23
crates/path/Cargo.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "path"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lib]
|
||||
path = "src/path.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
dunce.workspace = true
|
||||
serde = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
1
crates/path/LICENSE-APACHE
Symbolic link
1
crates/path/LICENSE-APACHE
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../LICENSE-APACHE
|
||||
286
crates/path/src/abs_path.rs
Normal file
286
crates/path/src/abs_path.rs
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
use std::{
|
||||
borrow::{Borrow, Cow},
|
||||
fmt, io,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::{PathStyle, rel_path::RelPath};
|
||||
|
||||
// An absolute path on the user's local filesystem.
|
||||
// Requires paths to be valid utf-8
|
||||
#[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct AbsPath(Path);
|
||||
|
||||
impl AbsPath {
|
||||
pub fn new(path: &Path) -> anyhow::Result<&Self> {
|
||||
if !path.is_absolute() {
|
||||
return Err(anyhow::anyhow!("Path is not absolute: {:?}", path));
|
||||
}
|
||||
if path.to_str().is_none() {
|
||||
return Err(anyhow::anyhow!("Path is not valid utf-8: {:?}", path));
|
||||
}
|
||||
Ok(Self::new_unchecked(path))
|
||||
}
|
||||
|
||||
fn new_unchecked(path: &Path) -> &Self {
|
||||
// SAFETY: `AbsPath` is a `repr(transparent)` wrapper around `Path`.
|
||||
unsafe { &*(path as *const Path as *const Self) }
|
||||
}
|
||||
|
||||
pub fn to_abs_path_buf(&self) -> AbsPathBuf {
|
||||
AbsPathBuf(self.0.to_owned())
|
||||
}
|
||||
|
||||
pub fn join(&self, name: impl AsRef<str>) -> AbsPathBuf {
|
||||
AbsPathBuf(self.0.join(name.as_ref()))
|
||||
}
|
||||
|
||||
pub fn join_rel_path(&self, relative_path: &RelPath) -> AbsPathBuf {
|
||||
AbsPathBuf(self.0.join(relative_path.as_std_path()))
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<&AbsPath> {
|
||||
let parent = self.0.parent()?;
|
||||
Some(AbsPath::new_unchecked(parent))
|
||||
}
|
||||
|
||||
pub fn starts_with(&self, other: &AbsPath) -> bool {
|
||||
self.0.starts_with(&other.0)
|
||||
}
|
||||
|
||||
pub fn ends_with(&self, other: &RelPath) -> bool {
|
||||
self.0.ends_with(other.as_std_path())
|
||||
}
|
||||
|
||||
pub fn is_descendant_of(&self, ancestor: &Self) -> bool {
|
||||
if self == ancestor {
|
||||
return false;
|
||||
}
|
||||
self.starts_with(ancestor)
|
||||
}
|
||||
|
||||
pub fn file_name(&self) -> Option<&str> {
|
||||
self.0.file_name()?.to_str()
|
||||
}
|
||||
|
||||
pub fn display(&self) -> impl fmt::Display + '_ {
|
||||
self.0.display()
|
||||
}
|
||||
|
||||
pub fn as_std_path(&self) -> &Path {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0
|
||||
.to_str()
|
||||
.expect("valid UTF-8 enforced in constructor")
|
||||
}
|
||||
|
||||
pub fn ancestors(&self) -> impl Iterator<Item = &AbsPath> {
|
||||
self.0.ancestors().map(|p| AbsPath::new_unchecked(p))
|
||||
}
|
||||
|
||||
pub fn strip_prefix<'a>(&'a self, prefix: &AbsPath) -> Option<Cow<'a, RelPath>> {
|
||||
let prefix = self.0.strip_prefix(&prefix.0).ok()?;
|
||||
RelPath::new(prefix, PathStyle::local()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOwned for AbsPath {
|
||||
type Owned = AbsPathBuf;
|
||||
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
self.to_abs_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for AbsPath {
|
||||
fn as_ref(&self) -> &Path {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<AbsPath> for AbsPath {
|
||||
fn as_ref(&self) -> &AbsPath {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AbsPath> for Arc<AbsPath> {
|
||||
fn from(path: &AbsPath) -> Self {
|
||||
let arc: Arc<Path> = Arc::from(&path.0);
|
||||
// SAFETY: `AbsPath` is a `repr(transparent)` wrapper around `Path`.
|
||||
unsafe { Arc::from_raw(Arc::into_raw(arc) as *const AbsPath) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AbsPath> for Rc<AbsPath> {
|
||||
fn from(path: &AbsPath) -> Self {
|
||||
let arc: Rc<Path> = Rc::from(&path.0);
|
||||
// SAFETY: `AbsPath` is a `repr(transparent)` wrapper around `Path`.
|
||||
unsafe { Rc::from_raw(Rc::into_raw(arc) as *const AbsPath) }
|
||||
}
|
||||
}
|
||||
|
||||
// An absolute path on the user's local filesystem.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct AbsPathBuf(PathBuf);
|
||||
|
||||
impl AbsPathBuf {
|
||||
pub fn new(path: impl Into<PathBuf>) -> anyhow::Result<Self> {
|
||||
let path = path.into();
|
||||
if let Err(e) = AbsPath::new(&path) {
|
||||
return Err(e);
|
||||
}
|
||||
Ok(Self(path))
|
||||
}
|
||||
|
||||
pub fn home_dir() -> anyhow::Result<Self> {
|
||||
let home = std::env::home_dir().context("no home dir available")?;
|
||||
Self::new(home)
|
||||
}
|
||||
|
||||
/// Resolves `path` to its canonical on-disk spelling: symlinks and `..`
|
||||
/// are resolved, relative input is anchored on the current directory, and
|
||||
/// on case-insensitive filesystems each component takes the casing stored
|
||||
/// on disk. Unlike [`std::fs::canonicalize`], the result never uses
|
||||
/// Windows extended-length (`\\?\`) syntax, which chokes tools the path
|
||||
/// is later handed to (e.g. `git`).
|
||||
///
|
||||
/// Paths act as identity in several places (lock keys, watch-target
|
||||
/// comparisons, persisted repository records), so canonicalize a path
|
||||
/// where it enters the system whenever it may have been spelled by a user
|
||||
/// or an external tool.
|
||||
pub fn canonicalize(path: impl AsRef<Path>) -> io::Result<Self> {
|
||||
let canonical = dunce::canonicalize(path.as_ref())?;
|
||||
Self::new(canonical).map_err(io::Error::other)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, name: &str) {
|
||||
self.0.push(name);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn new_test(path: &'static str) -> Self {
|
||||
if cfg!(windows) {
|
||||
Self::new(format!("C:{path}")).unwrap()
|
||||
} else {
|
||||
Self::new(path).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn abs_path(path: &str) -> AbsPathBuf {
|
||||
if cfg!(windows) {
|
||||
AbsPathBuf::new(format!("C:{path}")).unwrap()
|
||||
} else {
|
||||
AbsPathBuf::new(path).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AbsPathBuf {
|
||||
type Target = AbsPath;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
AbsPath::new_unchecked(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<AbsPath> for AbsPathBuf {
|
||||
fn borrow(&self) -> &AbsPath {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for AbsPathBuf {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<AbsPath> for AbsPathBuf {
|
||||
fn as_ref(&self) -> &AbsPath {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsPathBuf> for PathBuf {
|
||||
fn from(path: AbsPathBuf) -> PathBuf {
|
||||
path.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AbsPathBuf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.as_str().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<AbsPath> for AbsPathBuf {
|
||||
fn eq(&self, other: &AbsPath) -> bool {
|
||||
**self == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<AbsPathBuf> for AbsPath {
|
||||
fn eq(&self, other: &AbsPathBuf) -> bool {
|
||||
*self == **other
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_canonicalize_restores_on_disk_spelling() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let root = dunce::canonicalize(temp.path()).unwrap();
|
||||
let dir = root.join("CamelCase");
|
||||
std::fs::create_dir(&dir).unwrap();
|
||||
|
||||
let canonical = AbsPathBuf::canonicalize(&dir).unwrap();
|
||||
assert_eq!(canonical.as_std_path(), dir);
|
||||
assert!(
|
||||
!canonical.as_str().starts_with(r"\\?\"),
|
||||
"canonical paths must not use Windows extended-length syntax: {canonical}"
|
||||
);
|
||||
|
||||
// A differently-cased spelling addresses the same directory only on a
|
||||
// case-insensitive filesystem; when it does, canonicalization must
|
||||
// restore the stored casing.
|
||||
let lowercased = root.join("camelcase");
|
||||
if std::fs::metadata(&lowercased).is_ok() {
|
||||
assert_eq!(
|
||||
AbsPathBuf::canonicalize(&lowercased).unwrap().as_std_path(),
|
||||
dir,
|
||||
"canonicalization should restore the on-disk casing"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_test_normalizes_rooted_paths() {
|
||||
if cfg!(windows) {
|
||||
assert_eq!(AbsPathBuf::new_test("/").as_str(), "C:/");
|
||||
assert_eq!(
|
||||
AbsPathBuf::new_test("/test/project").as_str(),
|
||||
"C:/test/project"
|
||||
);
|
||||
} else {
|
||||
assert_eq!(AbsPathBuf::new_test("/").as_str(), "/");
|
||||
assert_eq!(
|
||||
AbsPathBuf::new_test("/test/project").as_str(),
|
||||
"/test/project"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
263
crates/path/src/path.rs
Normal file
263
crates/path/src/path.rs
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
//! Relative path types for deltadb.
|
||||
//!
|
||||
//! Provides [`RelPath`] and [`RelPathBuf`] — path types that are guaranteed to be
|
||||
//! relative, normalized, and valid unicode. Internally stored in POSIX (`/`-delimited)
|
||||
//! format regardless of host platform.
|
||||
//!
|
||||
//! Adapted from Zed's `util::rel_path` module.
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::rel_path::RelPath;
|
||||
|
||||
pub mod abs_path;
|
||||
pub mod rel_path;
|
||||
|
||||
pub trait PathExt {
|
||||
fn to_rel_path_buf(&self) -> anyhow::Result<rel_path::RelPathBuf>;
|
||||
}
|
||||
|
||||
impl<T: AsRef<Path> + ?Sized> PathExt for T {
|
||||
fn to_rel_path_buf(&self) -> anyhow::Result<rel_path::RelPathBuf> {
|
||||
Ok(RelPath::new(self.as_ref(), PathStyle::local())?.into_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum PathStyle {
|
||||
Unix,
|
||||
Windows,
|
||||
}
|
||||
|
||||
impl PathStyle {
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const fn local() -> Self {
|
||||
PathStyle::Windows
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub const fn local() -> Self {
|
||||
PathStyle::Unix
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_separator(&self) -> &'static str {
|
||||
match self {
|
||||
PathStyle::Unix => "/",
|
||||
PathStyle::Windows => "\\",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separators(&self) -> &'static [&'static str] {
|
||||
match self {
|
||||
PathStyle::Unix => &["/"],
|
||||
PathStyle::Windows => &["\\", "/"],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separators_ch(&self) -> &'static [char] {
|
||||
match self {
|
||||
PathStyle::Unix => &['/'],
|
||||
PathStyle::Windows => &['\\', '/'],
|
||||
}
|
||||
}
|
||||
|
||||
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_windows(&self) -> bool {
|
||||
*self == PathStyle::Windows
|
||||
}
|
||||
|
||||
pub fn is_posix(&self) -> bool {
|
||||
*self == PathStyle::Unix
|
||||
}
|
||||
|
||||
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) {
|
||||
return None;
|
||||
}
|
||||
let left = left.as_ref().to_str()?;
|
||||
if left.is_empty() {
|
||||
Some(right.into())
|
||||
} else {
|
||||
Some(format!(
|
||||
"{left}{}{right}",
|
||||
if left.ends_with(self.primary_separator()) {
|
||||
""
|
||||
} else {
|
||||
self.primary_separator()
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join_path(
|
||||
self,
|
||||
left: impl AsRef<Path>,
|
||||
right: impl AsRef<Path>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let left = left
|
||||
.as_ref()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8"))?;
|
||||
let right = right.as_ref();
|
||||
let right_string = right
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8"))?;
|
||||
let joined = self
|
||||
.join(left, right_string)
|
||||
.ok_or_else(|| anyhow::anyhow!("Path must be relative: {right:?}"))?;
|
||||
Ok(PathBuf::from(self.normalize(&joined)))
|
||||
}
|
||||
|
||||
pub fn normalize(self, path_like: &str) -> String {
|
||||
match self {
|
||||
PathStyle::Windows => crate::normalize_path(Path::new(path_like))
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
PathStyle::Unix => {
|
||||
let is_absolute = path_like.starts_with('/');
|
||||
let remainder = if is_absolute {
|
||||
path_like.trim_start_matches('/')
|
||||
} else {
|
||||
path_like
|
||||
};
|
||||
|
||||
let mut components = Vec::new();
|
||||
for component in remainder.split(self.separators_ch()) {
|
||||
match component {
|
||||
"" | "." => {}
|
||||
".." => {
|
||||
if components
|
||||
.last()
|
||||
.is_some_and(|component| *component != "..")
|
||||
{
|
||||
components.pop();
|
||||
} else if !is_absolute {
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
component => components.push(component),
|
||||
}
|
||||
}
|
||||
|
||||
let normalized = components.join(self.primary_separator());
|
||||
if is_absolute && normalized.is_empty() {
|
||||
"/".to_string()
|
||||
} else if is_absolute {
|
||||
format!("/{normalized}")
|
||||
} else {
|
||||
normalized
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(self, path_like: &str) -> (Option<&str>, &str) {
|
||||
let Some(pos) = path_like.rfind(self.primary_separator()) else {
|
||||
return (None, path_like);
|
||||
};
|
||||
let filename_start = pos + self.primary_separator().len();
|
||||
(
|
||||
Some(&path_like[..filename_start]),
|
||||
&path_like[filename_start..],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn strip_prefix<'a>(
|
||||
&self,
|
||||
child: &'a Path,
|
||||
parent: &'a Path,
|
||||
) -> Option<std::borrow::Cow<'a, RelPath>> {
|
||||
let parent = parent.to_str()?;
|
||||
if parent.is_empty() {
|
||||
return RelPath::new(child, *self).ok();
|
||||
}
|
||||
let parent = self
|
||||
.separators()
|
||||
.iter()
|
||||
.find_map(|sep| parent.strip_suffix(sep))
|
||||
.unwrap_or(parent);
|
||||
let child = child.to_str()?;
|
||||
|
||||
// Match behavior of std::path::Path, which is case-insensitive for drive letters (e.g., "C:" == "c:")
|
||||
let stripped = if self.is_windows()
|
||||
&& child.as_bytes().get(1) == Some(&b':')
|
||||
&& parent.as_bytes().get(1) == Some(&b':')
|
||||
&& child.as_bytes()[0].eq_ignore_ascii_case(&parent.as_bytes()[0])
|
||||
{
|
||||
child[2..].strip_prefix(&parent[2..])?
|
||||
} else {
|
||||
child.strip_prefix(parent)?
|
||||
};
|
||||
if let Some(relative) = self
|
||||
.separators()
|
||||
.iter()
|
||||
.find_map(|sep| stripped.strip_prefix(sep))
|
||||
{
|
||||
RelPath::new(relative.as_ref(), *self).ok()
|
||||
} else if stripped.is_empty() {
|
||||
Some(Cow::Borrowed(RelPath::empty()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
use std::path::Component;
|
||||
let mut components = path.components().peekable();
|
||||
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
||||
components.next();
|
||||
PathBuf::from(c.as_os_str())
|
||||
} else {
|
||||
PathBuf::new()
|
||||
};
|
||||
|
||||
for component in components {
|
||||
match component {
|
||||
Component::Prefix(..) => unreachable!(),
|
||||
Component::RootDir => {
|
||||
ret.push(component.as_os_str());
|
||||
}
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
ret.pop();
|
||||
}
|
||||
Component::Normal(c) => {
|
||||
ret.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
use crate::paths::{PathStyle, is_absolute};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::{Borrow, Cow},
|
||||
fmt,
|
||||
|
|
@ -9,6 +7,12 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
PathStyle,
|
||||
abs_path::{AbsPath, AbsPathBuf},
|
||||
is_absolute,
|
||||
};
|
||||
|
||||
/// A file system path that is guaranteed to be relative and normalized.
|
||||
///
|
||||
/// This type can be used to represent paths in a uniform way, regardless of
|
||||
|
|
@ -20,20 +24,22 @@ use std::{
|
|||
///
|
||||
/// Relative paths are also guaranteed to be valid unicode.
|
||||
#[repr(transparent)]
|
||||
#[derive(PartialEq, Eq, Hash, Serialize)]
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct RelPath(str);
|
||||
|
||||
/// An owned representation of a file system path that is guaranteed to be
|
||||
/// relative and normalized.
|
||||
///
|
||||
/// This type is to [`RelPath`] as [`std::path::PathBuf`] is to [`std::path::Path`]
|
||||
#[derive(PartialEq, Eq, Clone, Ord, PartialOrd, Serialize)]
|
||||
#[derive(PartialEq, Eq, Clone, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct RelPathBuf(String);
|
||||
|
||||
impl RelPath {
|
||||
/// Creates an empty [`RelPath`].
|
||||
pub fn empty() -> &'static Self {
|
||||
Self::new_unchecked("")
|
||||
Self::from_str("")
|
||||
}
|
||||
|
||||
/// Creates an empty [`RelPath`].
|
||||
|
|
@ -54,7 +60,7 @@ impl RelPath {
|
|||
let mut path = path.to_str().context("non utf-8 path")?;
|
||||
|
||||
let (prefixes, suffixes): (&[_], &[_]) = match path_style {
|
||||
PathStyle::Posix => (&["./"], &['/']),
|
||||
PathStyle::Unix => (&["./"], &['/']),
|
||||
PathStyle::Windows => (&["./", ".\\"], &['/', '\\']),
|
||||
};
|
||||
|
||||
|
|
@ -77,7 +83,7 @@ impl RelPath {
|
|||
}
|
||||
|
||||
let mut result = match string {
|
||||
Cow::Borrowed(string) => Cow::Borrowed(Self::new_unchecked(string)),
|
||||
Cow::Borrowed(string) => Cow::Borrowed(Self::from_str(string)),
|
||||
Cow::Owned(string) => Cow::Owned(RelPathBuf(string)),
|
||||
};
|
||||
|
||||
|
|
@ -95,7 +101,7 @@ impl RelPath {
|
|||
return Err(anyhow!("path is not relative: {result:?}"));
|
||||
}
|
||||
}
|
||||
other => normalized.push(RelPath::new_unchecked(other)),
|
||||
other => normalized.push(RelPath::from_str(other)),
|
||||
}
|
||||
}
|
||||
result = Cow::Owned(normalized)
|
||||
|
|
@ -104,20 +110,25 @@ impl RelPath {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn new_test<'a>(path: &'a str) -> Cow<'a, Self> {
|
||||
Self::new(Path::new(path), PathStyle::Unix).unwrap()
|
||||
}
|
||||
|
||||
/// Converts a path that is already normalized and uses '/' separators
|
||||
/// into a [`RelPath`] .
|
||||
///
|
||||
/// Returns an error if the path is not already in the correct format.
|
||||
#[track_caller]
|
||||
pub fn unix<S: AsRef<Path> + ?Sized>(path: &S) -> anyhow::Result<&Self> {
|
||||
pub fn from_unix_str<S: AsRef<Path> + ?Sized>(path: &S) -> anyhow::Result<&Self> {
|
||||
let path = path.as_ref();
|
||||
match Self::new(path, PathStyle::Posix)? {
|
||||
match Self::new(path, PathStyle::Unix)? {
|
||||
Cow::Borrowed(path) => Ok(path),
|
||||
Cow::Owned(_) => Err(anyhow!("invalid relative path {path:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_unchecked(s: &str) -> &Self {
|
||||
fn from_str(s: &str) -> &Self {
|
||||
// Safety: `RelPath` is a transparent wrapper around `str`.
|
||||
unsafe { &*(s as *const str as *const Self) }
|
||||
}
|
||||
|
|
@ -131,7 +142,12 @@ impl RelPath {
|
|||
}
|
||||
|
||||
pub fn ancestors(&self) -> RelPathAncestors<'_> {
|
||||
RelPathAncestors(Some(&self.0))
|
||||
RelPathAncestors {
|
||||
full: &self.0,
|
||||
front: self.0.len(),
|
||||
back: 0,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_name(&self) -> Option<&str> {
|
||||
|
|
@ -156,7 +172,22 @@ impl RelPath {
|
|||
self.strip_prefix(other).is_ok()
|
||||
}
|
||||
|
||||
/// Returns true if this path is a strict descendant of `ancestor`.
|
||||
///
|
||||
/// Unlike `starts_with`, this returns false when `self == ancestor`
|
||||
/// and false when `ancestor` is empty (since every path trivially
|
||||
/// starts with the empty prefix).
|
||||
pub fn is_descendant_of(&self, ancestor: &Self) -> bool {
|
||||
if ancestor.is_empty() || self == ancestor {
|
||||
return false;
|
||||
}
|
||||
self.starts_with(ancestor)
|
||||
}
|
||||
|
||||
pub fn ends_with(&self, other: &Self) -> bool {
|
||||
if other.is_empty() {
|
||||
return true;
|
||||
}
|
||||
if let Some(suffix) = self.0.strip_suffix(&other.0) {
|
||||
if suffix.ends_with('/') {
|
||||
return true;
|
||||
|
|
@ -173,7 +204,7 @@ impl RelPath {
|
|||
}
|
||||
if let Some(suffix) = self.0.strip_prefix(&other.0) {
|
||||
if let Some(suffix) = suffix.strip_prefix('/') {
|
||||
return Ok(Self::new_unchecked(suffix));
|
||||
return Ok(Self::from_str(suffix));
|
||||
} else if suffix.is_empty() {
|
||||
return Ok(Self::empty());
|
||||
}
|
||||
|
|
@ -182,7 +213,11 @@ impl RelPath {
|
|||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.matches('/').count() + 1
|
||||
if self.0.is_empty() {
|
||||
0
|
||||
} else {
|
||||
self.0.matches('/').count() + 1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_n_components(&self, count: usize) -> Option<&Self> {
|
||||
|
|
@ -198,15 +233,14 @@ impl RelPath {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn join(&self, other: &Self) -> Arc<Self> {
|
||||
let result = if self.0.is_empty() {
|
||||
Cow::Borrowed(&other.0)
|
||||
pub fn join(&self, other: &Self) -> RelPathBuf {
|
||||
if self.0.is_empty() {
|
||||
other.to_rel_path_buf()
|
||||
} else if other.0.is_empty() {
|
||||
Cow::Borrowed(&self.0)
|
||||
self.to_rel_path_buf()
|
||||
} else {
|
||||
Cow::Owned(format!("{}/{}", &self.0, &other.0))
|
||||
};
|
||||
Arc::from(Self::new_unchecked(result.as_ref()))
|
||||
RelPathBuf(format!("{}/{}", &self.0, &other.0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_rel_path_buf(&self) -> RelPathBuf {
|
||||
|
|
@ -217,23 +251,13 @@ impl RelPath {
|
|||
Arc::from(self)
|
||||
}
|
||||
|
||||
/// Convert the path into the wire representation.
|
||||
pub fn to_proto(&self) -> String {
|
||||
self.as_unix_str().to_owned()
|
||||
}
|
||||
|
||||
/// Load the path from its wire representation.
|
||||
pub fn from_proto(path: &str) -> Result<Arc<Self>> {
|
||||
Ok(Arc::from(Self::unix(path)?))
|
||||
}
|
||||
|
||||
/// Convert the path into a string with the given path style.
|
||||
///
|
||||
/// Whenever a path is presented to the user, it should be converted to
|
||||
/// a string via this method.
|
||||
pub fn display(&self, style: PathStyle) -> Cow<'_, str> {
|
||||
match style {
|
||||
PathStyle::Posix => Cow::Borrowed(&self.0),
|
||||
PathStyle::Unix => Cow::Borrowed(&self.0),
|
||||
PathStyle::Windows if self.0.contains('/') => Cow::Owned(self.0.replace('/', "\\")),
|
||||
PathStyle::Windows => Cow::Borrowed(&self.0),
|
||||
}
|
||||
|
|
@ -255,19 +279,16 @@ impl RelPath {
|
|||
pub fn as_std_path(&self) -> &Path {
|
||||
Path::new(&self.0)
|
||||
}
|
||||
|
||||
/// Resolves this relative path against an absolute base path.
|
||||
pub fn absolutize(&self, base: impl AsRef<AbsPath>) -> AbsPathBuf {
|
||||
base.as_ref().join(self.as_unix_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StripPrefixError;
|
||||
|
||||
impl std::fmt::Display for StripPrefixError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("prefix not found")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for StripPrefixError {}
|
||||
|
||||
impl ToOwned for RelPath {
|
||||
type Owned = RelPathBuf;
|
||||
|
||||
|
|
@ -324,14 +345,33 @@ impl RelPathBuf {
|
|||
}
|
||||
|
||||
pub fn push(&mut self, path: &RelPath) {
|
||||
if path.is_empty() {
|
||||
return;
|
||||
}
|
||||
if !self.is_empty() {
|
||||
self.0.push('/');
|
||||
}
|
||||
self.0.push_str(&path.0);
|
||||
}
|
||||
|
||||
pub fn push_component(&mut self, component: &str) -> Result<()> {
|
||||
anyhow::ensure!(
|
||||
!component.is_empty()
|
||||
&& !component.contains('/')
|
||||
&& component != "."
|
||||
&& component != "..",
|
||||
"invalid relative path component: {component:?}"
|
||||
);
|
||||
|
||||
if !self.is_empty() {
|
||||
self.0.push('/');
|
||||
}
|
||||
self.0.push_str(component);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn as_rel_path(&self) -> &RelPath {
|
||||
RelPath::new_unchecked(self.0.as_str())
|
||||
RelPath::from_str(self.0.as_str())
|
||||
}
|
||||
|
||||
pub fn set_extension(&mut self, extension: &str) -> bool {
|
||||
|
|
@ -339,7 +379,7 @@ impl RelPathBuf {
|
|||
let mut filename = PathBuf::from(filename);
|
||||
filename.set_extension(extension);
|
||||
self.pop();
|
||||
self.0.push_str(filename.to_str().unwrap());
|
||||
self.push(RelPath::from_str(filename.to_str().unwrap()));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
@ -347,33 +387,21 @@ impl RelPathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RelPathBuf {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let path = String::deserialize(deserializer)?;
|
||||
let rel_path =
|
||||
RelPath::new(Path::new(&path), PathStyle::local()).map_err(serde::de::Error::custom)?;
|
||||
Ok(rel_path.into_owned())
|
||||
impl PartialOrd for RelPathBuf {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Arc<RelPath>> for RelPathBuf {
|
||||
fn into(self) -> Arc<RelPath> {
|
||||
Arc::from(self.as_rel_path())
|
||||
impl Ord for RelPathBuf {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.as_rel_path().cmp(other.as_rel_path())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for RelPathBuf {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_std_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for RelPath {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_std_path()
|
||||
impl From<RelPathBuf> for Arc<RelPath> {
|
||||
fn from(value: RelPathBuf) -> Self {
|
||||
Arc::from(value.as_rel_path())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -383,12 +411,24 @@ impl AsRef<RelPath> for RelPathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::path::Path> for RelPathBuf {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_std_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<RelPath> for RelPath {
|
||||
fn as_ref(&self) -> &RelPath {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<std::path::Path> for RelPath {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_std_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for RelPathBuf {
|
||||
type Target = RelPath;
|
||||
|
||||
|
|
@ -406,20 +446,37 @@ impl<'a> From<&'a RelPath> for Cow<'a, RelPath> {
|
|||
impl From<&RelPath> for Arc<RelPath> {
|
||||
fn from(rel_path: &RelPath) -> Self {
|
||||
let bytes: Arc<str> = Arc::from(&rel_path.0);
|
||||
// SAFETY: `AbsPath` is a `repr(transparent)` wrapper around `Path`.
|
||||
unsafe { Arc::from_raw(Arc::into_raw(bytes) as *const RelPath) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for &'a RelPath {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(s: &'a str) -> Result<Self> {
|
||||
RelPath::from_unix_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for RelPathBuf {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self> {
|
||||
RelPath::new(Path::new(s), PathStyle::Unix).map(|cow| cow.into_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[track_caller]
|
||||
pub fn rel_path(path: &str) -> &RelPath {
|
||||
RelPath::unix(path).unwrap()
|
||||
RelPath::from_unix_str(path).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[track_caller]
|
||||
pub fn rel_path_buf(path: &str) -> RelPathBuf {
|
||||
RelPath::unix(path).unwrap().to_rel_path_buf()
|
||||
rel_path(path).to_owned()
|
||||
}
|
||||
|
||||
impl PartialEq<str> for RelPath {
|
||||
|
|
@ -428,26 +485,45 @@ impl PartialEq<str> for RelPath {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait PathExt {
|
||||
fn to_rel_path_buf(&self) -> Result<RelPathBuf>;
|
||||
impl PartialEq<RelPath> for RelPathBuf {
|
||||
fn eq(&self, other: &RelPath) -> bool {
|
||||
self.as_rel_path() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<Path> + ?Sized> PathExt for T {
|
||||
fn to_rel_path_buf(&self) -> Result<RelPathBuf> {
|
||||
Ok(RelPath::new(self.as_ref(), PathStyle::local())?.into_owned())
|
||||
impl PartialEq<RelPathBuf> for RelPath {
|
||||
fn eq(&self, other: &RelPathBuf) -> bool {
|
||||
other.as_rel_path() == self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RelPath {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RelPathBuf {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RelPathComponents<'a>(&'a str);
|
||||
|
||||
pub struct RelPathAncestors<'a>(Option<&'a str>);
|
||||
pub struct RelPathAncestors<'a> {
|
||||
full: &'a str,
|
||||
front: usize,
|
||||
back: usize,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
const SEPARATOR: char = '/';
|
||||
|
||||
impl<'a> RelPathComponents<'a> {
|
||||
pub fn rest(&self) -> &'a RelPath {
|
||||
RelPath::new_unchecked(self.0)
|
||||
RelPath::from_str(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -473,15 +549,35 @@ impl<'a> Iterator for RelPathAncestors<'a> {
|
|||
type Item = &'a RelPath;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let result = self.0?;
|
||||
if let Some(sep_ix) = result.rfind(SEPARATOR) {
|
||||
self.0 = Some(&result[..sep_ix]);
|
||||
} else if !result.is_empty() {
|
||||
self.0 = Some("");
|
||||
} else {
|
||||
self.0 = None;
|
||||
if self.done {
|
||||
return None;
|
||||
}
|
||||
Some(RelPath::new_unchecked(result))
|
||||
let result = &self.full[..self.front];
|
||||
if self.front == self.back {
|
||||
self.done = true;
|
||||
} else {
|
||||
self.front = result.rfind(SEPARATOR).unwrap_or_default();
|
||||
}
|
||||
Some(RelPath::from_str(result))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for RelPathAncestors<'a> {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
if self.done {
|
||||
return None;
|
||||
}
|
||||
let result = &self.full[..self.back];
|
||||
if self.front == self.back {
|
||||
self.done = true;
|
||||
} else {
|
||||
let search_start = if self.back == 0 { 0 } else { self.back + 1 };
|
||||
self.back = match self.full[search_start..].find(SEPARATOR) {
|
||||
Some(sep_ix) => search_start + sep_ix,
|
||||
None => self.full.len(),
|
||||
};
|
||||
}
|
||||
Some(RelPath::from_str(result))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -501,11 +597,21 @@ impl<'a> DoubleEndedIterator for RelPathComponents<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for RelPathBuf {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let path = String::deserialize(deserializer)?;
|
||||
let rel_path =
|
||||
RelPath::new(Path::new(&path), PathStyle::local()).map_err(serde::de::Error::custom)?;
|
||||
Ok(rel_path.into_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::assert_matches;
|
||||
|
||||
#[test]
|
||||
fn test_rel_path_new() {
|
||||
|
|
@ -515,11 +621,11 @@ mod tests {
|
|||
|
||||
let path = RelPath::new("foo/".as_ref(), PathStyle::local()).unwrap();
|
||||
assert_eq!(path, rel_path("foo").into());
|
||||
assert_matches!(path, Cow::Borrowed(_));
|
||||
assert!(matches!(path, Cow::Borrowed(_)));
|
||||
|
||||
let path = RelPath::new("foo\\".as_ref(), PathStyle::Windows).unwrap();
|
||||
assert_eq!(path, rel_path("foo").into());
|
||||
assert_matches!(path, Cow::Borrowed(_));
|
||||
assert!(matches!(path, Cow::Borrowed(_)));
|
||||
|
||||
assert_eq!(
|
||||
RelPath::new("foo/bar/../baz/./quux/".as_ref(), PathStyle::local())
|
||||
|
|
@ -528,29 +634,29 @@ mod tests {
|
|||
rel_path("foo/baz/quux")
|
||||
);
|
||||
|
||||
let path = RelPath::new("./foo/bar".as_ref(), PathStyle::Posix).unwrap();
|
||||
let path = RelPath::new("./foo/bar".as_ref(), PathStyle::Unix).unwrap();
|
||||
assert_eq!(path.as_ref(), rel_path("foo/bar"));
|
||||
assert_matches!(path, Cow::Borrowed(_));
|
||||
assert!(matches!(path, Cow::Borrowed(_)));
|
||||
|
||||
let path = RelPath::new(".\\foo".as_ref(), PathStyle::Windows).unwrap();
|
||||
assert_eq!(path, rel_path("foo").into());
|
||||
assert_matches!(path, Cow::Borrowed(_));
|
||||
assert!(matches!(path, Cow::Borrowed(_)));
|
||||
|
||||
let path = RelPath::new("./.\\./foo/\\/".as_ref(), PathStyle::Windows).unwrap();
|
||||
assert_eq!(path, rel_path("foo").into());
|
||||
assert_matches!(path, Cow::Borrowed(_));
|
||||
assert!(matches!(path, Cow::Borrowed(_)));
|
||||
|
||||
let path = RelPath::new("foo/./bar".as_ref(), PathStyle::Posix).unwrap();
|
||||
let path = RelPath::new("foo/./bar".as_ref(), PathStyle::Unix).unwrap();
|
||||
assert_eq!(path.as_ref(), rel_path("foo/bar"));
|
||||
assert_matches!(path, Cow::Owned(_));
|
||||
assert!(matches!(path, Cow::Owned(_)));
|
||||
|
||||
let path = RelPath::new("./foo/bar".as_ref(), PathStyle::Windows).unwrap();
|
||||
assert_eq!(path.as_ref(), rel_path("foo/bar"));
|
||||
assert_matches!(path, Cow::Borrowed(_));
|
||||
assert!(matches!(path, Cow::Borrowed(_)));
|
||||
|
||||
let path = RelPath::new(".\\foo\\bar".as_ref(), PathStyle::Windows).unwrap();
|
||||
assert_eq!(path.as_ref(), rel_path("foo/bar"));
|
||||
assert_matches!(path, Cow::Owned(_));
|
||||
assert!(matches!(path, Cow::Owned(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -590,6 +696,41 @@ mod tests {
|
|||
let mut ancestors = path.ancestors();
|
||||
assert_eq!(ancestors.next(), Some(RelPath::empty()));
|
||||
assert_eq!(ancestors.next(), None);
|
||||
|
||||
let path = rel_path("foo/bar/baz");
|
||||
let mut ancestors = path.ancestors();
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("")));
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("foo")));
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("foo/bar")));
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("foo/bar/baz")));
|
||||
assert_eq!(ancestors.next_back(), None);
|
||||
|
||||
let path = rel_path("foo/bar/baz");
|
||||
let mut ancestors = path.ancestors();
|
||||
assert_eq!(ancestors.next(), Some(rel_path("foo/bar/baz")));
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("")));
|
||||
assert_eq!(ancestors.next(), Some(rel_path("foo/bar")));
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("foo")));
|
||||
assert_eq!(ancestors.next(), None);
|
||||
assert_eq!(ancestors.next_back(), None);
|
||||
|
||||
let path = rel_path("foo");
|
||||
let mut ancestors = path.ancestors();
|
||||
assert_eq!(ancestors.next_back(), Some(RelPath::empty()));
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("foo")));
|
||||
assert_eq!(ancestors.next_back(), None);
|
||||
|
||||
let path = RelPath::empty();
|
||||
let mut ancestors = path.ancestors();
|
||||
assert_eq!(ancestors.next_back(), Some(RelPath::empty()));
|
||||
assert_eq!(ancestors.next_back(), None);
|
||||
|
||||
let path = rel_path("über/x");
|
||||
let mut ancestors = path.ancestors();
|
||||
assert_eq!(ancestors.next_back(), Some(RelPath::empty()));
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("über")));
|
||||
assert_eq!(ancestors.next_back(), Some(rel_path("über/x")));
|
||||
assert_eq!(ancestors.next_back(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -602,13 +743,18 @@ mod tests {
|
|||
#[test]
|
||||
fn test_rel_path_partial_ord_is_compatible_with_std() {
|
||||
let test_cases = ["a/b/c", "relative/path/with/dot.", "relative/path/with.dot"];
|
||||
for [lhs, rhs] in test_cases.iter().array_combinations::<2>() {
|
||||
assert_eq!(
|
||||
Path::new(lhs).cmp(Path::new(rhs)),
|
||||
RelPath::unix(lhs)
|
||||
.unwrap()
|
||||
.cmp(&RelPath::unix(rhs).unwrap())
|
||||
);
|
||||
for (i, lhs) in test_cases.iter().enumerate() {
|
||||
for rhs in &test_cases[i + 1..] {
|
||||
assert_eq!(
|
||||
Path::new(lhs).cmp(Path::new(rhs)),
|
||||
RelPath::from_unix_str(lhs)
|
||||
.unwrap()
|
||||
.cmp(RelPath::from_unix_str(rhs).unwrap()),
|
||||
"ordering mismatch for {:?} vs {:?}",
|
||||
lhs,
|
||||
rhs,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -621,19 +767,28 @@ mod tests {
|
|||
assert_eq!(child.strip_prefix(parent).unwrap(), child);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ends_with() {
|
||||
assert!(rel_path("foo/bar").ends_with(rel_path("bar")));
|
||||
assert!(rel_path("foo/bar").ends_with(rel_path("foo/bar")));
|
||||
assert!(rel_path("foo/bar").ends_with(RelPath::empty()));
|
||||
assert!(RelPath::empty().ends_with(RelPath::empty()));
|
||||
assert!(!rel_path("foobar").ends_with(rel_path("bar")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rel_path_constructors_absolute_path() {
|
||||
assert!(RelPath::new(Path::new("/a/b"), PathStyle::Windows).is_err());
|
||||
assert!(RelPath::new(Path::new("\\a\\b"), PathStyle::Windows).is_err());
|
||||
assert!(RelPath::new(Path::new("/a/b"), PathStyle::Posix).is_err());
|
||||
assert!(RelPath::new(Path::new("/a/b"), PathStyle::Unix).is_err());
|
||||
assert!(RelPath::new(Path::new("C:/a/b"), PathStyle::Windows).is_err());
|
||||
assert!(RelPath::new(Path::new("C:\\a\\b"), PathStyle::Windows).is_err());
|
||||
assert!(RelPath::new(Path::new("C:/a/b"), PathStyle::Posix).is_ok());
|
||||
assert!(RelPath::new(Path::new("C:/a/b"), PathStyle::Unix).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pop() {
|
||||
let mut path = rel_path("a/b").to_rel_path_buf();
|
||||
let mut path = rel_path_buf("a/b");
|
||||
path.pop();
|
||||
assert_eq!(path.as_rel_path().as_unix_str(), "a");
|
||||
path.pop();
|
||||
|
|
@ -641,4 +796,27 @@ mod tests {
|
|||
path.pop();
|
||||
assert_eq!(path.as_rel_path().as_unix_str(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
assert_eq!(RelPath::empty().len(), 0);
|
||||
assert_eq!(rel_path("a").len(), 1);
|
||||
assert_eq!(rel_path("a/b").len(), 2);
|
||||
assert_eq!(rel_path("a/b/c").len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_extension() {
|
||||
let mut path = rel_path_buf("a/b/c.txt");
|
||||
assert!(path.set_extension("rs"));
|
||||
assert_eq!(path.as_rel_path().as_unix_str(), "a/b/c.rs");
|
||||
|
||||
let mut single = rel_path_buf("file.txt");
|
||||
assert!(single.set_extension("md"));
|
||||
assert_eq!(single.as_rel_path().as_unix_str(), "file.md");
|
||||
|
||||
let mut no_ext = rel_path_buf("a/b/c");
|
||||
assert!(no_ext.set_extension("rs"));
|
||||
assert_eq!(no_ext.as_rel_path().as_unix_str(), "a/b/c.rs");
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ static CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
|
|||
/// Returns the relative path to the zed_server directory on the ssh host.
|
||||
pub fn remote_server_dir_relative() -> &'static RelPath {
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed_server").unwrap());
|
||||
LazyLock::new(|| RelPath::from_unix_str(".zed_server").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ pub fn remote_server_dir_relative() -> &'static RelPath {
|
|||
/// Returns the relative path to the zed_wsl_server directory on the wsl host.
|
||||
pub fn remote_wsl_server_dir_relative() -> &'static RelPath {
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed_wsl_server").unwrap());
|
||||
LazyLock::new(|| RelPath::from_unix_str(".zed_wsl_server").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
|
|
@ -496,21 +496,21 @@ pub fn local_vscode_folder_name() -> &'static str {
|
|||
/// Returns the relative path to a `settings.json` file within a project.
|
||||
pub fn local_settings_file_relative_path() -> &'static RelPath {
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed/settings.json").unwrap());
|
||||
LazyLock::new(|| RelPath::from_unix_str(".zed/settings.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
/// Returns the relative path to a `tasks.json` file within a project.
|
||||
pub fn local_tasks_file_relative_path() -> &'static RelPath {
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed/tasks.json").unwrap());
|
||||
LazyLock::new(|| RelPath::from_unix_str(".zed/tasks.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
/// Returns the relative path to a `.vscode/tasks.json` file within a project.
|
||||
pub fn local_vscode_tasks_file_relative_path() -> &'static RelPath {
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".vscode/tasks.json").unwrap());
|
||||
LazyLock::new(|| RelPath::from_unix_str(".vscode/tasks.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
|
|
@ -526,14 +526,14 @@ pub fn task_file_name() -> &'static str {
|
|||
/// .zed/debug.json
|
||||
pub fn local_debug_file_relative_path() -> &'static RelPath {
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".zed/debug.json").unwrap());
|
||||
LazyLock::new(|| RelPath::from_unix_str(".zed/debug.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
/// Returns the relative path to a `.vscode/launch.json` file within a project.
|
||||
pub fn local_vscode_launch_file_relative_path() -> &'static RelPath {
|
||||
static CACHED: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".vscode/launch.json").unwrap());
|
||||
LazyLock::new(|| RelPath::from_unix_str(".vscode/launch.json").unwrap());
|
||||
*CACHED
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ test-support = [
|
|||
aho-corasick.workspace = true
|
||||
anyhow.workspace = true
|
||||
askpass.workspace = true
|
||||
async-trait.workspace = true
|
||||
async-channel.workspace = true
|
||||
async-trait.workspace = true
|
||||
base64.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
circular-buffer.workspace = true
|
||||
|
|
@ -60,15 +60,17 @@ globset.workspace = true
|
|||
gpui.workspace = true
|
||||
http_client = { workspace = true, features = ["github-download"] }
|
||||
image.workspace = true
|
||||
itertools.workspace = true
|
||||
indexmap.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
markdown.workspace = true
|
||||
node_runtime.workspace = true
|
||||
parking_lot.workspace = true
|
||||
path.workspace = true
|
||||
paths.workspace = true
|
||||
percent-encoding.workspace = true
|
||||
postage.workspace = true
|
||||
prettier.workspace = true
|
||||
rand.workspace = true
|
||||
|
|
@ -93,8 +95,8 @@ tempfile.workspace = true
|
|||
terminal.workspace = true
|
||||
text.workspace = true
|
||||
toml.workspace = true
|
||||
tracing.workspace = true
|
||||
url.workspace = true
|
||||
percent-encoding.workspace = true
|
||||
util.workspace = true
|
||||
watch.workspace = true
|
||||
wax.workspace = true
|
||||
|
|
@ -104,7 +106,6 @@ zed_credentials_provider.workspace = true
|
|||
zeroize.workspace = true
|
||||
zlog.workspace = true
|
||||
ztracing.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ impl RemoteBufferStore {
|
|||
.request(proto::OpenBufferByPath {
|
||||
project_id,
|
||||
worktree_id,
|
||||
path: path.to_proto(),
|
||||
path: path.as_unix_str().to_owned(),
|
||||
})
|
||||
.await?;
|
||||
let buffer_id = BufferId::new(response.buffer_id)?;
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ impl language::File for IndexTextFile {
|
|||
rpc::proto::File {
|
||||
worktree_id: self.worktree_id.to_proto(),
|
||||
entry_id: None,
|
||||
path: self.path.as_ref().to_proto(),
|
||||
path: self.path.as_ref().as_unix_str().to_owned(),
|
||||
mtime: None,
|
||||
is_deleted: false,
|
||||
is_historic: true,
|
||||
|
|
@ -336,7 +336,7 @@ impl StatusEntry {
|
|||
};
|
||||
|
||||
proto::StatusEntry {
|
||||
repo_path: self.repo_path.to_proto(),
|
||||
repo_path: self.repo_path.as_unix_str().to_owned(),
|
||||
simple_status,
|
||||
status: Some(status_to_proto(self.status)),
|
||||
diff_stat_added: self.diff_stat.map(|ds| ds.added),
|
||||
|
|
@ -3825,7 +3825,7 @@ impl GitStore {
|
|||
.files
|
||||
.into_iter()
|
||||
.map(|file| proto::CommitFile {
|
||||
path: file.path.to_proto(),
|
||||
path: file.path.as_unix_str().to_owned(),
|
||||
old_text: file.old_text,
|
||||
new_text: file.new_text,
|
||||
is_binary: file.is_binary,
|
||||
|
|
@ -4023,7 +4023,7 @@ impl GitStore {
|
|||
.entries
|
||||
.into_iter()
|
||||
.map(|(path, status)| proto::TreeDiffStatus {
|
||||
path: path.as_ref().to_proto(),
|
||||
path: path.as_ref().as_unix_str().to_owned(),
|
||||
status: match status {
|
||||
TreeDiffStatus::Added {} => proto::tree_diff_status::Status::Added.into(),
|
||||
TreeDiffStatus::Modified { .. } => {
|
||||
|
|
@ -5109,7 +5109,7 @@ impl RepositorySnapshot {
|
|||
.merge
|
||||
.merge_heads_by_conflicted_path
|
||||
.iter()
|
||||
.map(|(repo_path, _)| repo_path.to_proto())
|
||||
.map(|(repo_path, _)| repo_path.as_unix_str().to_owned())
|
||||
.collect(),
|
||||
merge_message: self.merge.message.as_ref().map(|msg| msg.to_string()),
|
||||
project_id,
|
||||
|
|
@ -5165,13 +5165,13 @@ impl RepositorySnapshot {
|
|||
current_new_entry = new_statuses.next();
|
||||
}
|
||||
Ordering::Greater => {
|
||||
removed_statuses.push(old_entry.repo_path.to_proto());
|
||||
removed_statuses.push(old_entry.repo_path.as_unix_str().to_owned());
|
||||
current_old_entry = old_statuses.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, Some(old_entry)) => {
|
||||
removed_statuses.push(old_entry.repo_path.to_proto());
|
||||
removed_statuses.push(old_entry.repo_path.as_unix_str().to_owned());
|
||||
current_old_entry = old_statuses.next();
|
||||
}
|
||||
(Some(new_entry), None) => {
|
||||
|
|
@ -5196,7 +5196,7 @@ impl RepositorySnapshot {
|
|||
.merge
|
||||
.merge_heads_by_conflicted_path
|
||||
.iter()
|
||||
.map(|(path, _)| path.to_proto())
|
||||
.map(|(path, _)| path.as_unix_str().to_owned())
|
||||
.collect(),
|
||||
merge_message: self.merge.message.as_ref().map(|msg| msg.to_string()),
|
||||
project_id,
|
||||
|
|
@ -6051,7 +6051,7 @@ impl Repository {
|
|||
commit,
|
||||
paths: paths
|
||||
.into_iter()
|
||||
.map(|p| p.to_proto())
|
||||
.map(|p| p.as_unix_str().to_owned())
|
||||
.collect(),
|
||||
})
|
||||
.await?;
|
||||
|
|
@ -7013,7 +7013,9 @@ impl Repository {
|
|||
repository_id: id.to_proto(),
|
||||
paths: entries
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.map(|repo_path| {
|
||||
repo_path.as_unix_str().to_owned()
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
|
|
@ -7026,7 +7028,9 @@ impl Repository {
|
|||
repository_id: id.to_proto(),
|
||||
paths: entries
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.map(|repo_path| {
|
||||
repo_path.as_unix_str().to_owned()
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await
|
||||
|
|
@ -7157,7 +7161,7 @@ impl Repository {
|
|||
repository_id: id.to_proto(),
|
||||
paths: entries
|
||||
.into_iter()
|
||||
.map(|repo_path| repo_path.to_proto())
|
||||
.map(|repo_path| repo_path.as_unix_str().to_owned())
|
||||
.collect(),
|
||||
})
|
||||
.await?;
|
||||
|
|
@ -7245,7 +7249,7 @@ impl Repository {
|
|||
is_dir: bool,
|
||||
) -> oneshot::Receiver<Result<()>> {
|
||||
let work_dir = self.snapshot.work_directory_abs_path.clone();
|
||||
let path_display = repo_path.as_ref().display(PathStyle::Posix);
|
||||
let path_display = repo_path.as_ref().display(PathStyle::Unix);
|
||||
let file_path_str = if is_dir {
|
||||
format!("{}/", path_display)
|
||||
} else {
|
||||
|
|
@ -7279,7 +7283,7 @@ impl Repository {
|
|||
is_dir: bool,
|
||||
) -> oneshot::Receiver<Result<()>> {
|
||||
let repository_dir = self.snapshot.repository_dir_abs_path.clone();
|
||||
let path_display = repo_path.as_ref().display(PathStyle::Posix);
|
||||
let path_display = repo_path.as_ref().display(PathStyle::Unix);
|
||||
let file_path_str = if is_dir {
|
||||
format!("{}/", path_display)
|
||||
} else {
|
||||
|
|
@ -7722,7 +7726,7 @@ impl Repository {
|
|||
.request(proto::SetIndexText {
|
||||
project_id: project_id.0,
|
||||
repository_id: id.to_proto(),
|
||||
path: path.to_proto(),
|
||||
path: path.as_unix_str().to_owned(),
|
||||
text: content,
|
||||
})
|
||||
.await?;
|
||||
|
|
@ -8406,7 +8410,7 @@ impl Repository {
|
|||
};
|
||||
Some((
|
||||
RepoPath::from_rel_path(
|
||||
&RelPath::from_proto(&entry.path).log_err()?,
|
||||
RelPath::from_unix_str(&entry.path).log_err()?,
|
||||
),
|
||||
status,
|
||||
))
|
||||
|
|
@ -8735,7 +8739,7 @@ impl Repository {
|
|||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
Some(sum_tree::Edit::Remove(PathKey(
|
||||
RelPath::from_proto(&path).log_err()?,
|
||||
RelPath::from_unix_str(&path).log_err()?.into(),
|
||||
)))
|
||||
})
|
||||
.chain(
|
||||
|
|
@ -9326,7 +9330,7 @@ fn format_job_key(key: &GitJobKey) -> SharedString {
|
|||
.iter()
|
||||
.map(|p| {
|
||||
let rel: &RelPath = p;
|
||||
format!("{}", AsRef::<Path>::as_ref(rel).display())
|
||||
rel.display(PathStyle::local())
|
||||
})
|
||||
.collect();
|
||||
format!("WriteIndex({})", paths_str.join(", ")).into()
|
||||
|
|
@ -9405,7 +9409,7 @@ pub fn worktrees_directory_for_repo(
|
|||
let resolved = if path_style.is_posix() {
|
||||
joined
|
||||
} else {
|
||||
util::normalize_path(&joined)
|
||||
path::normalize_path(&joined)
|
||||
};
|
||||
let resolved = if resolved.starts_with(repository_anchor_path) {
|
||||
resolved
|
||||
|
|
@ -9662,7 +9666,9 @@ fn log_source_to_proto(log_source: &LogSource) -> proto::GitLogSource {
|
|||
LogSource::All => proto::git_log_source::Source::All(proto::GitLogSourceAll {}),
|
||||
LogSource::Branch(branch) => proto::git_log_source::Source::Branch(branch.to_string()),
|
||||
LogSource::Sha(sha) => proto::git_log_source::Source::Sha(sha.to_string()),
|
||||
LogSource::Path(path) => proto::git_log_source::Source::Path(path.to_proto()),
|
||||
LogSource::Path(path) => {
|
||||
proto::git_log_source::Source::Path(path.as_unix_str().to_owned())
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
@ -10066,11 +10072,9 @@ mod tests {
|
|||
fn test_new_worktree_path_uses_posix_style_for_remote_paths() {
|
||||
let work_dir = Path::new("/home/user/dev/lsp-tests");
|
||||
let directory =
|
||||
worktrees_directory_for_repo(work_dir, "../worktrees", PathStyle::Posix).unwrap();
|
||||
let directory = PathStyle::Posix
|
||||
.join_path(&directory, "nimble-sky")
|
||||
.unwrap();
|
||||
let path = PathStyle::Posix.join_path(&directory, "lsp-tests").unwrap();
|
||||
worktrees_directory_for_repo(work_dir, "../worktrees", PathStyle::Unix).unwrap();
|
||||
let directory = PathStyle::Unix.join_path(&directory, "nimble-sky").unwrap();
|
||||
let path = PathStyle::Unix.join_path(&directory, "lsp-tests").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
path,
|
||||
|
|
|
|||
|
|
@ -698,7 +698,7 @@ impl ImageStoreImpl for Entity<RemoteImageStore> {
|
|||
.request(rpc::proto::OpenImageByPath {
|
||||
project_id,
|
||||
worktree_id,
|
||||
path: path.to_proto(),
|
||||
path: path.as_unix_str().to_owned(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1614,7 +1614,7 @@ fn edit_prediction_definitions_to_proto(
|
|||
.into_iter()
|
||||
.map(|definition| proto::EditPredictionDefinition {
|
||||
worktree_id: definition.path.worktree_id.to_proto(),
|
||||
path: definition.path.path.as_ref().to_proto(),
|
||||
path: definition.path.path.as_ref().as_unix_str().to_owned(),
|
||||
start: Some(proto::PointUtf16 {
|
||||
row: definition.range.start.0.row,
|
||||
column: definition.range.start.0.column,
|
||||
|
|
@ -1638,7 +1638,9 @@ fn edit_prediction_definitions_from_proto(
|
|||
Ok(EditPredictionDefinition {
|
||||
path: ProjectPath {
|
||||
worktree_id: worktree::WorktreeId::from_proto(definition.worktree_id),
|
||||
path: RelPath::from_proto(&definition.path).context("invalid path")?,
|
||||
path: RelPath::from_unix_str(&definition.path)
|
||||
.context("invalid path")?
|
||||
.into(),
|
||||
},
|
||||
range: Unclipped(PointUtf16::new(start.row, start.column))
|
||||
..Unclipped(PointUtf16::new(end.row, end.column)),
|
||||
|
|
|
|||
|
|
@ -8755,7 +8755,7 @@ impl LspStore {
|
|||
project_id: *project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
summary: Some(proto::DiagnosticSummary {
|
||||
path: path.as_ref().to_proto(),
|
||||
path: path.as_ref().as_unix_str().to_owned(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: 0,
|
||||
warning_count: 0,
|
||||
|
|
@ -9046,7 +9046,7 @@ impl LspStore {
|
|||
diagnostics_summary
|
||||
.more_summaries
|
||||
.push(proto::DiagnosticSummary {
|
||||
path: project_path.path.as_ref().to_proto(),
|
||||
path: project_path.path.as_ref().as_unix_str().to_owned(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: new_summary.error_count,
|
||||
warning_count: new_summary.warning_count,
|
||||
|
|
@ -9057,7 +9057,7 @@ impl LspStore {
|
|||
project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
summary: Some(proto::DiagnosticSummary {
|
||||
path: project_path.path.as_ref().to_proto(),
|
||||
path: project_path.path.as_ref().as_unix_str().to_owned(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: new_summary.error_count,
|
||||
warning_count: new_summary.warning_count,
|
||||
|
|
@ -9141,7 +9141,7 @@ impl LspStore {
|
|||
Ok(ControlFlow::Continue(Some((
|
||||
*project_id,
|
||||
proto::DiagnosticSummary {
|
||||
path: path_in_worktree.to_proto(),
|
||||
path: path_in_worktree.as_unix_str().to_owned(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: new_summary.error_count as u32,
|
||||
warning_count: new_summary.warning_count as u32,
|
||||
|
|
@ -9946,7 +9946,7 @@ impl LspStore {
|
|||
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||
let new_worktree_id = WorktreeId::from_proto(envelope.payload.new_worktree_id);
|
||||
let new_path =
|
||||
RelPath::from_proto(&envelope.payload.new_path).context("invalid relative path")?;
|
||||
RelPath::from_unix_str(&envelope.payload.new_path).context("invalid relative path")?;
|
||||
|
||||
let (worktree_store, old_worktree, new_worktree, old_entry) = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
|
|
@ -10015,7 +10015,9 @@ impl LspStore {
|
|||
{
|
||||
let project_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: RelPath::from_proto(&message_summary.path).context("invalid path")?,
|
||||
path: RelPath::from_unix_str(&message_summary.path)
|
||||
.context("invalid path")?
|
||||
.into(),
|
||||
};
|
||||
let path = project_path.path.clone();
|
||||
let server_id = LanguageServerId(message_summary.language_server_id as usize);
|
||||
|
|
@ -10050,7 +10052,7 @@ impl LspStore {
|
|||
diagnostics_summary
|
||||
.more_summaries
|
||||
.push(proto::DiagnosticSummary {
|
||||
path: project_path.path.as_ref().to_proto(),
|
||||
path: project_path.path.as_ref().as_unix_str().to_owned(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: summary.error_count as u32,
|
||||
warning_count: summary.warning_count as u32,
|
||||
|
|
@ -10061,7 +10063,7 @@ impl LspStore {
|
|||
project_id: *project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
summary: Some(proto::DiagnosticSummary {
|
||||
path: project_path.path.as_ref().to_proto(),
|
||||
path: project_path.path.as_ref().as_unix_str().to_owned(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: summary.error_count as u32,
|
||||
warning_count: summary.warning_count as u32,
|
||||
|
|
@ -11539,7 +11541,7 @@ impl LspStore {
|
|||
project_id,
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
summary: Some(proto::DiagnosticSummary {
|
||||
path: path.as_ref().to_proto(),
|
||||
path: path.as_ref().as_unix_str().to_owned(),
|
||||
language_server_id: server_id.0 as u64,
|
||||
error_count: 0,
|
||||
warning_count: 0,
|
||||
|
|
@ -12571,7 +12573,7 @@ impl LspStore {
|
|||
match &symbol.path {
|
||||
SymbolLocation::InProject(path) => {
|
||||
result.worktree_id = path.worktree_id.to_proto();
|
||||
result.path = path.path.to_proto();
|
||||
result.path = path.path.as_unix_str().to_owned();
|
||||
}
|
||||
SymbolLocation::OutsideProject {
|
||||
abs_path,
|
||||
|
|
@ -12592,8 +12594,9 @@ impl LspStore {
|
|||
let path = if serialized_symbol.signature.is_empty() {
|
||||
SymbolLocation::InProject(ProjectPath {
|
||||
worktree_id,
|
||||
path: RelPath::from_proto(&serialized_symbol.path)
|
||||
.context("invalid symbol path")?,
|
||||
path: RelPath::from_unix_str(&serialized_symbol.path)
|
||||
.context("invalid symbol path")?
|
||||
.into(),
|
||||
})
|
||||
} else {
|
||||
SymbolLocation::OutsideProject {
|
||||
|
|
@ -14840,7 +14843,7 @@ impl DiagnosticSummary {
|
|||
path: &RelPath,
|
||||
) -> proto::DiagnosticSummary {
|
||||
proto::DiagnosticSummary {
|
||||
path: path.to_proto(),
|
||||
path: path.as_unix_str().to_owned(),
|
||||
language_server_id: language_server_id.0 as u64,
|
||||
error_count: self.error_count as u32,
|
||||
warning_count: self.warning_count as u32,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use path::rel_path::RelPathBuf;
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
/// [RootPathTrie] is a workhorse of [super::ManifestTree]. It is responsible for determining the closest known entry for a given path.
|
||||
|
|
@ -59,12 +60,12 @@ impl<Label: Ord + Clone> RootPathTrie<Label> {
|
|||
) -> &mut Self {
|
||||
let mut current = self;
|
||||
|
||||
let mut path_so_far = <Arc<RelPath>>::from(RelPath::empty());
|
||||
let mut path_so_far = RelPathBuf::new();
|
||||
for key in path.0.iter() {
|
||||
path_so_far = path_so_far.join(RelPath::unix(key.as_ref()).unwrap());
|
||||
path_so_far = path_so_far.join(RelPath::from_unix_str(key.as_ref()).unwrap());
|
||||
current = match current.children.entry(key.clone()) {
|
||||
Entry::Vacant(vacant_entry) => {
|
||||
vacant_entry.insert(RootPathTrie::new_with_key(path_so_far.clone()))
|
||||
vacant_entry.insert(RootPathTrie::new_with_key(path_so_far.clone().into()))
|
||||
}
|
||||
Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ impl PrettierStore {
|
|||
) {
|
||||
let prettier_config_files = Prettier::CONFIG_FILE_NAMES
|
||||
.iter()
|
||||
.map(|name| RelPath::unix(name).unwrap())
|
||||
.map(|name| RelPath::from_unix_str(name).unwrap())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let prettier_config_file_changed = changes
|
||||
|
|
|
|||
|
|
@ -440,14 +440,14 @@ impl ProjectPath {
|
|||
pub fn from_proto(p: proto::ProjectPath) -> Option<Self> {
|
||||
Some(Self {
|
||||
worktree_id: WorktreeId::from_proto(p.worktree_id),
|
||||
path: RelPath::from_proto(&p.path).log_err()?,
|
||||
path: RelPath::from_unix_str(&p.path).log_err()?.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::ProjectPath {
|
||||
proto::ProjectPath {
|
||||
worktree_id: self.worktree_id.to_proto(),
|
||||
path: self.path.as_ref().to_proto(),
|
||||
path: self.path.as_ref().as_unix_str().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1725,7 +1725,7 @@ impl Project {
|
|||
let path_style = if response.payload.windows_paths {
|
||||
PathStyle::Windows
|
||||
} else {
|
||||
PathStyle::Posix
|
||||
PathStyle::Unix
|
||||
};
|
||||
|
||||
let worktree_store = cx.new(|cx| {
|
||||
|
|
@ -2152,7 +2152,7 @@ impl Project {
|
|||
root_repo_is_linked_worktree: false,
|
||||
},
|
||||
client,
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
cx,
|
||||
);
|
||||
self.worktree_store
|
||||
|
|
@ -3152,7 +3152,7 @@ impl Project {
|
|||
// because SSH projects have client_state: Local but still need to communicate with remote server
|
||||
let project_id = self.remote_id().unwrap_or(REMOTE_SERVER_PROJECT_ID);
|
||||
let downloading_files = self.downloading_files.clone();
|
||||
let path_str = path.to_proto();
|
||||
let path_str = path.as_unix_str().to_owned();
|
||||
|
||||
static NEXT_FILE_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
|
||||
let file_id = NEXT_FILE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
|
|
@ -5766,7 +5766,7 @@ impl Project {
|
|||
) -> Result<proto::OpenBufferResponse> {
|
||||
let peer_id = envelope.original_sender_id()?;
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let path = RelPath::from_proto(&envelope.payload.path)?;
|
||||
let path = RelPath::from_unix_str(&envelope.payload.path)?.into();
|
||||
let open_buffer = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.open_buffer(ProjectPath { worktree_id, path }, cx)
|
||||
|
|
|
|||
|
|
@ -1023,7 +1023,7 @@ impl SettingsObserver {
|
|||
.send(proto::UpdateWorktreeSettings {
|
||||
project_id,
|
||||
worktree_id,
|
||||
path: path.to_proto(),
|
||||
path: path.as_unix_str().to_owned(),
|
||||
content: Some(content),
|
||||
kind: Some(
|
||||
local_settings_kind_to_proto(LocalSettingsKind::Settings).into(),
|
||||
|
|
@ -1206,7 +1206,7 @@ impl SettingsObserver {
|
|||
.unwrap()
|
||||
.into();
|
||||
(settings_dir, LocalSettingsKind::Debug)
|
||||
} else if path.ends_with(RelPath::unix(EDITORCONFIG_NAME).unwrap()) {
|
||||
} else if path.ends_with(RelPath::from_unix_str(EDITORCONFIG_NAME).unwrap()) {
|
||||
let Some(settings_dir) = path.parent().map(Arc::from) else {
|
||||
continue;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ impl ToolchainStore {
|
|||
};
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let path = if let Some(path) = envelope.payload.path {
|
||||
RelPath::from_proto(&path)?
|
||||
RelPath::from_unix_str(&path)?.into()
|
||||
} else {
|
||||
RelPath::empty_arc()
|
||||
};
|
||||
|
|
@ -277,7 +277,7 @@ impl ToolchainStore {
|
|||
envelope: TypedEnvelope<proto::ActiveToolchain>,
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::ActiveToolchainResponse> {
|
||||
let path = RelPath::unix(envelope.payload.path.as_deref().unwrap_or(""))?;
|
||||
let path = RelPath::from_unix_str(envelope.payload.path.as_deref().unwrap_or(""))?;
|
||||
let toolchain = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
let language_name = LanguageName::from_proto(envelope.payload.language_name);
|
||||
|
|
@ -314,7 +314,8 @@ impl ToolchainStore {
|
|||
.update(&mut cx, |this, cx| {
|
||||
let language_name = LanguageName::from_proto(envelope.payload.language_name);
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
let path = RelPath::from_proto(envelope.payload.path.as_deref().unwrap_or(""))?;
|
||||
let path =
|
||||
RelPath::from_unix_str(envelope.payload.path.as_deref().unwrap_or(""))?.into();
|
||||
anyhow::Ok(this.list_toolchains(
|
||||
ProjectPath { worktree_id, path },
|
||||
language_name,
|
||||
|
|
@ -364,7 +365,7 @@ impl ToolchainStore {
|
|||
has_values,
|
||||
toolchains,
|
||||
groups,
|
||||
relative_worktree_path: Some(relative_path.to_proto()),
|
||||
relative_worktree_path: Some(relative_path.as_unix_str().to_owned()),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -637,7 +638,7 @@ impl RemoteToolchainStore {
|
|||
path: path.to_string_lossy().into_owned(),
|
||||
raw_json: toolchain.as_json.to_string(),
|
||||
}),
|
||||
path: Some(project_path.path.to_proto()),
|
||||
path: Some(project_path.path.as_unix_str().to_owned()),
|
||||
})
|
||||
.await
|
||||
.log_err()?;
|
||||
|
|
@ -667,7 +668,7 @@ impl RemoteToolchainStore {
|
|||
project_id,
|
||||
worktree_id: path.worktree_id.to_proto(),
|
||||
language_name: language_name.clone().into(),
|
||||
path: Some(path.path.to_proto()),
|
||||
path: Some(path.path.as_unix_str().to_owned()),
|
||||
})
|
||||
.await
|
||||
.log_err()?;
|
||||
|
|
@ -693,7 +694,7 @@ impl RemoteToolchainStore {
|
|||
Some((usize::try_from(group.start_index).ok()?, group.name.into()))
|
||||
})
|
||||
.collect();
|
||||
let relative_path = RelPath::from_proto(
|
||||
let relative_path = RelPath::from_unix_str(
|
||||
response
|
||||
.relative_worktree_path
|
||||
.as_deref()
|
||||
|
|
@ -706,7 +707,7 @@ impl RemoteToolchainStore {
|
|||
default: None,
|
||||
groups,
|
||||
},
|
||||
relative_path,
|
||||
relative_path.into(),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
@ -724,7 +725,7 @@ impl RemoteToolchainStore {
|
|||
project_id,
|
||||
worktree_id: path.worktree_id.to_proto(),
|
||||
language_name: language_name.clone().into(),
|
||||
path: Some(path.path.to_proto()),
|
||||
path: Some(path.path.as_unix_str().to_owned()),
|
||||
})
|
||||
.await
|
||||
.log_err()?;
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ impl WorktreeStore {
|
|||
let response = upstream_client.request(proto::CopyProjectEntry {
|
||||
project_id: *upstream_project_id,
|
||||
entry_id: entry_id.to_proto(),
|
||||
new_path: new_project_path.path.to_proto(),
|
||||
new_path: new_project_path.path.as_unix_str().to_owned(),
|
||||
new_worktree_id: new_project_path.worktree_id.to_proto(),
|
||||
});
|
||||
cx.spawn(async move |_, cx| {
|
||||
|
|
@ -668,7 +668,7 @@ impl WorktreeStore {
|
|||
let response = upstream_client.request(proto::RenameProjectEntry {
|
||||
project_id: *upstream_project_id,
|
||||
entry_id: entry_id.to_proto(),
|
||||
new_path: new_project_path.path.to_proto(),
|
||||
new_path: new_project_path.path.as_unix_str().to_owned(),
|
||||
new_worktree_id: new_project_path.worktree_id.to_proto(),
|
||||
});
|
||||
cx.spawn(async move |_, cx| {
|
||||
|
|
@ -1231,7 +1231,7 @@ impl WorktreeStore {
|
|||
let new_worktree_id = WorktreeId::from_proto(envelope.payload.new_worktree_id);
|
||||
let new_project_path = (
|
||||
new_worktree_id,
|
||||
RelPath::from_proto(&envelope.payload.new_path)?,
|
||||
RelPath::from_unix_str(&envelope.payload.new_path)?,
|
||||
);
|
||||
let (scan_id, entry) = this.update(&mut cx, |this, cx| {
|
||||
let Some((_, project_id)) = this.downstream_client else {
|
||||
|
|
@ -1289,7 +1289,7 @@ impl WorktreeStore {
|
|||
) -> Result<proto::ProjectEntryResponse> {
|
||||
let entry_id = ProjectEntryId::from_proto(request.entry_id);
|
||||
let new_worktree_id = WorktreeId::from_proto(request.new_worktree_id);
|
||||
let rel_path = RelPath::from_proto(&request.new_path)
|
||||
let rel_path = RelPath::from_unix_str(&request.new_path)
|
||||
.with_context(|| format!("received invalid relative path {:?}", &request.new_path))?;
|
||||
|
||||
let (scan_id, task) = this.update(&mut cx, |this, cx| {
|
||||
|
|
|
|||
|
|
@ -721,7 +721,12 @@ mod git_traversal {
|
|||
|
||||
let traversal = GitTraversal::new(
|
||||
&repo_snapshots,
|
||||
worktree_snapshot.traverse_from_path(true, false, true, RelPath::unix("x").unwrap()),
|
||||
worktree_snapshot.traverse_from_path(
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
RelPath::from_unix_str("x").unwrap(),
|
||||
),
|
||||
);
|
||||
let entries = traversal
|
||||
.map(|entry| (entry.path.clone(), entry.git_summary))
|
||||
|
|
@ -1204,45 +1209,44 @@ mod git_worktrees {
|
|||
let work_dir = Path::new("/code/my-project");
|
||||
|
||||
// Valid: sibling
|
||||
assert!(worktrees_directory_for_repo(work_dir, "../worktrees", PathStyle::Posix).is_ok());
|
||||
assert!(worktrees_directory_for_repo(work_dir, "../worktrees", PathStyle::Unix).is_ok());
|
||||
|
||||
// Valid: subdirectory
|
||||
assert!(
|
||||
worktrees_directory_for_repo(work_dir, ".git/zed-worktrees", PathStyle::Posix).is_ok()
|
||||
worktrees_directory_for_repo(work_dir, ".git/zed-worktrees", PathStyle::Unix).is_ok()
|
||||
);
|
||||
assert!(worktrees_directory_for_repo(work_dir, "my-worktrees", PathStyle::Posix).is_ok());
|
||||
assert!(worktrees_directory_for_repo(work_dir, "my-worktrees", PathStyle::Unix).is_ok());
|
||||
|
||||
// Invalid: just ".." would resolve back to the working directory itself
|
||||
let err = worktrees_directory_for_repo(work_dir, "..", PathStyle::Posix).unwrap_err();
|
||||
let err = worktrees_directory_for_repo(work_dir, "..", PathStyle::Unix).unwrap_err();
|
||||
assert!(err.to_string().contains("must not be \"..\""));
|
||||
|
||||
// Invalid: ".." with trailing separators
|
||||
let err = worktrees_directory_for_repo(work_dir, "..\\", PathStyle::Posix).unwrap_err();
|
||||
let err = worktrees_directory_for_repo(work_dir, "..\\", PathStyle::Unix).unwrap_err();
|
||||
assert!(err.to_string().contains("must not be \"..\""));
|
||||
let err = worktrees_directory_for_repo(work_dir, "../", PathStyle::Posix).unwrap_err();
|
||||
let err = worktrees_directory_for_repo(work_dir, "../", PathStyle::Unix).unwrap_err();
|
||||
assert!(err.to_string().contains("must not be \"..\""));
|
||||
|
||||
// Invalid: empty string would resolve to the working directory itself
|
||||
let err = worktrees_directory_for_repo(work_dir, "", PathStyle::Posix).unwrap_err();
|
||||
let err = worktrees_directory_for_repo(work_dir, "", PathStyle::Unix).unwrap_err();
|
||||
assert!(err.to_string().contains("must not be empty"));
|
||||
|
||||
// Invalid: absolute path
|
||||
let err =
|
||||
worktrees_directory_for_repo(work_dir, "/tmp/worktrees", PathStyle::Posix).unwrap_err();
|
||||
worktrees_directory_for_repo(work_dir, "/tmp/worktrees", PathStyle::Unix).unwrap_err();
|
||||
assert!(err.to_string().contains("relative path"));
|
||||
|
||||
// Invalid: "/" is absolute on Unix
|
||||
let err = worktrees_directory_for_repo(work_dir, "/", PathStyle::Posix).unwrap_err();
|
||||
let err = worktrees_directory_for_repo(work_dir, "/", PathStyle::Unix).unwrap_err();
|
||||
assert!(err.to_string().contains("relative path"));
|
||||
|
||||
// Invalid: "///" is absolute
|
||||
let err = worktrees_directory_for_repo(work_dir, "///", PathStyle::Posix).unwrap_err();
|
||||
let err = worktrees_directory_for_repo(work_dir, "///", PathStyle::Unix).unwrap_err();
|
||||
assert!(err.to_string().contains("relative path"));
|
||||
|
||||
// Invalid: escapes too far up
|
||||
let err =
|
||||
worktrees_directory_for_repo(work_dir, "../../other-project/wt", PathStyle::Posix)
|
||||
.unwrap_err();
|
||||
let err = worktrees_directory_for_repo(work_dir, "../../other-project/wt", PathStyle::Unix)
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("outside"));
|
||||
}
|
||||
|
||||
|
|
@ -1251,7 +1255,7 @@ mod git_worktrees {
|
|||
let work_dir = Path::new("/home/user/dev/lsp-tests");
|
||||
|
||||
let directory =
|
||||
worktrees_directory_for_repo(work_dir, "../worktrees", PathStyle::Posix).unwrap();
|
||||
worktrees_directory_for_repo(work_dir, "../worktrees", PathStyle::Unix).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
directory,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ async fn test_path_inclusion_matcher(cx: &mut gpui::TestAppContext) {
|
|||
let entry = Entry {
|
||||
id: ProjectEntryId::from_proto(1),
|
||||
kind: EntryKind::UnloadedDir,
|
||||
path: Arc::from(RelPath::unix(Path::new("src/data")).unwrap()),
|
||||
path: Arc::from(RelPath::from_unix_str(Path::new("src/data")).unwrap()),
|
||||
inode: 0,
|
||||
mtime: None,
|
||||
canonical_path: None,
|
||||
|
|
@ -90,7 +90,7 @@ async fn test_path_inclusion_matcher(cx: &mut gpui::TestAppContext) {
|
|||
// 2. Test searching for `field`, including ignored files but updating
|
||||
// `files_to_include` to only include files under `src/lib`.
|
||||
let include_ignored = true;
|
||||
let files_to_include = PathMatcher::new(vec!["src/lib"], PathStyle::Posix).unwrap();
|
||||
let files_to_include = PathMatcher::new(vec!["src/lib"], PathStyle::Unix).unwrap();
|
||||
let files_to_exclude = PathMatcher::default();
|
||||
let match_full_paths = false;
|
||||
let search_query = SearchQuery::text(
|
||||
|
|
|
|||
|
|
@ -15719,7 +15719,7 @@ mod disable_ai_settings_tests {
|
|||
|
||||
let worktree_id = WorktreeId::from_usize(1);
|
||||
let rel_path = |path: &str| -> std::sync::Arc<util::rel_path::RelPath> {
|
||||
std::sync::Arc::from(util::rel_path::RelPath::unix(path).unwrap())
|
||||
std::sync::Arc::from(util::rel_path::RelPath::from_unix_str(path).unwrap())
|
||||
};
|
||||
let project_path = rel_path("project");
|
||||
let settings_location = SettingsLocation {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ fn load_linux_repo_snapshot() -> Vec<GitEntry> {
|
|||
|
||||
let entry = Entry {
|
||||
kind,
|
||||
path: Arc::from(RelPath::unix(&(line.trim_end()[2..])).unwrap()),
|
||||
path: Arc::from(RelPath::from_unix_str(&(line.trim_end()[2..])).unwrap()),
|
||||
id: ProjectEntryId::default(),
|
||||
size: 0,
|
||||
inode: 0,
|
||||
|
|
|
|||
|
|
@ -1841,7 +1841,7 @@ impl ProjectPanel {
|
|||
}
|
||||
let trimmed_filename = trimmed_filename.trim_start_matches('/');
|
||||
|
||||
let Ok(filename) = RelPath::unix(trimmed_filename) else {
|
||||
let Ok(filename) = RelPath::from_unix_str(trimmed_filename) else {
|
||||
edit_state.validation_state = ValidationState::Warning(
|
||||
"File or directory name contains leading or trailing whitespace.".to_string(),
|
||||
);
|
||||
|
|
@ -1865,7 +1865,7 @@ impl ProjectPanel {
|
|||
let new_path = if let Some(parent) = entry.path.clone().parent() {
|
||||
parent.join(&filename)
|
||||
} else {
|
||||
filename.into()
|
||||
filename.to_owned()
|
||||
};
|
||||
if let Some(existing) = worktree.read(cx).entry_for_path(&new_path)
|
||||
&& existing.id != entry.id
|
||||
|
|
@ -1950,7 +1950,7 @@ impl ProjectPanel {
|
|||
});
|
||||
} else {
|
||||
let new_path = if let Some(parent) = entry.path.parent() {
|
||||
parent.join(&filename)
|
||||
parent.join(&filename).into()
|
||||
} else {
|
||||
filename.clone()
|
||||
};
|
||||
|
|
@ -3303,7 +3303,7 @@ impl ProjectPanel {
|
|||
let source_entry = source_worktree.read(cx).entry_for_id(source.entry_id)?;
|
||||
|
||||
let clipboard_entry_file_name = source_entry.path.file_name()?.to_string();
|
||||
new_path.push(RelPath::unix(&clipboard_entry_file_name).unwrap());
|
||||
new_path.push(RelPath::from_unix_str(&clipboard_entry_file_name).unwrap());
|
||||
|
||||
let (extension, file_name_without_extension) = if source_entry.is_file() {
|
||||
(
|
||||
|
|
@ -3339,7 +3339,7 @@ impl ProjectPanel {
|
|||
new_file_name.push_str(extension);
|
||||
}
|
||||
|
||||
new_path.push(RelPath::unix(&new_file_name).unwrap());
|
||||
new_path.push(RelPath::from_unix_str(&new_file_name).unwrap());
|
||||
|
||||
disambiguation_range = Some(0..(file_name_len + disambiguation_len));
|
||||
ix += 1;
|
||||
|
|
@ -3858,7 +3858,7 @@ impl ProjectPanel {
|
|||
let dir_path = if include_root {
|
||||
worktree.read(cx).root_name().join(&dir_path)
|
||||
} else {
|
||||
dir_path
|
||||
dir_path.to_rel_path_buf()
|
||||
};
|
||||
|
||||
self.workspace
|
||||
|
|
@ -3923,7 +3923,7 @@ impl ProjectPanel {
|
|||
let Some(source_name) = source_path.path.file_name() else {
|
||||
return (None, None);
|
||||
};
|
||||
let Ok(source_name) = RelPath::unix(source_name) else {
|
||||
let Ok(source_name) = RelPath::from_unix_str(source_name) else {
|
||||
return (None, None);
|
||||
};
|
||||
|
||||
|
|
@ -4183,7 +4183,10 @@ impl ProjectPanel {
|
|||
entry: Entry {
|
||||
id: NEW_ENTRY_ID,
|
||||
kind: new_entry_kind,
|
||||
path: parent_entry.path.join(RelPath::unix("\0").unwrap()),
|
||||
path: parent_entry
|
||||
.path
|
||||
.join(RelPath::from_unix_str("\0").unwrap())
|
||||
.into(),
|
||||
inode: 0,
|
||||
mtime: parent_entry.mtime,
|
||||
size: parent_entry.size,
|
||||
|
|
@ -4390,19 +4393,21 @@ impl ProjectPanel {
|
|||
entry.path.strip_prefix(root_folded_entry).ok().and_then(
|
||||
|suffix| {
|
||||
Some(
|
||||
RelPath::unix(root_folded_entry.file_name()?)
|
||||
.unwrap()
|
||||
.join(suffix),
|
||||
RelPath::from_unix_str(
|
||||
root_folded_entry.file_name()?,
|
||||
)
|
||||
.unwrap()
|
||||
.join(suffix),
|
||||
)
|
||||
},
|
||||
)
|
||||
})
|
||||
.or_else(|| {
|
||||
entry.path.file_name().map(|file_name| {
|
||||
RelPath::unix(file_name).unwrap().into()
|
||||
RelPath::from_unix_str(file_name).unwrap().to_owned()
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| entry.path.clone());
|
||||
.unwrap_or_else(|| entry.path.to_rel_path_buf());
|
||||
let depth = path.components().count();
|
||||
(depth, path.as_unix_str().chars().count())
|
||||
};
|
||||
|
|
@ -4588,7 +4593,7 @@ impl ProjectPanel {
|
|||
if let Some(name) = path.file_name()
|
||||
&& let Some(name) = name.to_str()
|
||||
{
|
||||
let target_path = target_directory.join(RelPath::unix(name).unwrap());
|
||||
let target_path = target_directory.join(RelPath::from_unix_str(name).unwrap());
|
||||
if worktree.read(cx).entry_for_path(&target_path).is_some() {
|
||||
paths_to_replace.push((name.to_string(), path.clone()));
|
||||
}
|
||||
|
|
@ -4946,7 +4951,7 @@ impl ProjectPanel {
|
|||
move_results.iter().find(|(id, _)| id == resolved_id)?;
|
||||
let worktree = project.worktree_for_entry(new_entry.id, cx)?;
|
||||
let leaf_path = new_entry.path.join(suffix);
|
||||
Some((worktree, leaf_path))
|
||||
Some((worktree, leaf_path.into()))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
|
|||
let path = match &symbol.path {
|
||||
SymbolLocation::InProject(project_path) => {
|
||||
let project = self.project.read(cx);
|
||||
let mut path = project_path.path.clone();
|
||||
let mut path = project_path.path.to_rel_path_buf();
|
||||
if self.show_worktree_root_name
|
||||
&& let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ const DEV_CONTAINER_SUGGEST_KEY: &str = "dev_container_suggest_dismissed";
|
|||
|
||||
fn devcontainer_dir_path() -> &'static RelPath {
|
||||
static PATH: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".devcontainer").expect("valid path"));
|
||||
LazyLock::new(|| RelPath::from_unix_str(".devcontainer").expect("valid path"));
|
||||
*PATH
|
||||
}
|
||||
|
||||
fn devcontainer_json_path() -> &'static RelPath {
|
||||
static PATH: LazyLock<&'static RelPath> =
|
||||
LazyLock::new(|| RelPath::unix(".devcontainer.json").expect("valid path"));
|
||||
LazyLock::new(|| RelPath::from_unix_str(".devcontainer.json").expect("valid path"));
|
||||
*PATH
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1845,7 +1845,7 @@ impl RemoteServerProjects {
|
|||
.and_then(|path| path.into_abs_path())
|
||||
.map(|path| RemotePathBuf::new(path, path_style))
|
||||
.unwrap_or_else(|| match path_style {
|
||||
PathStyle::Posix => RemotePathBuf::from_str("/", PathStyle::Posix),
|
||||
PathStyle::Unix => RemotePathBuf::from_str("/", PathStyle::Unix),
|
||||
PathStyle::Windows => {
|
||||
RemotePathBuf::from_str("C:\\", PathStyle::Windows)
|
||||
}
|
||||
|
|
@ -2164,7 +2164,7 @@ impl RemoteServerProjects {
|
|||
if let Some(worktree) = worktree {
|
||||
let tree_id = worktree.read(cx).id();
|
||||
let devcontainer_path =
|
||||
match RelPath::new(&config_path, util::paths::PathStyle::Posix) {
|
||||
match RelPath::new(&config_path, util::paths::PathStyle::Unix) {
|
||||
Ok(path) => path.into_owned(),
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ impl DockerExecConnection {
|
|||
|
||||
this.path_style = match remote_platform.os {
|
||||
RemoteOs::Windows => Some(PathStyle::Windows),
|
||||
_ => Some(PathStyle::Posix),
|
||||
_ => Some(PathStyle::Unix),
|
||||
};
|
||||
|
||||
this.remote_platform = Some(remote_platform);
|
||||
|
|
@ -219,7 +219,7 @@ impl DockerExecConnection {
|
|||
version_str
|
||||
);
|
||||
let dst_path =
|
||||
paths::remote_server_dir_relative().join(RelPath::unix(&binary_name).unwrap());
|
||||
paths::remote_server_dir_relative().join(RelPath::from_unix_str(&binary_name).unwrap());
|
||||
|
||||
let binary_exists_on_server = self
|
||||
.run_docker_exec(
|
||||
|
|
@ -240,7 +240,7 @@ impl DockerExecConnection {
|
|||
.await?
|
||||
{
|
||||
let tmp_path = paths::remote_server_dir_relative().join(
|
||||
RelPath::unix(&format!(
|
||||
RelPath::from_unix_str(&format!(
|
||||
"download-{}-{}",
|
||||
std::process::id(),
|
||||
remote_server_path.file_name().unwrap().to_string_lossy()
|
||||
|
|
@ -257,11 +257,11 @@ impl DockerExecConnection {
|
|||
.await?;
|
||||
self.extract_server_binary(&dst_path, &tmp_path, &remote_dir_for_server, delegate, cx)
|
||||
.await?;
|
||||
return Ok(dst_path);
|
||||
return Ok(dst_path.into());
|
||||
}
|
||||
|
||||
if binary_exists_on_server {
|
||||
return Ok(dst_path);
|
||||
return Ok(dst_path.into());
|
||||
}
|
||||
|
||||
let wanted_version = cx.update(|cx| match release_channel {
|
||||
|
|
@ -276,7 +276,7 @@ impl DockerExecConnection {
|
|||
})?;
|
||||
|
||||
let tmp_path_gz = paths::remote_server_dir_relative().join(
|
||||
RelPath::unix(&format!(
|
||||
RelPath::from_unix_str(&format!(
|
||||
"{}-download-{}.gz",
|
||||
binary_name,
|
||||
std::process::id()
|
||||
|
|
@ -302,7 +302,7 @@ impl DockerExecConnection {
|
|||
)
|
||||
.await
|
||||
.context("extracting server binary")?;
|
||||
return Ok(dst_path);
|
||||
return Ok(dst_path.into());
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
|
|
@ -334,7 +334,7 @@ impl DockerExecConnection {
|
|||
)
|
||||
.await
|
||||
.context("extracting server binary")?;
|
||||
Ok(dst_path)
|
||||
Ok(dst_path.into())
|
||||
}
|
||||
|
||||
async fn docker_user_home_dir(&self) -> Result<String> {
|
||||
|
|
@ -850,7 +850,7 @@ impl RemoteConnection for DockerExecConnection {
|
|||
}
|
||||
|
||||
fn path_style(&self) -> PathStyle {
|
||||
self.path_style.unwrap_or(PathStyle::Posix)
|
||||
self.path_style.unwrap_or(PathStyle::Unix)
|
||||
}
|
||||
|
||||
fn remote_platform(&self) -> RemotePlatform {
|
||||
|
|
|
|||
|
|
@ -773,7 +773,7 @@ impl SshRemoteConnection {
|
|||
|
||||
let (ssh_path_style, ssh_default_system_shell) = match ssh_platform.os {
|
||||
RemoteOs::Windows => (PathStyle::Windows, ssh_shell.clone()),
|
||||
_ => (PathStyle::Posix, String::from("/bin/sh")),
|
||||
_ => (PathStyle::Unix, String::from("/bin/sh")),
|
||||
};
|
||||
|
||||
let mut this = Self {
|
||||
|
|
@ -822,7 +822,7 @@ impl SshRemoteConnection {
|
|||
}
|
||||
);
|
||||
let dst_path =
|
||||
paths::remote_server_dir_relative().join(RelPath::unix(&binary_name).unwrap());
|
||||
paths::remote_server_dir_relative().join(RelPath::from_unix_str(&binary_name).unwrap());
|
||||
|
||||
let binary_exists_on_server = self
|
||||
.socket
|
||||
|
|
@ -845,7 +845,7 @@ impl SshRemoteConnection {
|
|||
.await?
|
||||
{
|
||||
let tmp_path = paths::remote_server_dir_relative().join(
|
||||
RelPath::unix(&format!(
|
||||
RelPath::from_unix_str(&format!(
|
||||
"download-{}-{}",
|
||||
std::process::id(),
|
||||
remote_server_path.file_name().unwrap().to_string_lossy()
|
||||
|
|
@ -856,11 +856,11 @@ impl SshRemoteConnection {
|
|||
.await?;
|
||||
self.extract_server_binary(&dst_path, &tmp_path, delegate, cx)
|
||||
.await?;
|
||||
return Ok(dst_path);
|
||||
return Ok(dst_path.into());
|
||||
}
|
||||
|
||||
if binary_exists_on_server {
|
||||
return Ok(dst_path);
|
||||
return Ok(dst_path.into());
|
||||
}
|
||||
|
||||
let wanted_version = cx.update(|cx| match release_channel {
|
||||
|
|
@ -875,7 +875,7 @@ impl SshRemoteConnection {
|
|||
})?;
|
||||
|
||||
let tmp_path_compressed = remote_server_dir_relative().join(
|
||||
RelPath::unix(&format!(
|
||||
RelPath::from_unix_str(&format!(
|
||||
"{}-download-{}.{}",
|
||||
binary_name,
|
||||
std::process::id(),
|
||||
|
|
@ -905,7 +905,7 @@ impl SshRemoteConnection {
|
|||
self.extract_server_binary(&dst_path, &tmp_path_compressed, delegate, cx)
|
||||
.await
|
||||
.context("extracting server binary")?;
|
||||
return Ok(dst_path);
|
||||
return Ok(dst_path.into());
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
|
|
@ -930,7 +930,7 @@ impl SshRemoteConnection {
|
|||
self.extract_server_binary(&dst_path, &tmp_path_compressed, delegate, cx)
|
||||
.await
|
||||
.context("extracting server binary")?;
|
||||
Ok(dst_path)
|
||||
Ok(dst_path.into())
|
||||
}
|
||||
|
||||
async fn download_binary_on_server(
|
||||
|
|
@ -2041,7 +2041,7 @@ mod tests {
|
|||
Some("~/work".to_string()),
|
||||
None,
|
||||
env.clone(),
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/bin/bash",
|
||||
ShellKind::Posix,
|
||||
vec!["-o".to_string(), "ControlMaster=auto".to_string()],
|
||||
|
|
@ -2061,7 +2061,7 @@ mod tests {
|
|||
Some("~/work".to_string()),
|
||||
None,
|
||||
env.clone(),
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/bin/fish",
|
||||
ShellKind::Fish,
|
||||
vec!["-p".to_string(), "2222".to_string()],
|
||||
|
|
@ -2096,7 +2096,7 @@ mod tests {
|
|||
None,
|
||||
Some((1, "foo".to_owned(), 2)),
|
||||
env.clone(),
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/bin/fish",
|
||||
ShellKind::Fish,
|
||||
vec!["-p".to_string(), "2222".to_string()],
|
||||
|
|
@ -2136,7 +2136,7 @@ mod tests {
|
|||
None,
|
||||
None,
|
||||
HashMap::default(),
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/bin/bash",
|
||||
ShellKind::Posix,
|
||||
vec![],
|
||||
|
|
@ -2291,7 +2291,7 @@ mod tests {
|
|||
None,
|
||||
Some((8080, "::1".to_owned(), 80)),
|
||||
HashMap::default(),
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/bin/bash",
|
||||
ShellKind::Posix,
|
||||
vec![],
|
||||
|
|
|
|||
|
|
@ -203,10 +203,10 @@ impl WslRemoteConnection {
|
|||
);
|
||||
|
||||
let dst_path =
|
||||
paths::remote_server_dir_relative().join(RelPath::unix(&binary_name).unwrap());
|
||||
paths::remote_server_dir_relative().join(RelPath::from_unix_str(&binary_name).unwrap());
|
||||
|
||||
if let Some(parent) = dst_path.parent() {
|
||||
let parent = parent.display(PathStyle::Posix);
|
||||
let parent = parent.display(PathStyle::Unix);
|
||||
let mkdir = self.shell_kind.prepend_command_prefix("mkdir");
|
||||
self.run_wsl_command(&mkdir, &["-p", &parent])
|
||||
.await
|
||||
|
|
@ -214,7 +214,7 @@ impl WslRemoteConnection {
|
|||
}
|
||||
|
||||
let binary_exists_on_server = self
|
||||
.run_wsl_command(&dst_path.display(PathStyle::Posix), &["version"])
|
||||
.run_wsl_command(&dst_path.display(PathStyle::Unix), &["version"])
|
||||
.await
|
||||
.is_ok();
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ impl WslRemoteConnection {
|
|||
.await?
|
||||
{
|
||||
let tmp_path = paths::remote_server_dir_relative().join(
|
||||
&RelPath::unix(&format!(
|
||||
&RelPath::from_unix_str(&format!(
|
||||
"download-{}-{}",
|
||||
std::process::id(),
|
||||
remote_server_path.file_name().unwrap().to_string_lossy()
|
||||
|
|
@ -239,11 +239,11 @@ impl WslRemoteConnection {
|
|||
.await?;
|
||||
self.extract_and_install(&tmp_path, &dst_path, delegate, cx)
|
||||
.await?;
|
||||
return Ok(dst_path);
|
||||
return Ok(dst_path.into());
|
||||
}
|
||||
|
||||
if binary_exists_on_server {
|
||||
return Ok(dst_path);
|
||||
return Ok(dst_path.into());
|
||||
}
|
||||
|
||||
let wanted_version = match release_channel {
|
||||
|
|
@ -257,16 +257,16 @@ impl WslRemoteConnection {
|
|||
|
||||
let tmp_path = format!(
|
||||
"{}.{}.gz",
|
||||
dst_path.display(PathStyle::Posix),
|
||||
dst_path.display(PathStyle::Unix),
|
||||
std::process::id()
|
||||
);
|
||||
let tmp_path = RelPath::unix(&tmp_path).unwrap();
|
||||
let tmp_path = RelPath::from_unix_str(&tmp_path).unwrap();
|
||||
|
||||
self.upload_file(&src_path, &tmp_path, delegate, cx).await?;
|
||||
self.extract_and_install(&tmp_path, &dst_path, delegate, cx)
|
||||
.await?;
|
||||
|
||||
Ok(dst_path)
|
||||
Ok(dst_path.into())
|
||||
}
|
||||
|
||||
async fn upload_file(
|
||||
|
|
@ -279,7 +279,7 @@ impl WslRemoteConnection {
|
|||
delegate.set_status(Some("Uploading remote server"), cx);
|
||||
|
||||
if let Some(parent) = dst_path.parent() {
|
||||
let parent = parent.display(PathStyle::Posix);
|
||||
let parent = parent.display(PathStyle::Unix);
|
||||
let mkdir = self.shell_kind.prepend_command_prefix("mkdir");
|
||||
self.run_wsl_command(&mkdir, &["-p", &parent])
|
||||
.await
|
||||
|
|
@ -301,7 +301,7 @@ impl WslRemoteConnection {
|
|||
let cp = self.shell_kind.prepend_command_prefix("cp");
|
||||
self.run_wsl_command(
|
||||
&cp,
|
||||
&["-f", &src_path_in_wsl, &dst_path.display(PathStyle::Posix)],
|
||||
&["-f", &src_path_in_wsl, &dst_path.display(PathStyle::Unix)],
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
|
|
@ -327,8 +327,8 @@ impl WslRemoteConnection {
|
|||
) -> Result<()> {
|
||||
delegate.set_status(Some("Extracting remote server"), cx);
|
||||
|
||||
let tmp_path_str = tmp_path.display(PathStyle::Posix);
|
||||
let dst_path_str = dst_path.display(PathStyle::Posix);
|
||||
let tmp_path_str = tmp_path.display(PathStyle::Unix);
|
||||
let dst_path_str = dst_path.display(PathStyle::Unix);
|
||||
|
||||
// Build extraction script with proper error handling
|
||||
let script = if tmp_path_str.ends_with(".gz") {
|
||||
|
|
@ -379,7 +379,7 @@ impl RemoteConnection for WslRemoteConnection {
|
|||
}
|
||||
}
|
||||
|
||||
proxy_args.push(remote_binary_path.display(PathStyle::Posix).into_owned());
|
||||
proxy_args.push(remote_binary_path.display(PathStyle::Unix).into_owned());
|
||||
proxy_args.push("proxy".to_owned());
|
||||
proxy_args.push("--identifier".to_owned());
|
||||
proxy_args.push(unique_identifier);
|
||||
|
|
@ -467,7 +467,7 @@ impl RemoteConnection for WslRemoteConnection {
|
|||
|
||||
let shell_kind = self.shell_kind;
|
||||
let working_dir = working_dir
|
||||
.map(|working_dir| RemotePathBuf::new(working_dir, PathStyle::Posix).to_string())
|
||||
.map(|working_dir| RemotePathBuf::new(working_dir, PathStyle::Unix).to_string())
|
||||
.unwrap_or("~".to_string());
|
||||
|
||||
let mut exec = String::from("exec env ");
|
||||
|
|
@ -538,7 +538,7 @@ impl RemoteConnection for WslRemoteConnection {
|
|||
}
|
||||
|
||||
fn path_style(&self) -> PathStyle {
|
||||
PathStyle::Posix
|
||||
PathStyle::Unix
|
||||
}
|
||||
|
||||
fn remote_platform(&self) -> RemotePlatform {
|
||||
|
|
|
|||
|
|
@ -585,7 +585,7 @@ impl HeadlessProject {
|
|||
mut cx: AsyncApp,
|
||||
) -> Result<proto::OpenBufferResponse> {
|
||||
let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
|
||||
let path = RelPath::from_proto(&message.payload.path)?;
|
||||
let path = RelPath::from_unix_str(&message.payload.path)?.into();
|
||||
let (buffer_store, buffer) = this.update(&mut cx, |this, cx| {
|
||||
let buffer_store = this.buffer_store.clone();
|
||||
let buffer = this.buffer_store.update(cx, |buffer_store, cx| {
|
||||
|
|
@ -614,7 +614,7 @@ impl HeadlessProject {
|
|||
) -> Result<proto::OpenImageResponse> {
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
||||
let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
|
||||
let path = RelPath::from_proto(&message.payload.path)?;
|
||||
let path = RelPath::from_unix_str(&message.payload.path)?;
|
||||
let project_id = message.payload.project_id;
|
||||
use proto::create_image_for_peer::Variant;
|
||||
|
||||
|
|
@ -729,7 +729,7 @@ impl HeadlessProject {
|
|||
);
|
||||
|
||||
let worktree_id = WorktreeId::from_proto(message.payload.worktree_id);
|
||||
let path = RelPath::from_proto(&message.payload.path)?;
|
||||
let path = RelPath::from_unix_str(&message.payload.path)?;
|
||||
let project_id = message.payload.project_id;
|
||||
let file_id = message.payload.file_id;
|
||||
log::debug!(
|
||||
|
|
|
|||
|
|
@ -100,7 +100,9 @@ impl EditorconfigStore {
|
|||
return Err(InvalidSettingsError::Editorconfig {
|
||||
message: e.to_string(),
|
||||
path: LocalSettingsPath::InWorktree(
|
||||
rel_path.join(RelPath::unix(EDITORCONFIG_NAME).unwrap()),
|
||||
rel_path
|
||||
.join(RelPath::from_unix_str(EDITORCONFIG_NAME).unwrap())
|
||||
.into(),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ impl LocalSettingsPath {
|
|||
|
||||
pub fn to_proto(&self) -> String {
|
||||
match self {
|
||||
Self::InWorktree(path) => path.to_proto(),
|
||||
Self::InWorktree(path) => path.as_unix_str().to_owned(),
|
||||
Self::OutsideWorktree(path) => path.to_string_lossy().to_string(),
|
||||
}
|
||||
}
|
||||
|
|
@ -237,7 +237,7 @@ impl LocalSettingsPath {
|
|||
if is_outside_worktree {
|
||||
Ok(Self::OutsideWorktree(PathBuf::from(path).into()))
|
||||
} else {
|
||||
Ok(Self::InWorktree(RelPath::from_proto(path)?))
|
||||
Ok(Self::InWorktree(RelPath::from_unix_str(path)?.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1048,7 +1048,7 @@ impl SettingsStore {
|
|||
return Err(InvalidSettingsError::Tasks {
|
||||
message: "Attempted to submit tasks into the settings store".to_string(),
|
||||
path: directory_path
|
||||
.join(RelPath::unix(task_file_name()).unwrap())
|
||||
.join(RelPath::from_unix_str(task_file_name()).unwrap())
|
||||
.as_std_path()
|
||||
.to_path_buf(),
|
||||
});
|
||||
|
|
@ -1058,7 +1058,7 @@ impl SettingsStore {
|
|||
message: "Attempted to submit debugger config into the settings store"
|
||||
.to_string(),
|
||||
path: directory_path
|
||||
.join(RelPath::unix(task_file_name()).unwrap())
|
||||
.join(RelPath::from_unix_str(task_file_name()).unwrap())
|
||||
.as_std_path()
|
||||
.to_path_buf(),
|
||||
});
|
||||
|
|
@ -1085,7 +1085,9 @@ impl SettingsStore {
|
|||
ParseStatus::Success => Ok(()),
|
||||
ParseStatus::Unchanged => Ok(()),
|
||||
ParseStatus::Failed { error } => Err(InvalidSettingsError::LocalSettings {
|
||||
path: directory_path.join(local_settings_file_relative_path()),
|
||||
path: directory_path
|
||||
.join(local_settings_file_relative_path())
|
||||
.into(),
|
||||
message: error,
|
||||
}),
|
||||
}?;
|
||||
|
|
@ -2772,23 +2774,17 @@ mod tests {
|
|||
|
||||
let local_1_child = (
|
||||
WorktreeId::from_usize(0),
|
||||
RelPath::new(
|
||||
std::path::Path::new("child1"),
|
||||
util::paths::PathStyle::Posix,
|
||||
)
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
RelPath::new(std::path::Path::new("child1"), util::paths::PathStyle::Unix)
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
);
|
||||
|
||||
let local_2 = (WorktreeId::from_usize(1), RelPath::empty_arc());
|
||||
let local_2_child = (
|
||||
WorktreeId::from_usize(1),
|
||||
RelPath::new(
|
||||
std::path::Path::new("child2"),
|
||||
util::paths::PathStyle::Posix,
|
||||
)
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
RelPath::new(std::path::Path::new("child2"), util::paths::PathStyle::Unix)
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
);
|
||||
|
||||
fn get(content: &SettingsContent) -> Option<&u32> {
|
||||
|
|
|
|||
|
|
@ -4113,7 +4113,7 @@ impl SettingsWindow {
|
|||
} else {
|
||||
Some(worktree.update(cx, |tree, cx| {
|
||||
tree.create_entry(
|
||||
settings_path.clone(),
|
||||
settings_path.clone().into(),
|
||||
false,
|
||||
Some(initial_project_settings_content().as_bytes().to_vec()),
|
||||
cx,
|
||||
|
|
@ -4647,7 +4647,7 @@ fn update_settings_file(
|
|||
anyhow::bail!("No settings window found");
|
||||
};
|
||||
|
||||
update_project_setting_file(worktree_id, rel_path, update, settings_window, cx)
|
||||
update_project_setting_file(worktree_id, rel_path.into(), update, settings_window, cx)
|
||||
}
|
||||
SettingsUiFile::User => {
|
||||
// todo(settings_ui) error?
|
||||
|
|
@ -6477,7 +6477,7 @@ mod project_settings_update_tests {
|
|||
(worktree.read(cx).id(), worktree.downgrade())
|
||||
});
|
||||
|
||||
let rel_path: Arc<RelPath> = RelPath::unix(".zed/settings.json")
|
||||
let rel_path: Arc<RelPath> = RelPath::from_unix_str(".zed/settings.json")
|
||||
.expect("valid path")
|
||||
.into_arc();
|
||||
let project_path = ProjectPath {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ path = "src/util.rs"
|
|||
doctest = true
|
||||
|
||||
[features]
|
||||
test-support = ["rand", "util_macros"]
|
||||
test-support = ["rand", "util_macros", "path/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
|
|
@ -42,6 +42,7 @@ url.workspace = true
|
|||
percent-encoding.workspace = true
|
||||
util_macros = { workspace = true, optional = true }
|
||||
gpui_util.workspace = true
|
||||
path = { workspace = true, features = ["serde"] }
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
smol.workspace = true
|
||||
|
|
@ -66,3 +67,4 @@ windows.workspace = true
|
|||
rand.workspace = true
|
||||
util_macros.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
path = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use globset::{GlobBuilder, GlobSet, GlobSetBuilder};
|
|||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
|
@ -15,8 +14,10 @@ use std::{
|
|||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use crate::rel_path::RelPath;
|
||||
use crate::rel_path::RelPathBuf;
|
||||
use path::rel_path::RelPath;
|
||||
use path::rel_path::RelPathBuf;
|
||||
|
||||
pub use path::PathStyle;
|
||||
|
||||
/// Returns the path to the user's home directory.
|
||||
pub fn home_dir() -> &'static PathBuf {
|
||||
|
|
@ -349,199 +350,6 @@ impl AsRef<Path> for SanitizedPath {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum PathStyle {
|
||||
Posix,
|
||||
Windows,
|
||||
}
|
||||
|
||||
impl PathStyle {
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const fn local() -> Self {
|
||||
PathStyle::Windows
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub const fn local() -> Self {
|
||||
PathStyle::Posix
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn primary_separator(&self) -> &'static str {
|
||||
match self {
|
||||
PathStyle::Posix => "/",
|
||||
PathStyle::Windows => "\\",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separators(&self) -> &'static [&'static str] {
|
||||
match self {
|
||||
PathStyle::Posix => &["/"],
|
||||
PathStyle::Windows => &["\\", "/"],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separators_ch(&self) -> &'static [char] {
|
||||
match self {
|
||||
PathStyle::Posix => &['/'],
|
||||
PathStyle::Windows => &['\\', '/'],
|
||||
}
|
||||
}
|
||||
|
||||
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_windows(&self) -> bool {
|
||||
*self == PathStyle::Windows
|
||||
}
|
||||
|
||||
pub fn is_posix(&self) -> bool {
|
||||
*self == PathStyle::Posix
|
||||
}
|
||||
|
||||
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) {
|
||||
return None;
|
||||
}
|
||||
let left = left.as_ref().to_str()?;
|
||||
if left.is_empty() {
|
||||
Some(right.into())
|
||||
} else {
|
||||
Some(format!(
|
||||
"{left}{}{right}",
|
||||
if left.ends_with(self.primary_separator()) {
|
||||
""
|
||||
} else {
|
||||
self.primary_separator()
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join_path(
|
||||
self,
|
||||
left: impl AsRef<Path>,
|
||||
right: impl AsRef<Path>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let left = left
|
||||
.as_ref()
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8"))?;
|
||||
let right = right.as_ref();
|
||||
let right_string = right
|
||||
.to_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Path contains invalid UTF-8"))?;
|
||||
let joined = self
|
||||
.join(left, right_string)
|
||||
.ok_or_else(|| anyhow::anyhow!("Path must be relative: {right:?}"))?;
|
||||
Ok(PathBuf::from(self.normalize(&joined)))
|
||||
}
|
||||
|
||||
pub fn normalize(self, path_like: &str) -> String {
|
||||
match self {
|
||||
PathStyle::Windows => crate::normalize_path(Path::new(path_like))
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
PathStyle::Posix => {
|
||||
let is_absolute = path_like.starts_with('/');
|
||||
let remainder = if is_absolute {
|
||||
path_like.trim_start_matches('/')
|
||||
} else {
|
||||
path_like
|
||||
};
|
||||
|
||||
let mut components = Vec::new();
|
||||
for component in remainder.split(self.separators_ch()) {
|
||||
match component {
|
||||
"" | "." => {}
|
||||
".." => {
|
||||
if components
|
||||
.last()
|
||||
.is_some_and(|component| *component != "..")
|
||||
{
|
||||
components.pop();
|
||||
} else if !is_absolute {
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
component => components.push(component),
|
||||
}
|
||||
}
|
||||
|
||||
let normalized = components.join(self.primary_separator());
|
||||
if is_absolute && normalized.is_empty() {
|
||||
"/".to_string()
|
||||
} else if is_absolute {
|
||||
format!("/{normalized}")
|
||||
} else {
|
||||
normalized
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(self, path_like: &str) -> (Option<&str>, &str) {
|
||||
let Some(pos) = path_like.rfind(self.primary_separator()) else {
|
||||
return (None, path_like);
|
||||
};
|
||||
let filename_start = pos + self.primary_separator().len();
|
||||
(
|
||||
Some(&path_like[..filename_start]),
|
||||
&path_like[filename_start..],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn strip_prefix<'a>(
|
||||
&self,
|
||||
child: &'a Path,
|
||||
parent: &'a Path,
|
||||
) -> Option<std::borrow::Cow<'a, RelPath>> {
|
||||
let parent = parent.to_str()?;
|
||||
if parent.is_empty() {
|
||||
return RelPath::new(child, *self).ok();
|
||||
}
|
||||
let parent = self
|
||||
.separators()
|
||||
.iter()
|
||||
.find_map(|sep| parent.strip_suffix(sep))
|
||||
.unwrap_or(parent);
|
||||
let child = child.to_str()?;
|
||||
|
||||
// Match behavior of std::path::Path, which is case-insensitive for drive letters (e.g., "C:" == "c:")
|
||||
let stripped = if self.is_windows()
|
||||
&& child.as_bytes().get(1) == Some(&b':')
|
||||
&& parent.as_bytes().get(1) == Some(&b':')
|
||||
&& child.as_bytes()[0].eq_ignore_ascii_case(&parent.as_bytes()[0])
|
||||
{
|
||||
child[2..].strip_prefix(&parent[2..])?
|
||||
} else {
|
||||
child.strip_prefix(parent)?
|
||||
};
|
||||
if let Some(relative) = self
|
||||
.separators()
|
||||
.iter()
|
||||
.find_map(|sep| stripped.strip_prefix(sep))
|
||||
{
|
||||
RelPath::new(relative.as_ref(), *self).ok()
|
||||
} else if stripped.is_empty() {
|
||||
Some(Cow::Borrowed(RelPath::empty()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemotePathBuf {
|
||||
style: PathStyle,
|
||||
|
|
@ -1538,7 +1346,7 @@ impl UrlExt for url::Url {
|
|||
str_len.saturating_sub(self.scheme().len() + 3)
|
||||
};
|
||||
return match source_path_style {
|
||||
PathStyle::Posix => {
|
||||
PathStyle::Unix => {
|
||||
file_url_segments_to_pathbuf_posix(estimated_capacity, host, segments)
|
||||
}
|
||||
PathStyle::Windows => {
|
||||
|
|
@ -1576,7 +1384,7 @@ impl UrlExt for url::Url {
|
|||
|
||||
let path = String::from_utf8(bytes).map_err(|_| ())?;
|
||||
debug_assert!(
|
||||
PathStyle::Posix.is_absolute(&path),
|
||||
PathStyle::Unix.is_absolute(&path),
|
||||
"to_file_path() failed to produce an absolute Path"
|
||||
);
|
||||
|
||||
|
|
@ -1659,7 +1467,7 @@ impl UrlExt for url::Url {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::rel_path::rel_path;
|
||||
use path::rel_path::rel_path;
|
||||
|
||||
use super::*;
|
||||
use util_macros::perf;
|
||||
|
|
@ -1676,7 +1484,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_join_path_uses_path_style_separator() {
|
||||
let posix_path = PathStyle::Posix
|
||||
let posix_path = PathStyle::Unix
|
||||
.join_path(Path::new("/home/user/dev"), "worktrees")
|
||||
.unwrap();
|
||||
let windows_path = PathStyle::Windows
|
||||
|
|
@ -1693,7 +1501,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_normalize_uses_path_style_separator() {
|
||||
assert_eq!(
|
||||
PathStyle::Posix.normalize("/home/user/dev/../worktrees/./zed"),
|
||||
PathStyle::Unix.normalize("/home/user/dev/../worktrees/./zed"),
|
||||
"/home/user/worktrees/zed"
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -1703,7 +1511,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn rel_path_entry(path: &'static str, is_file: bool) -> (&'static RelPath, bool) {
|
||||
(RelPath::unix(path).unwrap(), is_file)
|
||||
(RelPath::from_unix_str(path).unwrap(), is_file)
|
||||
}
|
||||
|
||||
fn sorted_rel_paths(
|
||||
|
|
@ -1838,22 +1646,22 @@ mod tests {
|
|||
fn compare_rel_paths_mixed_case_insensitive() {
|
||||
// Test that mixed mode is case-insensitive
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("zebra.txt").unwrap(), true),
|
||||
(RelPath::unix("Apple").unwrap(), false),
|
||||
(RelPath::unix("banana.rs").unwrap(), true),
|
||||
(RelPath::unix("Carrot").unwrap(), false),
|
||||
(RelPath::unix("aardvark.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("zebra.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("Apple").unwrap(), false),
|
||||
(RelPath::from_unix_str("banana.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("Carrot").unwrap(), false),
|
||||
(RelPath::from_unix_str("aardvark.txt").unwrap(), true),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
// Case-insensitive: aardvark < Apple < banana < Carrot < zebra
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("aardvark.txt").unwrap(), true),
|
||||
(RelPath::unix("Apple").unwrap(), false),
|
||||
(RelPath::unix("banana.rs").unwrap(), true),
|
||||
(RelPath::unix("Carrot").unwrap(), false),
|
||||
(RelPath::unix("zebra.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("aardvark.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("Apple").unwrap(), false),
|
||||
(RelPath::from_unix_str("banana.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("Carrot").unwrap(), false),
|
||||
(RelPath::from_unix_str("zebra.txt").unwrap(), true),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1862,11 +1670,11 @@ mod tests {
|
|||
fn compare_rel_paths_files_first_basic() {
|
||||
// Test that files come before directories
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("zebra.txt").unwrap(), true),
|
||||
(RelPath::unix("Apple").unwrap(), false),
|
||||
(RelPath::unix("banana.rs").unwrap(), true),
|
||||
(RelPath::unix("Carrot").unwrap(), false),
|
||||
(RelPath::unix("aardvark.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("zebra.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("Apple").unwrap(), false),
|
||||
(RelPath::from_unix_str("banana.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("Carrot").unwrap(), false),
|
||||
(RelPath::from_unix_str("aardvark.txt").unwrap(), true),
|
||||
];
|
||||
paths
|
||||
.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::FilesFirst, SortOrder::Default));
|
||||
|
|
@ -1874,11 +1682,11 @@ mod tests {
|
|||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("aardvark.txt").unwrap(), true),
|
||||
(RelPath::unix("banana.rs").unwrap(), true),
|
||||
(RelPath::unix("zebra.txt").unwrap(), true),
|
||||
(RelPath::unix("Apple").unwrap(), false),
|
||||
(RelPath::unix("Carrot").unwrap(), false),
|
||||
(RelPath::from_unix_str("aardvark.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("banana.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("zebra.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("Apple").unwrap(), false),
|
||||
(RelPath::from_unix_str("Carrot").unwrap(), false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1887,22 +1695,22 @@ mod tests {
|
|||
fn compare_rel_paths_files_first_case_insensitive() {
|
||||
// Test case-insensitive sorting within files and directories
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("Zebra.txt").unwrap(), true),
|
||||
(RelPath::unix("apple").unwrap(), false),
|
||||
(RelPath::unix("Banana.rs").unwrap(), true),
|
||||
(RelPath::unix("carrot").unwrap(), false),
|
||||
(RelPath::unix("Aardvark.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("Zebra.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("apple").unwrap(), false),
|
||||
(RelPath::from_unix_str("Banana.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("carrot").unwrap(), false),
|
||||
(RelPath::from_unix_str("Aardvark.txt").unwrap(), true),
|
||||
];
|
||||
paths
|
||||
.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::FilesFirst, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("Aardvark.txt").unwrap(), true),
|
||||
(RelPath::unix("Banana.rs").unwrap(), true),
|
||||
(RelPath::unix("Zebra.txt").unwrap(), true),
|
||||
(RelPath::unix("apple").unwrap(), false),
|
||||
(RelPath::unix("carrot").unwrap(), false),
|
||||
(RelPath::from_unix_str("Aardvark.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("Banana.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("Zebra.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("apple").unwrap(), false),
|
||||
(RelPath::from_unix_str("carrot").unwrap(), false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1911,22 +1719,22 @@ mod tests {
|
|||
fn compare_rel_paths_files_first_numeric() {
|
||||
// Test natural number sorting with files first
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("file10.txt").unwrap(), true),
|
||||
(RelPath::unix("dir2").unwrap(), false),
|
||||
(RelPath::unix("file2.txt").unwrap(), true),
|
||||
(RelPath::unix("dir10").unwrap(), false),
|
||||
(RelPath::unix("file1.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("file10.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("dir2").unwrap(), false),
|
||||
(RelPath::from_unix_str("file2.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("dir10").unwrap(), false),
|
||||
(RelPath::from_unix_str("file1.txt").unwrap(), true),
|
||||
];
|
||||
paths
|
||||
.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::FilesFirst, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("file1.txt").unwrap(), true),
|
||||
(RelPath::unix("file2.txt").unwrap(), true),
|
||||
(RelPath::unix("file10.txt").unwrap(), true),
|
||||
(RelPath::unix("dir2").unwrap(), false),
|
||||
(RelPath::unix("dir10").unwrap(), false),
|
||||
(RelPath::from_unix_str("file1.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("file2.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("file10.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("dir2").unwrap(), false),
|
||||
(RelPath::from_unix_str("dir10").unwrap(), false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1935,18 +1743,18 @@ mod tests {
|
|||
fn compare_rel_paths_mixed_case() {
|
||||
// Test case-insensitive sorting with varied capitalization
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("README.md").unwrap(), true),
|
||||
(RelPath::unix("readme.txt").unwrap(), true),
|
||||
(RelPath::unix("ReadMe.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("README.md").unwrap(), true),
|
||||
(RelPath::from_unix_str("readme.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("ReadMe.rs").unwrap(), true),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
// All "readme" variants should group together, sorted by extension
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("README.md").unwrap(), true),
|
||||
(RelPath::unix("ReadMe.rs").unwrap(), true),
|
||||
(RelPath::unix("readme.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("README.md").unwrap(), true),
|
||||
(RelPath::from_unix_str("ReadMe.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("readme.txt").unwrap(), true),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1955,20 +1763,20 @@ mod tests {
|
|||
fn compare_rel_paths_mixed_files_and_dirs() {
|
||||
// Verify directories and files are still mixed
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("file2.txt").unwrap(), true),
|
||||
(RelPath::unix("Dir1").unwrap(), false),
|
||||
(RelPath::unix("file1.txt").unwrap(), true),
|
||||
(RelPath::unix("dir2").unwrap(), false),
|
||||
(RelPath::from_unix_str("file2.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("Dir1").unwrap(), false),
|
||||
(RelPath::from_unix_str("file1.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("dir2").unwrap(), false),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
// Case-insensitive: dir1, dir2, file1, file2 (all mixed)
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("Dir1").unwrap(), false),
|
||||
(RelPath::unix("dir2").unwrap(), false),
|
||||
(RelPath::unix("file1.txt").unwrap(), true),
|
||||
(RelPath::unix("file2.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("Dir1").unwrap(), false),
|
||||
(RelPath::from_unix_str("dir2").unwrap(), false),
|
||||
(RelPath::from_unix_str("file1.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("file2.txt").unwrap(), true),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -1976,28 +1784,28 @@ mod tests {
|
|||
#[perf]
|
||||
fn compare_rel_paths_mixed_same_name_different_case_file_and_dir() {
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("Hello.txt").unwrap(), true),
|
||||
(RelPath::unix("hello").unwrap(), false),
|
||||
(RelPath::from_unix_str("Hello.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("hello").unwrap(), false),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("hello").unwrap(), false),
|
||||
(RelPath::unix("Hello.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("hello").unwrap(), false),
|
||||
(RelPath::from_unix_str("Hello.txt").unwrap(), true),
|
||||
]
|
||||
);
|
||||
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("hello").unwrap(), false),
|
||||
(RelPath::unix("Hello.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("hello").unwrap(), false),
|
||||
(RelPath::from_unix_str("Hello.txt").unwrap(), true),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("hello").unwrap(), false),
|
||||
(RelPath::unix("Hello.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("hello").unwrap(), false),
|
||||
(RelPath::from_unix_str("Hello.txt").unwrap(), true),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -2006,19 +1814,19 @@ mod tests {
|
|||
fn compare_rel_paths_mixed_with_nested_paths() {
|
||||
// Test that nested paths still work correctly
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("src/main.rs").unwrap(), true),
|
||||
(RelPath::unix("Cargo.toml").unwrap(), true),
|
||||
(RelPath::unix("src").unwrap(), false),
|
||||
(RelPath::unix("target").unwrap(), false),
|
||||
(RelPath::from_unix_str("src/main.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("Cargo.toml").unwrap(), true),
|
||||
(RelPath::from_unix_str("src").unwrap(), false),
|
||||
(RelPath::from_unix_str("target").unwrap(), false),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("Cargo.toml").unwrap(), true),
|
||||
(RelPath::unix("src").unwrap(), false),
|
||||
(RelPath::unix("src/main.rs").unwrap(), true),
|
||||
(RelPath::unix("target").unwrap(), false),
|
||||
(RelPath::from_unix_str("Cargo.toml").unwrap(), true),
|
||||
(RelPath::from_unix_str("src").unwrap(), false),
|
||||
(RelPath::from_unix_str("src/main.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("target").unwrap(), false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -2027,20 +1835,20 @@ mod tests {
|
|||
fn compare_rel_paths_files_first_with_nested() {
|
||||
// Files come before directories, even with nested paths
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("src/lib.rs").unwrap(), true),
|
||||
(RelPath::unix("README.md").unwrap(), true),
|
||||
(RelPath::unix("src").unwrap(), false),
|
||||
(RelPath::unix("tests").unwrap(), false),
|
||||
(RelPath::from_unix_str("src/lib.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("README.md").unwrap(), true),
|
||||
(RelPath::from_unix_str("src").unwrap(), false),
|
||||
(RelPath::from_unix_str("tests").unwrap(), false),
|
||||
];
|
||||
paths
|
||||
.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::FilesFirst, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("README.md").unwrap(), true),
|
||||
(RelPath::unix("src").unwrap(), false),
|
||||
(RelPath::unix("src/lib.rs").unwrap(), true),
|
||||
(RelPath::unix("tests").unwrap(), false),
|
||||
(RelPath::from_unix_str("README.md").unwrap(), true),
|
||||
(RelPath::from_unix_str("src").unwrap(), false),
|
||||
(RelPath::from_unix_str("src/lib.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("tests").unwrap(), false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -2049,19 +1857,19 @@ mod tests {
|
|||
fn compare_rel_paths_mixed_dotfiles() {
|
||||
// Test that dotfiles are handled correctly in mixed mode
|
||||
let mut paths = vec![
|
||||
(RelPath::unix(".gitignore").unwrap(), true),
|
||||
(RelPath::unix("README.md").unwrap(), true),
|
||||
(RelPath::unix(".github").unwrap(), false),
|
||||
(RelPath::unix("src").unwrap(), false),
|
||||
(RelPath::from_unix_str(".gitignore").unwrap(), true),
|
||||
(RelPath::from_unix_str("README.md").unwrap(), true),
|
||||
(RelPath::from_unix_str(".github").unwrap(), false),
|
||||
(RelPath::from_unix_str("src").unwrap(), false),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix(".github").unwrap(), false),
|
||||
(RelPath::unix(".gitignore").unwrap(), true),
|
||||
(RelPath::unix("README.md").unwrap(), true),
|
||||
(RelPath::unix("src").unwrap(), false),
|
||||
(RelPath::from_unix_str(".github").unwrap(), false),
|
||||
(RelPath::from_unix_str(".gitignore").unwrap(), true),
|
||||
(RelPath::from_unix_str("README.md").unwrap(), true),
|
||||
(RelPath::from_unix_str("src").unwrap(), false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -2070,20 +1878,20 @@ mod tests {
|
|||
fn compare_rel_paths_files_first_dotfiles() {
|
||||
// Test that dotfiles come first when they're files
|
||||
let mut paths = vec![
|
||||
(RelPath::unix(".gitignore").unwrap(), true),
|
||||
(RelPath::unix("README.md").unwrap(), true),
|
||||
(RelPath::unix(".github").unwrap(), false),
|
||||
(RelPath::unix("src").unwrap(), false),
|
||||
(RelPath::from_unix_str(".gitignore").unwrap(), true),
|
||||
(RelPath::from_unix_str("README.md").unwrap(), true),
|
||||
(RelPath::from_unix_str(".github").unwrap(), false),
|
||||
(RelPath::from_unix_str("src").unwrap(), false),
|
||||
];
|
||||
paths
|
||||
.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::FilesFirst, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix(".gitignore").unwrap(), true),
|
||||
(RelPath::unix("README.md").unwrap(), true),
|
||||
(RelPath::unix(".github").unwrap(), false),
|
||||
(RelPath::unix("src").unwrap(), false),
|
||||
(RelPath::from_unix_str(".gitignore").unwrap(), true),
|
||||
(RelPath::from_unix_str("README.md").unwrap(), true),
|
||||
(RelPath::from_unix_str(".github").unwrap(), false),
|
||||
(RelPath::from_unix_str("src").unwrap(), false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -2092,17 +1900,17 @@ mod tests {
|
|||
fn compare_rel_paths_mixed_same_stem_different_extension() {
|
||||
// Files with same stem but different extensions should sort by extension
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("file.rs").unwrap(), true),
|
||||
(RelPath::unix("file.md").unwrap(), true),
|
||||
(RelPath::unix("file.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("file.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("file.md").unwrap(), true),
|
||||
(RelPath::from_unix_str("file.txt").unwrap(), true),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("file.md").unwrap(), true),
|
||||
(RelPath::unix("file.rs").unwrap(), true),
|
||||
(RelPath::unix("file.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("file.md").unwrap(), true),
|
||||
(RelPath::from_unix_str("file.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("file.txt").unwrap(), true),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -2111,18 +1919,18 @@ mod tests {
|
|||
fn compare_rel_paths_files_first_same_stem() {
|
||||
// Same stem files should still sort by extension with files_first
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("main.rs").unwrap(), true),
|
||||
(RelPath::unix("main.c").unwrap(), true),
|
||||
(RelPath::unix("main").unwrap(), false),
|
||||
(RelPath::from_unix_str("main.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("main.c").unwrap(), true),
|
||||
(RelPath::from_unix_str("main").unwrap(), false),
|
||||
];
|
||||
paths
|
||||
.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::FilesFirst, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("main.c").unwrap(), true),
|
||||
(RelPath::unix("main.rs").unwrap(), true),
|
||||
(RelPath::unix("main").unwrap(), false),
|
||||
(RelPath::from_unix_str("main.c").unwrap(), true),
|
||||
(RelPath::from_unix_str("main.rs").unwrap(), true),
|
||||
(RelPath::from_unix_str("main").unwrap(), false),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -2131,19 +1939,19 @@ mod tests {
|
|||
fn compare_rel_paths_mixed_deep_nesting() {
|
||||
// Test sorting with deeply nested paths
|
||||
let mut paths = vec![
|
||||
(RelPath::unix("a/b/c.txt").unwrap(), true),
|
||||
(RelPath::unix("A/B.txt").unwrap(), true),
|
||||
(RelPath::unix("a.txt").unwrap(), true),
|
||||
(RelPath::unix("A.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("a/b/c.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("A/B.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("a.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("A.txt").unwrap(), true),
|
||||
];
|
||||
paths.sort_by(|&a, &b| compare_rel_paths_by(a, b, SortMode::Mixed, SortOrder::Default));
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec![
|
||||
(RelPath::unix("a/b/c.txt").unwrap(), true),
|
||||
(RelPath::unix("A/B.txt").unwrap(), true),
|
||||
(RelPath::unix("a.txt").unwrap(), true),
|
||||
(RelPath::unix("A.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("a/b/c.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("A/B.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("a.txt").unwrap(), true),
|
||||
(RelPath::from_unix_str("A.txt").unwrap(), true),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -2813,7 +2621,7 @@ mod tests {
|
|||
// fn edge_of_glob() {
|
||||
// let path = Path::new("/work/node_modules");
|
||||
// let path_matcher =
|
||||
// PathMatcher::new(&["**/node_modules/**".to_owned()], PathStyle::Posix).unwrap();
|
||||
// PathMatcher::new(&["**/node_modules/**".to_owned()], PathStyle::Unix).unwrap();
|
||||
// assert!(
|
||||
// path_matcher.is_match(path),
|
||||
// "Path matcher should match {path:?}"
|
||||
|
|
@ -2823,7 +2631,7 @@ mod tests {
|
|||
// #[perf]
|
||||
// fn file_in_dirs() {
|
||||
// let path = Path::new("/work/.env");
|
||||
// let path_matcher = PathMatcher::new(&["**/.env".to_owned()], PathStyle::Posix).unwrap();
|
||||
// let path_matcher = PathMatcher::new(&["**/.env".to_owned()], PathStyle::Unix).unwrap();
|
||||
// assert!(
|
||||
// path_matcher.is_match(path),
|
||||
// "Path matcher should match {path:?}"
|
||||
|
|
@ -2839,7 +2647,7 @@ mod tests {
|
|||
// fn project_search() {
|
||||
// let path = Path::new("/Users/someonetoignore/work/zed/zed.dev/node_modules");
|
||||
// let path_matcher =
|
||||
// PathMatcher::new(&["**/node_modules/**".to_owned()], PathStyle::Posix).unwrap();
|
||||
// PathMatcher::new(&["**/node_modules/**".to_owned()], PathStyle::Unix).unwrap();
|
||||
// assert!(
|
||||
// path_matcher.is_match(path),
|
||||
// "Path matcher should match {path:?}"
|
||||
|
|
@ -3264,28 +3072,28 @@ mod tests {
|
|||
fn test_strip_prefix() {
|
||||
let expected = [
|
||||
(
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/a/b/c",
|
||||
"/a/b",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/a/b/c",
|
||||
"/a/b/",
|
||||
Some(rel_path("c").into_arc()),
|
||||
),
|
||||
(
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/a/b/c",
|
||||
"/",
|
||||
Some(rel_path("a/b/c").into_arc()),
|
||||
),
|
||||
(PathStyle::Posix, "/a/b/c", "", None),
|
||||
(PathStyle::Posix, "/a/b//c", "/a/b/", None),
|
||||
(PathStyle::Posix, "/a/bc", "/a/b", None),
|
||||
(PathStyle::Unix, "/a/b/c", "", None),
|
||||
(PathStyle::Unix, "/a/b//c", "/a/b/", None),
|
||||
(PathStyle::Unix, "/a/bc", "/a/b", None),
|
||||
(
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
"/a/b/c",
|
||||
"/a/b/c",
|
||||
Some(rel_path("").into_arc()),
|
||||
|
|
@ -3390,19 +3198,19 @@ mod tests {
|
|||
|
||||
let url = url::Url::parse("file:///home/user/file.txt").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/home/user/file.txt"))
|
||||
);
|
||||
|
||||
let url = url::Url::parse("file:///").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/"))
|
||||
);
|
||||
|
||||
let url = url::Url::parse("file:///a/b/c/d/e").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/a/b/c/d/e"))
|
||||
);
|
||||
}
|
||||
|
|
@ -3413,19 +3221,19 @@ mod tests {
|
|||
|
||||
let url = url::Url::parse("file:///home/user/file%20with%20spaces.txt").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/home/user/file with spaces.txt"))
|
||||
);
|
||||
|
||||
let url = url::Url::parse("file:///path%2Fwith%2Fencoded%2Fslashes").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/path/with/encoded/slashes"))
|
||||
);
|
||||
|
||||
let url = url::Url::parse("file:///special%23chars%3F.txt").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/special#chars?.txt"))
|
||||
);
|
||||
}
|
||||
|
|
@ -3436,7 +3244,7 @@ mod tests {
|
|||
|
||||
let url = url::Url::parse("file://localhost/home/user/file.txt").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/home/user/file.txt"))
|
||||
);
|
||||
}
|
||||
|
|
@ -3446,7 +3254,7 @@ mod tests {
|
|||
use super::UrlExt;
|
||||
|
||||
let url = url::Url::parse("file://somehost/home/user/file.txt").unwrap();
|
||||
assert_eq!(url.to_file_path_ext(PathStyle::Posix), Err(()));
|
||||
assert_eq!(url.to_file_path_ext(PathStyle::Unix), Err(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -3455,13 +3263,13 @@ mod tests {
|
|||
|
||||
let url = url::Url::parse("file:///C:").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/C:/"))
|
||||
);
|
||||
|
||||
let url = url::Url::parse("file:///D|").unwrap();
|
||||
assert_eq!(
|
||||
url.to_file_path_ext(PathStyle::Posix),
|
||||
url.to_file_path_ext(PathStyle::Unix),
|
||||
Ok(PathBuf::from("/D|/"))
|
||||
);
|
||||
}
|
||||
|
|
@ -3574,11 +3382,11 @@ mod tests {
|
|||
use super::UrlExt;
|
||||
|
||||
let url = url::Url::parse("http://example.com/path").unwrap();
|
||||
assert_eq!(url.to_file_path_ext(PathStyle::Posix), Err(()));
|
||||
assert_eq!(url.to_file_path_ext(PathStyle::Unix), Err(()));
|
||||
assert_eq!(url.to_file_path_ext(PathStyle::Windows), Err(()));
|
||||
|
||||
let url = url::Url::parse("https://example.com/path").unwrap();
|
||||
assert_eq!(url.to_file_path_ext(PathStyle::Posix), Err(()));
|
||||
assert_eq!(url.to_file_path_ext(PathStyle::Unix), Err(()));
|
||||
assert_eq!(url.to_file_path_ext(PathStyle::Windows), Err(()));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ pub mod path_list;
|
|||
pub mod paths;
|
||||
pub mod process;
|
||||
pub mod redact;
|
||||
pub mod rel_path;
|
||||
pub mod schemars;
|
||||
pub mod serde;
|
||||
pub mod shell;
|
||||
|
|
@ -21,7 +20,7 @@ pub mod time;
|
|||
use anyhow::Result;
|
||||
use itertools::Either;
|
||||
use regex::Regex;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::LazyLock;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
|
@ -31,6 +30,9 @@ use std::{
|
|||
use unicase::UniCase;
|
||||
|
||||
pub use gpui_util::*;
|
||||
pub use path::PathExt;
|
||||
pub use path::normalize_path;
|
||||
pub use path::rel_path;
|
||||
|
||||
pub use take_until::*;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
|
@ -781,36 +783,6 @@ impl<O> From<anyhow::Result<O>> for ConnectionResult<O> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Normalizes a path by resolving `.` and `..` components without
|
||||
/// requiring the path to exist on disk (unlike `canonicalize`).
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
use std::path::Component;
|
||||
let mut components = path.components().peekable();
|
||||
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
|
||||
components.next();
|
||||
PathBuf::from(c.as_os_str())
|
||||
} else {
|
||||
PathBuf::new()
|
||||
};
|
||||
|
||||
for component in components {
|
||||
match component {
|
||||
Component::Prefix(..) => unreachable!(),
|
||||
Component::RootDir => {
|
||||
ret.push(component.as_os_str());
|
||||
}
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
ret.pop();
|
||||
}
|
||||
Component::Normal(c) => {
|
||||
ret.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -9110,11 +9110,11 @@ mod tests {
|
|||
path: util::rel_path::rel_path("dir/__init__.py").into(),
|
||||
};
|
||||
assert_eq!(
|
||||
dirty_message_for(Some(project_path), PathStyle::Posix),
|
||||
dirty_message_for(Some(project_path), PathStyle::Unix),
|
||||
"`dir/__init__.py` contains unsaved edits. Do you want to save it?"
|
||||
);
|
||||
assert_eq!(
|
||||
dirty_message_for(None, PathStyle::Posix),
|
||||
dirty_message_for(None, PathStyle::Unix),
|
||||
"This buffer contains unsaved edits. Do you want to save it?"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1435,7 +1435,8 @@ impl WorkspaceDb {
|
|||
relative_worktree_path == String::default()
|
||||
);
|
||||
|
||||
let Some(relative_path) = RelPath::unix(&relative_worktree_path).log_err() else {
|
||||
let Some(relative_path) = RelPath::from_unix_str(&relative_worktree_path).log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if worktree_root_path != String::default()
|
||||
|
|
@ -2496,7 +2497,7 @@ impl WorkspaceDb {
|
|||
as_json: serde_json::Value::from_str(&json).ok()?,
|
||||
},
|
||||
Arc::from(worktree_root_path.as_ref()),
|
||||
RelPath::from_proto(&relative_worktree_path).log_err()?,
|
||||
RelPath::from_unix_str(&relative_worktree_path).log_err()?.into(),
|
||||
))
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -534,7 +534,7 @@ mod tests {
|
|||
#[test]
|
||||
fn accepts_ancestor_or_equal() {
|
||||
let project = Path::new("/Users/me/dev/delta/wt/t1");
|
||||
let style = PathStyle::Posix;
|
||||
let style = PathStyle::Unix;
|
||||
assert_eq!(
|
||||
validate_trust_scope("/Users/me/dev/delta/wt", project, None, style).unwrap(),
|
||||
PathBuf::from("/Users/me/dev/delta/wt"),
|
||||
|
|
@ -551,7 +551,7 @@ mod tests {
|
|||
#[test]
|
||||
fn rejects_non_ancestor_relative_or_empty() {
|
||||
let project = Path::new("/Users/me/dev/delta/wt/t1");
|
||||
let style = PathStyle::Posix;
|
||||
let style = PathStyle::Unix;
|
||||
assert!(validate_trust_scope("/Users/other", project, None, style).is_err());
|
||||
assert!(validate_trust_scope("relative/path", project, None, style).is_err());
|
||||
assert!(validate_trust_scope(" ", project, None, style).is_err());
|
||||
|
|
@ -565,7 +565,7 @@ mod tests {
|
|||
fn expands_leading_tilde() {
|
||||
let home = Path::new("/Users/me");
|
||||
let project = Path::new("/Users/me/dev/wt/t1");
|
||||
let style = PathStyle::Posix;
|
||||
let style = PathStyle::Unix;
|
||||
assert_eq!(
|
||||
validate_trust_scope("~/dev/wt", project, Some(home), style).unwrap(),
|
||||
PathBuf::from("/Users/me/dev/wt"),
|
||||
|
|
|
|||
|
|
@ -498,7 +498,9 @@ impl Worktree {
|
|||
abs_path
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.map_or(RelPath::empty_arc(), |f| RelPath::unix(f).unwrap().into()),
|
||||
.map_or(RelPath::empty_arc(), |f| {
|
||||
RelPath::from_unix_str(f).unwrap().into()
|
||||
}),
|
||||
abs_path.clone(),
|
||||
PathStyle::local(),
|
||||
),
|
||||
|
|
@ -541,7 +543,7 @@ impl Worktree {
|
|||
} else {
|
||||
if let Some(file_name) = abs_path.file_name()
|
||||
&& let Some(file_name) = file_name.to_str()
|
||||
&& let Ok(path) = RelPath::unix(file_name)
|
||||
&& let Ok(path) = RelPath::from_unix_str(file_name)
|
||||
{
|
||||
entry.is_private = !share_private_files && settings.is_path_private(path);
|
||||
entry.is_hidden = settings.is_path_hidden(path);
|
||||
|
|
@ -586,7 +588,8 @@ impl Worktree {
|
|||
cx.new(|cx: &mut Context<Self>| {
|
||||
let mut snapshot = Snapshot::new(
|
||||
WorktreeId::from_proto(worktree.id),
|
||||
RelPath::from_proto(&worktree.root_name).unwrap_or_else(|_| RelPath::empty_arc()),
|
||||
RelPath::from_unix_str(&worktree.root_name)
|
||||
.map_or_else(|_| RelPath::empty_arc(), Into::into),
|
||||
Path::new(&worktree.abs_path).into(),
|
||||
path_style,
|
||||
);
|
||||
|
|
@ -681,9 +684,11 @@ impl Worktree {
|
|||
for entry in &update.updated_entries {
|
||||
// Remote updates don't distinguish creation from
|
||||
// modification, so report `AddedOrUpdated`.
|
||||
if let Some(path) = RelPath::from_proto(&entry.path).log_err() {
|
||||
if let Some(path) =
|
||||
RelPath::from_unix_str(&entry.path).log_err()
|
||||
{
|
||||
changed_entries.push((
|
||||
path,
|
||||
path.into(),
|
||||
ProjectEntryId::from_proto(entry.id),
|
||||
PathChange::AddedOrUpdated,
|
||||
));
|
||||
|
|
@ -792,7 +797,7 @@ impl Worktree {
|
|||
pub fn metadata_proto(&self) -> proto::WorktreeMetadata {
|
||||
proto::WorktreeMetadata {
|
||||
id: self.id().to_proto(),
|
||||
root_name: self.root_name().to_proto(),
|
||||
root_name: self.root_name().as_unix_str().to_owned(),
|
||||
visible: self.is_visible(),
|
||||
abs_path: self.abs_path().to_string_lossy().into_owned(),
|
||||
root_repo_common_dir: self
|
||||
|
|
@ -931,7 +936,7 @@ impl Worktree {
|
|||
let request = this.client.request(proto::CreateProjectEntry {
|
||||
worktree_id: worktree_id.to_proto(),
|
||||
project_id,
|
||||
path: path.as_ref().to_proto(),
|
||||
path: path.as_ref().as_unix_str().to_owned(),
|
||||
content,
|
||||
is_directory,
|
||||
});
|
||||
|
|
@ -1094,9 +1099,11 @@ impl Worktree {
|
|||
anyhow::Ok((
|
||||
this.scan_id(),
|
||||
this.create_entry(
|
||||
RelPath::from_proto(&request.path).with_context(|| {
|
||||
format!("received invalid relative path {:?}", request.path)
|
||||
})?,
|
||||
RelPath::from_unix_str(&request.path)
|
||||
.with_context(|| {
|
||||
format!("received invalid relative path {:?}", request.path)
|
||||
})?
|
||||
.into(),
|
||||
request.is_directory,
|
||||
request.content,
|
||||
cx,
|
||||
|
|
@ -1696,7 +1703,7 @@ impl LocalWorktree {
|
|||
let refresh_full_path = lowest_ancestor.join(refresh_path);
|
||||
|
||||
refreshes.push(this.as_local_mut().unwrap().refresh_entry(
|
||||
refresh_full_path,
|
||||
refresh_full_path.into(),
|
||||
None,
|
||||
cx,
|
||||
));
|
||||
|
|
@ -2159,7 +2166,9 @@ impl LocalWorktree {
|
|||
.as_path()
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.map_or(RelPath::empty_arc(), |f| RelPath::unix(f).unwrap().into());
|
||||
.map_or(RelPath::empty_arc(), |f| {
|
||||
RelPath::from_unix_str(f).unwrap().into()
|
||||
});
|
||||
self.snapshot.update_abs_path(new_path, root_name);
|
||||
self.restart_background_scanners(cx);
|
||||
}
|
||||
|
|
@ -2372,7 +2381,7 @@ impl RemoteWorktree {
|
|||
let Some(filename) = root_path_to_copy
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.and_then(|filename| RelPath::unix(filename).ok())
|
||||
.and_then(|filename| RelPath::from_unix_str(filename).ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -2401,7 +2410,7 @@ impl RemoteWorktree {
|
|||
requests.push(proto::CreateProjectEntry {
|
||||
project_id,
|
||||
worktree_id,
|
||||
path: target_path.to_proto(),
|
||||
path: target_path.as_unix_str().to_owned(),
|
||||
is_directory,
|
||||
content,
|
||||
});
|
||||
|
|
@ -2490,7 +2499,7 @@ impl Snapshot {
|
|||
project_id,
|
||||
worktree_id,
|
||||
abs_path: self.abs_path().to_string_lossy().into_owned(),
|
||||
root_name: self.root_name().to_proto(),
|
||||
root_name: self.root_name().as_unix_str().to_owned(),
|
||||
root_repo_common_dir: self
|
||||
.root_repo_common_dir()
|
||||
.map(|p| p.to_string_lossy().into_owned()),
|
||||
|
|
@ -2596,10 +2605,10 @@ impl Snapshot {
|
|||
update.updated_entries.len(),
|
||||
update.removed_entries.len()
|
||||
);
|
||||
if let Some(root_name) = RelPath::from_proto(&update.root_name).log_err() {
|
||||
if let Some(root_name) = RelPath::from_unix_str(&update.root_name).log_err() {
|
||||
self.update_abs_path(
|
||||
SanitizedPath::new_arc(&Path::new(&update.abs_path)),
|
||||
root_name,
|
||||
root_name.into(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -2894,7 +2903,7 @@ impl LocalSnapshot {
|
|||
project_id,
|
||||
worktree_id,
|
||||
abs_path: self.abs_path().to_string_lossy().into_owned(),
|
||||
root_name: self.root_name().to_proto(),
|
||||
root_name: self.root_name().as_unix_str().to_owned(),
|
||||
root_repo_common_dir: self
|
||||
.root_repo_common_dir()
|
||||
.map(|p| p.to_string_lossy().into_owned()),
|
||||
|
|
@ -3101,7 +3110,7 @@ impl LocalSnapshot {
|
|||
assert!(self.entry_for_path(ignore_parent_path).is_some());
|
||||
assert!(
|
||||
self.entry_for_path(
|
||||
&ignore_parent_path.join(RelPath::unix(GITIGNORE).unwrap())
|
||||
&ignore_parent_path.join(RelPath::from_unix_str(GITIGNORE).unwrap())
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
|
|
@ -3693,7 +3702,7 @@ impl language::File for File {
|
|||
rpc::proto::File {
|
||||
worktree_id: self.worktree.read(cx).id().to_proto(),
|
||||
entry_id: self.entry_id.map(|id| id.to_proto()),
|
||||
path: self.path.as_ref().to_proto(),
|
||||
path: self.path.as_ref().as_unix_str().to_owned(),
|
||||
mtime: self.disk_state.mtime().map(|time| time.into()),
|
||||
is_deleted: self.disk_state.is_deleted(),
|
||||
is_historic: matches!(self.disk_state, DiskState::Historic { .. }),
|
||||
|
|
@ -3778,7 +3787,9 @@ impl File {
|
|||
|
||||
Ok(Self {
|
||||
worktree,
|
||||
path: RelPath::from_proto(&proto.path).context("invalid path in file protobuf")?,
|
||||
path: RelPath::from_unix_str(&proto.path)
|
||||
.context("invalid path in file protobuf")?
|
||||
.into(),
|
||||
disk_state,
|
||||
entry_id: proto.entry_id.map(ProjectEntryId::from_proto),
|
||||
is_local: false,
|
||||
|
|
@ -5152,10 +5163,11 @@ impl BackgroundScanner {
|
|||
let child_name = child_abs_path.file_name().unwrap();
|
||||
let Some(child_path) = child_name
|
||||
.to_str()
|
||||
.and_then(|name| Some(job.path.join(RelPath::unix(name).ok()?)))
|
||||
.and_then(|name| Some(job.path.join(RelPath::from_unix_str(name).ok()?)))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let child_path: Arc<RelPath> = child_path.into();
|
||||
|
||||
if self.track_git_repositories {
|
||||
if child_name == DOT_GIT {
|
||||
|
|
@ -5295,7 +5307,7 @@ impl BackgroundScanner {
|
|||
{
|
||||
let relative_path = job
|
||||
.path
|
||||
.join(RelPath::unix(child_name.to_str().unwrap()).unwrap());
|
||||
.join(RelPath::from_unix_str(child_name.to_str().unwrap()).unwrap());
|
||||
if self.is_path_private(&relative_path) {
|
||||
log::debug!("detected private file: {relative_path:?}");
|
||||
child_entry.is_private = true;
|
||||
|
|
@ -5677,7 +5689,8 @@ impl BackgroundScanner {
|
|||
}
|
||||
}
|
||||
|
||||
let ignore_path = parent_path.join(RelPath::unix(GITIGNORE).unwrap());
|
||||
let ignore_path =
|
||||
parent_path.join(RelPath::from_unix_str(GITIGNORE).unwrap());
|
||||
if snapshot.snapshot.entry_for_path(&ignore_path).is_none() {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -5904,7 +5917,9 @@ impl BackgroundScanner {
|
|||
.entry_for_id(work_directory_id)
|
||||
.is_some_and(|entry| {
|
||||
snapshot
|
||||
.entry_for_path(&entry.path.join(RelPath::unix(DOT_GIT).unwrap()))
|
||||
.entry_for_path(
|
||||
&entry.path.join(RelPath::from_unix_str(DOT_GIT).unwrap()),
|
||||
)
|
||||
.is_some()
|
||||
});
|
||||
|
||||
|
|
@ -6284,7 +6299,7 @@ impl WorktreeModelHandle for Entity<Worktree> {
|
|||
// Check if condition is already met before waiting for events
|
||||
let file_exists = || {
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.entry_for_path(RelPath::unix(file_name).unwrap())
|
||||
tree.entry_for_path(RelPath::from_unix_str(file_name).unwrap())
|
||||
.is_some()
|
||||
})
|
||||
};
|
||||
|
|
@ -6304,7 +6319,7 @@ impl WorktreeModelHandle for Entity<Worktree> {
|
|||
// Check if condition is already met before waiting for events
|
||||
let file_gone = || {
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.entry_for_path(RelPath::unix(file_name).unwrap())
|
||||
tree.entry_for_path(RelPath::from_unix_str(file_name).unwrap())
|
||||
.is_none()
|
||||
})
|
||||
};
|
||||
|
|
@ -6671,7 +6686,7 @@ impl<'a> From<&'a Entry> for proto::Entry {
|
|||
Self {
|
||||
id: entry.id.to_proto(),
|
||||
is_dir: entry.is_dir(),
|
||||
path: entry.path.as_ref().to_proto(),
|
||||
path: entry.path.as_ref().as_unix_str().to_owned(),
|
||||
inode: entry.inode,
|
||||
mtime: entry.mtime.map(|time| time.into()),
|
||||
is_ignored: entry.is_ignored,
|
||||
|
|
@ -6699,14 +6714,14 @@ impl TryFrom<(&CharBag, &PathMatcher, proto::Entry)> for Entry {
|
|||
EntryKind::File
|
||||
};
|
||||
|
||||
let path =
|
||||
RelPath::from_proto(&entry.path).context("invalid relative path in proto message")?;
|
||||
let path = RelPath::from_unix_str(&entry.path)
|
||||
.context("invalid relative path in proto message")?;
|
||||
let char_bag = char_bag_for_path(*root_char_bag, &path);
|
||||
let is_always_included = always_included.is_match(&path);
|
||||
Ok(Entry {
|
||||
id: ProjectEntryId::from_proto(entry.id),
|
||||
kind,
|
||||
path,
|
||||
path: path.into(),
|
||||
inode: entry.inode,
|
||||
mtime: entry.mtime.map(|time| time.into()),
|
||||
size: entry.size.unwrap_or(0),
|
||||
|
|
|
|||
|
|
@ -3224,7 +3224,7 @@ fn randomly_mutate_worktree(
|
|||
if is_dir { "dir" } else { "file" },
|
||||
child_path,
|
||||
);
|
||||
let task = worktree.create_entry(child_path, is_dir, None, cx);
|
||||
let task = worktree.create_entry(child_path.into(), is_dir, None, cx);
|
||||
cx.background_spawn(async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
|
|
@ -5190,7 +5190,7 @@ async fn test_remote_worktree_without_git_emits_root_repo_event_after_first_upda
|
|||
root_repo_is_linked_worktree: false,
|
||||
},
|
||||
client,
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
|
@ -5287,7 +5287,7 @@ async fn test_remote_worktree_with_git_emits_root_repo_event_when_repo_info_arri
|
|||
root_repo_is_linked_worktree: false,
|
||||
},
|
||||
client,
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
|
@ -5386,7 +5386,7 @@ async fn test_remote_worktree_root_repo_metadata_cleared_only_by_completed_scan(
|
|||
root_repo_is_linked_worktree: true,
|
||||
},
|
||||
client,
|
||||
PathStyle::Posix,
|
||||
PathStyle::Unix,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue