diff --git a/Cargo.lock b/Cargo.lock index 9e1354c40d..f5c45f8d4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3752,6 +3752,12 @@ dependencies = [ "phf", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dwrote" version = "0.11.2" @@ -13689,6 +13695,7 @@ dependencies = [ "async-fs 1.6.0", "collections", "dirs 4.0.0", + "dunce", "futures 0.3.31", "futures-lite 1.13.0", "git2", diff --git a/Cargo.toml b/Cargo.toml index 7c141a1b6c..71701dd8f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -228,7 +228,9 @@ git = { path = "crates/git" } git_hosting_providers = { path = "crates/git_hosting_providers" } go_to_line = { path = "crates/go_to_line" } google_ai = { path = "crates/google_ai" } -gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]} +gpui = { path = "crates/gpui", default-features = false, features = [ + "http_client", +] } gpui_macros = { path = "crates/gpui_macros" } html_to_markdown = { path = "crates/html_to_markdown" } http_client = { path = "crates/http_client" } @@ -403,10 +405,10 @@ parking_lot = "0.12.1" pathdiff = "0.2" pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } -pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } -pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } -pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } -pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } +pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } +pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } +pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } +pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" } postage = { version = "0.5", features = ["futures-traits"] } pretty_assertions = { version = "1.3.0", features = ["unstable"] } profiling = "1" diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index fc0fae3fe8..37525db7d9 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -452,18 +452,16 @@ impl Fs for RealFs { #[cfg(target_os = "windows")] async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> { + use util::paths::SanitizedPath; use windows::{ core::HSTRING, Storage::{StorageDeleteOption, StorageFile}, }; // todo(windows) // When new version of `windows-rs` release, make this operation `async` - let path = path.canonicalize()?.to_string_lossy().to_string(); - let path_str = path.trim_start_matches("\\\\?\\"); - if path_str.is_empty() { - anyhow::bail!("File path is empty!"); - } - let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path_str))?.get()?; + let path = SanitizedPath::from(path.canonicalize()?); + let path_string = path.to_string(); + let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(path_string))?.get()?; file.DeleteAsync(StorageDeleteOption::Default)?.get()?; Ok(()) } @@ -480,19 +478,17 @@ impl Fs for RealFs { #[cfg(target_os = "windows")] async fn trash_dir(&self, path: &Path, _options: RemoveOptions) -> Result<()> { + use util::paths::SanitizedPath; use windows::{ core::HSTRING, Storage::{StorageDeleteOption, StorageFolder}, }; - let path = path.canonicalize()?.to_string_lossy().to_string(); - let path_str = path.trim_start_matches("\\\\?\\"); - if path_str.is_empty() { - anyhow::bail!("Folder path is empty!"); - } // todo(windows) // When new version of `windows-rs` release, make this operation `async` - let folder = StorageFolder::GetFolderFromPathAsync(&HSTRING::from(path_str))?.get()?; + let path = SanitizedPath::from(path.canonicalize()?); + let path_string = path.to_string(); + let folder = StorageFolder::GetFolderFromPathAsync(&HSTRING::from(path_string))?.get()?; folder.DeleteAsync(StorageDeleteOption::Default)?.get()?; Ok(()) } diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 91e9816106..389b90765d 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -6,7 +6,7 @@ use std::{ sync::Arc, }; -use ::util::ResultExt; +use ::util::{paths::SanitizedPath, ResultExt}; use anyhow::{anyhow, Context, Result}; use async_task::Runnable; use futures::channel::oneshot::{self, Receiver}; @@ -645,13 +645,11 @@ fn file_save_dialog(directory: PathBuf) -> Result> { let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? }; if !directory.to_string_lossy().is_empty() { if let Some(full_path) = directory.canonicalize().log_err() { - let full_path = full_path.to_string_lossy(); - let full_path_str = full_path.trim_start_matches("\\\\?\\"); - if !full_path_str.is_empty() { - let path_item: IShellItem = - unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_str), None)? }; - unsafe { dialog.SetFolder(&path_item).log_err() }; - } + let full_path = SanitizedPath::from(full_path); + let full_path_string = full_path.to_string(); + let path_item: IShellItem = + unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? }; + unsafe { dialog.SetFolder(&path_item).log_err() }; } } unsafe { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 29a0afcfe5..6f4d23fa76 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -5577,7 +5577,7 @@ impl LspStore { let worktree = worktree_handle.read(cx); let worktree_id = worktree.id(); - let worktree_path = worktree.abs_path(); + let root_path = worktree.abs_path(); let key = (worktree_id, adapter.name.clone()); if self.language_server_ids.contains_key(&key) { @@ -5599,7 +5599,6 @@ impl LspStore { as Arc; let server_id = self.languages.next_language_server_id(); - let root_path = worktree_path.clone(); log::info!( "attempting to start language server {:?}, path: {root_path:?}, id: {server_id}", adapter.name.0 diff --git a/crates/project/src/worktree_store.rs b/crates/project/src/worktree_store.rs index db5ae67ba7..1e48cc052e 100644 --- a/crates/project/src/worktree_store.rs +++ b/crates/project/src/worktree_store.rs @@ -23,7 +23,7 @@ use smol::{ stream::StreamExt, }; use text::ReplicaId; -use util::ResultExt; +use util::{paths::SanitizedPath, ResultExt}; use worktree::{Entry, ProjectEntryId, Worktree, WorktreeId, WorktreeSettings}; use crate::{search::SearchQuery, ProjectPath}; @@ -52,7 +52,7 @@ pub struct WorktreeStore { worktrees_reordered: bool, #[allow(clippy::type_complexity)] loading_worktrees: - HashMap, Shared, Arc>>>>, + HashMap, Arc>>>>, state: WorktreeStoreState, } @@ -147,11 +147,12 @@ impl WorktreeStore { pub fn find_worktree( &self, - abs_path: &Path, + abs_path: impl Into, cx: &AppContext, ) -> Option<(Model, PathBuf)> { + let abs_path: SanitizedPath = abs_path.into(); for tree in self.worktrees() { - if let Ok(relative_path) = abs_path.strip_prefix(tree.read(cx).abs_path()) { + if let Ok(relative_path) = abs_path.as_path().strip_prefix(tree.read(cx).abs_path()) { return Some((tree.clone(), relative_path.into())); } } @@ -192,12 +193,12 @@ impl WorktreeStore { pub fn create_worktree( &mut self, - abs_path: impl AsRef, + abs_path: impl Into, visible: bool, cx: &mut ModelContext, ) -> Task>> { - let path: Arc = abs_path.as_ref().into(); - if !self.loading_worktrees.contains_key(&path) { + let abs_path: SanitizedPath = abs_path.into(); + if !self.loading_worktrees.contains_key(&abs_path) { let task = match &self.state { WorktreeStoreState::Remote { upstream_client, .. @@ -205,20 +206,26 @@ impl WorktreeStore { if upstream_client.is_via_collab() { Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab")))) } else { - self.create_ssh_worktree(upstream_client.clone(), abs_path, visible, cx) + self.create_ssh_worktree( + upstream_client.clone(), + abs_path.clone(), + visible, + cx, + ) } } WorktreeStoreState::Local { fs } => { - self.create_local_worktree(fs.clone(), abs_path, visible, cx) + self.create_local_worktree(fs.clone(), abs_path.clone(), visible, cx) } }; - self.loading_worktrees.insert(path.clone(), task.shared()); + self.loading_worktrees + .insert(abs_path.clone(), task.shared()); } - let task = self.loading_worktrees.get(&path).unwrap().clone(); + let task = self.loading_worktrees.get(&abs_path).unwrap().clone(); cx.spawn(|this, mut cx| async move { let result = task.await; - this.update(&mut cx, |this, _| this.loading_worktrees.remove(&path)) + this.update(&mut cx, |this, _| this.loading_worktrees.remove(&abs_path)) .ok(); match result { Ok(worktree) => Ok(worktree), @@ -230,12 +237,11 @@ impl WorktreeStore { fn create_ssh_worktree( &mut self, client: AnyProtoClient, - abs_path: impl AsRef, + abs_path: impl Into, visible: bool, cx: &mut ModelContext, ) -> Task, Arc>> { - let path_key: Arc = abs_path.as_ref().into(); - let mut abs_path = path_key.clone().to_string_lossy().to_string(); + let mut abs_path = Into::::into(abs_path).to_string(); // If we start with `/~` that means the ssh path was something like `ssh://user@host/~/home-dir-folder/` // in which case want to strip the leading the `/`. // On the host-side, the `~` will get expanded. @@ -293,12 +299,12 @@ impl WorktreeStore { fn create_local_worktree( &mut self, fs: Arc, - abs_path: impl AsRef, + abs_path: impl Into, visible: bool, cx: &mut ModelContext, ) -> Task, Arc>> { let next_entry_id = self.next_entry_id.clone(); - let path: Arc = abs_path.as_ref().into(); + let path: SanitizedPath = abs_path.into(); cx.spawn(move |this, mut cx| async move { let worktree = Worktree::local(path.clone(), visible, fs, next_entry_id, &mut cx).await; @@ -308,7 +314,7 @@ impl WorktreeStore { if visible { cx.update(|cx| { - cx.add_recent_document(&path); + cx.add_recent_document(path.as_path()); }) .log_err(); } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 35ad35a0e1..44e97122b8 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -798,7 +798,6 @@ fn possible_open_paths_metadata( cx.background_executor().spawn(async move { let mut paths_with_metadata = Vec::with_capacity(potential_paths.len()); - #[cfg(not(target_os = "windows"))] let mut fetch_metadata_tasks = potential_paths .into_iter() .map(|potential_path| async { @@ -814,20 +813,6 @@ fn possible_open_paths_metadata( }) .collect::>(); - #[cfg(target_os = "windows")] - let mut fetch_metadata_tasks = potential_paths - .iter() - .map(|potential_path| async { - let metadata = fs.metadata(potential_path).await.ok().flatten(); - let path = PathBuf::from( - potential_path - .to_string_lossy() - .trim_start_matches("\\\\?\\"), - ); - (PathWithPosition { path, row, column }, metadata) - }) - .collect::>(); - while let Some((path, metadata)) = fetch_metadata_tasks.next().await { if let Some(metadata) = metadata { paths_with_metadata.push((path, metadata)); diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 94d580e643..2f84114409 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -37,6 +37,7 @@ unicase.workspace = true [target.'cfg(windows)'.dependencies] tendril = "0.4.3" +dunce = "1.0" [dev-dependencies] git2.workspace = true diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index f4e494f66e..e3b0af1fdb 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -1,5 +1,5 @@ use std::cmp; -use std::sync::OnceLock; +use std::sync::{Arc, OnceLock}; use std::{ ffi::OsStr, path::{Path, PathBuf}, @@ -95,6 +95,46 @@ impl> PathExt for T { } } +/// Due to the issue of UNC paths on Windows, which can cause bugs in various parts of Zed, introducing this `SanitizedPath` +/// leverages Rust's type system to ensure that all paths entering Zed are always "sanitized" by removing the `\\\\?\\` prefix. +/// On non-Windows operating systems, this struct is effectively a no-op. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SanitizedPath(Arc); + +impl SanitizedPath { + pub fn starts_with(&self, prefix: &SanitizedPath) -> bool { + self.0.starts_with(&prefix.0) + } + + pub fn as_path(&self) -> &Arc { + &self.0 + } + + pub fn to_string(&self) -> String { + self.0.to_string_lossy().to_string() + } +} + +impl From for Arc { + fn from(sanitized_path: SanitizedPath) -> Self { + sanitized_path.0 + } +} + +impl> From for SanitizedPath { + #[cfg(not(target_os = "windows"))] + fn from(path: T) -> Self { + let path = path.as_ref(); + SanitizedPath(path.into()) + } + + #[cfg(target_os = "windows")] + fn from(path: T) -> Self { + let path = path.as_ref(); + SanitizedPath(dunce::simplified(path).into()) + } +} + /// A delimiter to use in `path_query:row_number:column_number` strings parsing. pub const FILE_ROW_COLUMN_DELIMITER: char = ':'; @@ -805,4 +845,22 @@ mod tests { "Path matcher should match {path:?}" ); } + + #[test] + #[cfg(target_os = "windows")] + fn test_sanitized_path() { + let path = Path::new("C:\\Users\\someone\\test_file.rs"); + let sanitized_path = SanitizedPath::from(path); + assert_eq!( + sanitized_path.to_string(), + "C:\\Users\\someone\\test_file.rs" + ); + + let path = Path::new("\\\\?\\C:\\Users\\someone\\test_file.rs"); + let sanitized_path = SanitizedPath::from(path); + assert_eq!( + sanitized_path.to_string(), + "C:\\Users\\someone\\test_file.rs" + ); + } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4687b1decd..ed5aaa6e49 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -97,7 +97,7 @@ use ui::{ IntoElement, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _, WindowContext, }; -use util::{ResultExt, TryFutureExt}; +use util::{paths::SanitizedPath, ResultExt, TryFutureExt}; use uuid::Uuid; pub use workspace_settings::{ AutosaveSetting, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings, @@ -2024,7 +2024,7 @@ impl Workspace { }; let this = this.clone(); - let abs_path = abs_path.clone(); + let abs_path: Arc = SanitizedPath::from(abs_path.clone()).into(); let fs = fs.clone(); let pane = pane.clone(); let task = cx.spawn(move |mut cx| async move { @@ -2033,7 +2033,7 @@ impl Workspace { this.update(&mut cx, |workspace, cx| { let worktree = worktree.read(cx); let worktree_abs_path = worktree.abs_path(); - let entry_id = if abs_path == worktree_abs_path.as_ref() { + let entry_id = if abs_path.as_ref() == worktree_abs_path.as_ref() { worktree.root_entry() } else { abs_path diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index b7ee4466c7..e856bbf7de 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -66,7 +66,7 @@ use std::{ use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; use text::{LineEnding, Rope}; use util::{ - paths::{home_dir, PathMatcher}, + paths::{home_dir, PathMatcher, SanitizedPath}, ResultExt, }; pub use worktree_settings::WorktreeSettings; @@ -149,7 +149,7 @@ pub struct RemoteWorktree { #[derive(Clone)] pub struct Snapshot { id: WorktreeId, - abs_path: Arc, + abs_path: SanitizedPath, root_name: String, root_char_bag: CharBag, entries_by_path: SumTree, @@ -356,7 +356,7 @@ enum ScanState { scanning: bool, }, RootUpdated { - new_path: Option>, + new_path: Option, }, } @@ -654,8 +654,8 @@ impl Worktree { pub fn abs_path(&self) -> Arc { match self { - Worktree::Local(worktree) => worktree.abs_path.clone(), - Worktree::Remote(worktree) => worktree.abs_path.clone(), + Worktree::Local(worktree) => worktree.abs_path.clone().into(), + Worktree::Remote(worktree) => worktree.abs_path.clone().into(), } } @@ -1026,6 +1026,7 @@ impl LocalWorktree { } pub fn contains_abs_path(&self, path: &Path) -> bool { + let path = SanitizedPath::from(path); path.starts_with(&self.abs_path) } @@ -1066,13 +1067,13 @@ impl LocalWorktree { let (scan_states_tx, mut scan_states_rx) = mpsc::unbounded(); let background_scanner = cx.background_executor().spawn({ let abs_path = &snapshot.abs_path; - let abs_path = if cfg!(target_os = "windows") { - abs_path - .canonicalize() - .unwrap_or_else(|_| abs_path.to_path_buf()) - } else { - abs_path.to_path_buf() - }; + #[cfg(target_os = "windows")] + let abs_path = abs_path + .as_path() + .canonicalize() + .unwrap_or_else(|_| abs_path.as_path().to_path_buf()); + #[cfg(not(target_os = "windows"))] + let abs_path = abs_path.as_path().to_path_buf(); let background = cx.background_executor().clone(); async move { let (events, watcher) = fs.watch(&abs_path, FS_WATCH_LATENCY).await; @@ -1135,6 +1136,7 @@ impl LocalWorktree { this.snapshot.git_repositories = Default::default(); this.snapshot.ignores_by_parent_abs_path = Default::default(); let root_name = new_path + .as_path() .file_name() .map_or(String::new(), |f| f.to_string_lossy().to_string()); this.snapshot.update_abs_path(new_path, root_name); @@ -2075,7 +2077,7 @@ impl Snapshot { pub fn new(id: u64, root_name: String, abs_path: Arc) -> Self { Snapshot { id: WorktreeId::from_usize(id as usize), - abs_path, + abs_path: abs_path.into(), root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(), root_name, always_included_entries: Default::default(), @@ -2091,8 +2093,20 @@ impl Snapshot { self.id } + // TODO: + // Consider the following: + // + // ```rust + // let abs_path: Arc = snapshot.abs_path(); // e.g. "C:\Users\user\Desktop\project" + // let some_non_trimmed_path = Path::new("\\\\?\\C:\\Users\\user\\Desktop\\project\\main.rs"); + // // The caller perform some actions here: + // some_non_trimmed_path.strip_prefix(abs_path); // This fails + // some_non_trimmed_path.starts_with(abs_path); // This fails too + // ``` + // + // This is definitely a bug, but it's not clear if we should handle it here or not. pub fn abs_path(&self) -> &Arc { - &self.abs_path + self.abs_path.as_path() } fn build_initial_update(&self, project_id: u64, worktree_id: u64) -> proto::UpdateWorktree { @@ -2132,9 +2146,9 @@ impl Snapshot { return Err(anyhow!("invalid path")); } if path.file_name().is_some() { - Ok(self.abs_path.join(path)) + Ok(self.abs_path.as_path().join(path)) } else { - Ok(self.abs_path.to_path_buf()) + Ok(self.abs_path.as_path().to_path_buf()) } } @@ -2193,7 +2207,7 @@ impl Snapshot { .and_then(|entry| entry.git_status) } - fn update_abs_path(&mut self, abs_path: Arc, root_name: String) { + fn update_abs_path(&mut self, abs_path: SanitizedPath, root_name: String) { self.abs_path = abs_path; if root_name != self.root_name { self.root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect(); @@ -2212,7 +2226,7 @@ impl Snapshot { update.removed_entries.len() ); self.update_abs_path( - Arc::from(PathBuf::from(update.abs_path).as_path()), + SanitizedPath::from(PathBuf::from(update.abs_path)), update.root_name, ); @@ -2632,7 +2646,7 @@ impl LocalSnapshot { fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry { if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) { - let abs_path = self.abs_path.join(&entry.path); + let abs_path = self.abs_path.as_path().join(&entry.path); match smol::block_on(build_gitignore(&abs_path, fs)) { Ok(ignore) => { self.ignores_by_parent_abs_path @@ -2786,8 +2800,9 @@ impl LocalSnapshot { if git_state { for ignore_parent_abs_path in self.ignores_by_parent_abs_path.keys() { - let ignore_parent_path = - ignore_parent_abs_path.strip_prefix(&self.abs_path).unwrap(); + let ignore_parent_path = ignore_parent_abs_path + .strip_prefix(self.abs_path.as_path()) + .unwrap(); assert!(self.entry_for_path(ignore_parent_path).is_some()); assert!(self .entry_for_path(ignore_parent_path.join(*GITIGNORE)) @@ -2941,7 +2956,7 @@ impl BackgroundScannerState { } if let Some(ignore) = ignore { - let abs_parent_path = self.snapshot.abs_path.join(parent_path).into(); + let abs_parent_path = self.snapshot.abs_path.as_path().join(parent_path).into(); self.snapshot .ignores_by_parent_abs_path .insert(abs_parent_path, (ignore, false)); @@ -3004,7 +3019,11 @@ impl BackgroundScannerState { } if entry.path.file_name() == Some(&GITIGNORE) { - let abs_parent_path = self.snapshot.abs_path.join(entry.path.parent().unwrap()); + let abs_parent_path = self + .snapshot + .abs_path + .as_path() + .join(entry.path.parent().unwrap()); if let Some((_, needs_update)) = self .snapshot .ignores_by_parent_abs_path @@ -3085,7 +3104,7 @@ impl BackgroundScannerState { return None; } - let dot_git_abs_path = self.snapshot.abs_path.join(&dot_git_path); + let dot_git_abs_path = self.snapshot.abs_path.as_path().join(&dot_git_path); let t0 = Instant::now(); let repository = fs.open_repo(&dot_git_abs_path)?; @@ -3299,9 +3318,9 @@ impl language::LocalFile for File { fn abs_path(&self, cx: &AppContext) -> PathBuf { let worktree_path = &self.worktree.read(cx).as_local().unwrap().abs_path; if self.path.as_ref() == Path::new("") { - worktree_path.to_path_buf() + worktree_path.as_path().to_path_buf() } else { - worktree_path.join(&self.path) + worktree_path.as_path().join(&self.path) } } @@ -3712,7 +3731,7 @@ impl BackgroundScanner { // the git repository in an ancestor directory. Find any gitignore files // in ancestor directories. let root_abs_path = self.state.lock().snapshot.abs_path.clone(); - for (index, ancestor) in root_abs_path.ancestors().enumerate() { + for (index, ancestor) in root_abs_path.as_path().ancestors().enumerate() { if index != 0 { if let Ok(ignore) = build_gitignore(&ancestor.join(*GITIGNORE), self.fs.as_ref()).await @@ -3744,7 +3763,13 @@ impl BackgroundScanner { self.state.lock().insert_git_repository_for_path( Path::new("").into(), ancestor_dot_git.into(), - Some(root_abs_path.strip_prefix(ancestor).unwrap().into()), + Some( + root_abs_path + .as_path() + .strip_prefix(ancestor) + .unwrap() + .into(), + ), self.fs.as_ref(), self.watcher.as_ref(), ); @@ -3763,12 +3788,12 @@ impl BackgroundScanner { if let Some(mut root_entry) = state.snapshot.root_entry().cloned() { let ignore_stack = state .snapshot - .ignore_stack_for_abs_path(&root_abs_path, true); - if ignore_stack.is_abs_path_ignored(&root_abs_path, true) { + .ignore_stack_for_abs_path(root_abs_path.as_path(), true); + if ignore_stack.is_abs_path_ignored(root_abs_path.as_path(), true) { root_entry.is_ignored = true; state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref()); } - state.enqueue_scan_dir(root_abs_path, &root_entry, &scan_job_tx); + state.enqueue_scan_dir(root_abs_path.into(), &root_entry, &scan_job_tx); } }; @@ -3818,7 +3843,7 @@ impl BackgroundScanner { { let mut state = self.state.lock(); state.path_prefixes_to_scan.insert(path_prefix.clone()); - state.snapshot.abs_path.join(&path_prefix) + state.snapshot.abs_path.as_path().join(&path_prefix) }; if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() { @@ -3845,7 +3870,7 @@ impl BackgroundScanner { self.forcibly_load_paths(&request.relative_paths).await; let root_path = self.state.lock().snapshot.abs_path.clone(); - let root_canonical_path = match self.fs.canonicalize(&root_path).await { + let root_canonical_path = match self.fs.canonicalize(root_path.as_path()).await { Ok(path) => path, Err(err) => { log::error!("failed to canonicalize root path: {}", err); @@ -3874,7 +3899,7 @@ impl BackgroundScanner { } self.reload_entries_for_paths( - root_path, + root_path.into(), root_canonical_path, &request.relative_paths, abs_paths, @@ -3887,7 +3912,7 @@ impl BackgroundScanner { async fn process_events(&self, mut abs_paths: Vec) { let root_path = self.state.lock().snapshot.abs_path.clone(); - let root_canonical_path = match self.fs.canonicalize(&root_path).await { + let root_canonical_path = match self.fs.canonicalize(root_path.as_path()).await { Ok(path) => path, Err(err) => { let new_path = self @@ -3897,21 +3922,20 @@ impl BackgroundScanner { .root_file_handle .clone() .and_then(|handle| handle.current_path(&self.fs).log_err()) - .filter(|new_path| **new_path != *root_path); + .map(SanitizedPath::from) + .filter(|new_path| *new_path != root_path); if let Some(new_path) = new_path.as_ref() { log::info!( "root renamed from {} to {}", - root_path.display(), - new_path.display() + root_path.as_path().display(), + new_path.as_path().display() ) } else { log::warn!("root path could not be canonicalized: {}", err); } self.status_updates_tx - .unbounded_send(ScanState::RootUpdated { - new_path: new_path.map(|p| p.into()), - }) + .unbounded_send(ScanState::RootUpdated { new_path }) .ok(); return; } @@ -4006,7 +4030,7 @@ impl BackgroundScanner { let (scan_job_tx, scan_job_rx) = channel::unbounded(); log::debug!("received fs events {:?}", relative_paths); self.reload_entries_for_paths( - root_path, + root_path.into(), root_canonical_path, &relative_paths, abs_paths, @@ -4044,7 +4068,7 @@ impl BackgroundScanner { for ancestor in path.ancestors() { if let Some(entry) = state.snapshot.entry_for_path(ancestor) { if entry.kind == EntryKind::UnloadedDir { - let abs_path = root_path.join(ancestor); + let abs_path = root_path.as_path().join(ancestor); state.enqueue_scan_dir(abs_path.into(), entry, &scan_job_tx); state.paths_to_scan.insert(path.clone()); break; @@ -4548,7 +4572,7 @@ impl BackgroundScanner { snapshot .ignores_by_parent_abs_path .retain(|parent_abs_path, (_, needs_update)| { - if let Ok(parent_path) = parent_abs_path.strip_prefix(&abs_path) { + if let Ok(parent_path) = parent_abs_path.strip_prefix(abs_path.as_path()) { if *needs_update { *needs_update = false; if snapshot.snapshot.entry_for_path(parent_path).is_some() { @@ -4627,7 +4651,10 @@ impl BackgroundScanner { let mut entries_by_id_edits = Vec::new(); let mut entries_by_path_edits = Vec::new(); - let path = job.abs_path.strip_prefix(&snapshot.abs_path).unwrap(); + let path = job + .abs_path + .strip_prefix(snapshot.abs_path.as_path()) + .unwrap(); let repo = snapshot.repo_for_path(path); for mut entry in snapshot.child_entries(path).cloned() { let was_ignored = entry.is_ignored; diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index cfc11ade3f..c598054356 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -1124,10 +1124,7 @@ impl ToString for IdType { fn parse_url_arg(arg: &str, cx: &AppContext) -> Result { match std::fs::canonicalize(Path::new(&arg)) { - Ok(path) => Ok(format!( - "file://{}", - path.to_string_lossy().trim_start_matches(r#"\\?\"#) - )), + Ok(path) => Ok(format!("file://{}", path.display())), Err(error) => { if arg.starts_with("file://") || arg.starts_with("zed-cli://")