diff --git a/CHANGELOG.md b/CHANGELOG.md index f5316048..4cfa465a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - zsh: better cd completions. - elvish: `z -` now work as expected. - Lazily delete excluded directories from the database. +- Normalize drive letters when resolving paths on Windows. ## [0.9.4] - 2024-02-21 diff --git a/src/util.rs b/src/util.rs index 1f8fc95f..cf8aee89 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,7 +1,7 @@ use std::ffi::OsStr; use std::fs::{self, File, OpenOptions}; use std::io::{self, Read, Write}; -use std::path::{Component, Path, PathBuf}; +use std::path::{Component, Path, PathBuf, Prefix}; use std::process::{Child, Command, Stdio}; use std::time::SystemTime; use std::{env, mem}; @@ -263,6 +263,37 @@ pub fn path_to_str(path: &impl AsRef) -> Result<&str> { path.to_str().with_context(|| format!("invalid unicode in path: {}", path.display())) } +fn patch_path(path: PathBuf) -> PathBuf { + if cfg!(windows) { + fn patch_drive(drive_letter: u8) -> char { + drive_letter.to_ascii_uppercase() as char + } + + let mut components = path.components(); + match components.next() { + Some(Component::Prefix(prefix)) => { + let prefix = match prefix.kind() { + Prefix::Disk(drive_letter) => { + format!(r"{}:", patch_drive(drive_letter)) + } + Prefix::VerbatimDisk(drive_letter) => { + format!(r"\\?\{}:", patch_drive(drive_letter)) + } + _ => return path, + }; + + let mut path = PathBuf::default(); + path.push(prefix); + path.extend(components); + path + } + _ => path, + } + } else { + path + } +} + /// Returns the absolute version of a path. Like /// [`std::path::Path::canonicalize`], but doesn't resolve symlinks. pub fn resolve_path(path: impl AsRef) -> Result { @@ -274,8 +305,6 @@ pub fn resolve_path(path: impl AsRef) -> Result { // initialize root if cfg!(windows) { - use std::path::Prefix; - fn get_drive_letter(path: impl AsRef) -> Option { let path = path.as_ref(); let mut components = path.components(); @@ -292,17 +321,17 @@ pub fn resolve_path(path: impl AsRef) -> Result { } fn get_drive_path(drive_letter: u8) -> PathBuf { - format!(r"{}:\", drive_letter as char).into() + format!(r"{}:\", drive_letter.to_ascii_uppercase() as char).into() } fn get_drive_relative(drive_letter: u8) -> Result { let path = current_dir()?; if Some(drive_letter) == get_drive_letter(&path) { - return Ok(path); + return Ok(patch_path(path)); } if let Some(path) = env::var_os(format!("={}:", drive_letter as char)) { - return Ok(path.into()); + return Ok(patch_path(path.into())); } let path = get_drive_path(drive_letter); @@ -312,23 +341,25 @@ pub fn resolve_path(path: impl AsRef) -> Result { match components.peek() { Some(Component::Prefix(prefix)) => match prefix.kind() { Prefix::Disk(drive_letter) => { - let disk = components.next().unwrap(); + components.next(); if components.peek() == Some(&Component::RootDir) { - let root = components.next().unwrap(); - stack.push(disk); - stack.push(root); + components.next(); + base_path = get_drive_path(drive_letter); } else { base_path = get_drive_relative(drive_letter)?; - stack.extend(base_path.components()); } + + stack.extend(base_path.components()); } Prefix::VerbatimDisk(drive_letter) => { components.next(); if components.peek() == Some(&Component::RootDir) { components.next(); + base_path = get_drive_path(drive_letter); + } else { + bail!("illegal path: {}", path.display()); } - base_path = get_drive_path(drive_letter); stack.extend(base_path.components()); } _ => bail!("invalid path: {}", path.display()),