From d295ff4f04f0b38eca419c04bc3ac128474d92fc Mon Sep 17 00:00:00 2001 From: localcc Date: Mon, 24 Nov 2025 20:48:16 +0100 Subject: [PATCH] Improve Windows path canonicalization (#43423) Path canonicalization on windows will now favor keeping the drive letter intact when canonicalizing paths. This helps some lsps with mapped network drive compatibility. Closes #41336 Release Notes: - N/A --- crates/fs/src/fs.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 33cc83a7886349a537a87d4b6c8bb3f5211608fc..93192ecd2bd2449dafa622a69045be6811a43cf7 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -421,6 +421,75 @@ impl RealFs { job_event_subscribers: Arc::new(Mutex::new(Vec::new())), } } + + #[cfg(target_os = "windows")] + fn canonicalize(path: &Path) -> Result { + let mut strip_prefix = None; + + let mut new_path = PathBuf::new(); + for component in path.components() { + match component { + std::path::Component::Prefix(_) => { + let canonicalized = std::fs::canonicalize(component)?; + + let mut strip = PathBuf::new(); + for component in canonicalized.components() { + match component { + Component::Prefix(prefix_component) => { + match prefix_component.kind() { + std::path::Prefix::Verbatim(os_str) => { + strip.push(os_str); + } + std::path::Prefix::VerbatimUNC(host, share) => { + strip.push("\\\\"); + strip.push(host); + strip.push(share); + } + std::path::Prefix::VerbatimDisk(disk) => { + strip.push(format!("{}:", disk as char)); + } + _ => strip.push(component), + }; + } + _ => strip.push(component), + } + } + strip_prefix = Some(strip); + new_path.push(component); + } + std::path::Component::RootDir => { + new_path.push(component); + } + std::path::Component::CurDir => { + if strip_prefix.is_none() { + // unrooted path + new_path.push(component); + } + } + std::path::Component::ParentDir => { + if strip_prefix.is_some() { + // rooted path + new_path.pop(); + } else { + new_path.push(component); + } + } + std::path::Component::Normal(_) => { + if let Ok(link) = std::fs::read_link(new_path.join(component)) { + let link = match &strip_prefix { + Some(e) => link.strip_prefix(e).unwrap_or(&link), + None => &link, + }; + new_path.extend(link); + } else { + new_path.push(component); + } + } + } + } + + Ok(new_path) + } } #[async_trait::async_trait] @@ -749,7 +818,13 @@ impl Fs for RealFs { let path = path.to_owned(); self.executor .spawn(async move { - std::fs::canonicalize(&path).with_context(|| format!("canonicalizing {path:?}")) + #[cfg(target_os = "windows")] + let result = Self::canonicalize(&path); + + #[cfg(not(target_os = "windows"))] + let result = std::fs::canonicalize(&path); + + result.with_context(|| format!("canonicalizing {path:?}")) }) .await }