diff --git a/Cargo.lock b/Cargo.lock index 2754f5bd23..a00e672645 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3352,7 +3352,10 @@ version = "0.100.1" dependencies = [ "dirs", "omnipath", + "once_cell", "pwd", + "winapi", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a976aeec17..394565702e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -321,3 +321,6 @@ bench = false [[bench]] name = "benchmarks" harness = false + +[profile.dev] +incremental = true diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 2af932bcb4..67ee4a6e9d 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -114,7 +114,19 @@ impl Command for Cd { //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay PermissionResult::PermissionOk => { + let path_for_scd = path.clone(); stack.set_cwd(path)?; + use nu_path::state_driver_pwd::get_drive_pwd_map; + get_drive_pwd_map().lock().unwrap().set_pwd(path_for_scd.as_path()) + .map_err(|_e| { + Err(ShellError::GenericError{ + error: "Set PWD for drive failed".into(), + msg: "System error".into(), + span: None, + help: None, + inner: vec![], + }) + })?; Ok(PipelineData::empty()) } PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError { diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index 22ab716d14..59de73e5e4 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -16,6 +16,9 @@ dirs = { workspace = true } [target.'cfg(windows)'.dependencies] omnipath = { workspace = true } +once_cell = "1.20.1" +winapi = { version = "0.3.9", features = ["fileapi"] } +windows-sys = { workspace = true, features = ["Win32_System_Environment"] } [target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))'.dependencies] pwd = { workspace = true } diff --git a/crates/nu-path/src/lib.rs b/crates/nu-path/src/lib.rs index cf31a5789f..2d4ecb7d88 100644 --- a/crates/nu-path/src/lib.rs +++ b/crates/nu-path/src/lib.rs @@ -8,6 +8,7 @@ mod helpers; mod path; mod tilde; mod trailing_slash; +pub mod state_driver_pwd; pub use components::components; pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs}; diff --git a/crates/nu-protocol/src/engine/state_driver_pwd.rs b/crates/nu-path/src/state_driver_pwd.rs similarity index 85% rename from crates/nu-protocol/src/engine/state_driver_pwd.rs rename to crates/nu-path/src/state_driver_pwd.rs index dfdbe0ebc3..267438954a 100644 --- a/crates/nu-protocol/src/engine/state_driver_pwd.rs +++ b/crates/nu-path/src/state_driver_pwd.rs @@ -1,11 +1,10 @@ -use std::path::Path; +use std::path::{ Path, PathBuf }; pub struct DrivePwdMap { map: [Option; 26], // Fixed-size array for A-Z } impl DrivePwdMap { - /// Create a new DrivePwdMap pub fn new() -> Self { DrivePwdMap { map: Default::default(), // Initialize all to `None` @@ -16,7 +15,8 @@ impl DrivePwdMap { pub fn set_pwd(&mut self, path: &Path) -> Result<(), String> { if let Some(drive_letter) = Self::extract_drive_letter(path) { if let Some(index) = Self::drive_to_index(drive_letter) { - self.map[index] = Some(path.to_string_lossy().into_owned()); + let normalized = Self::normalize_path(path); + self.map[index] = Some(normalized.to_string_lossy().into_owned()); Ok(()) } else { Err(format!("Invalid drive letter: {}", drive_letter)) @@ -27,7 +27,6 @@ impl DrivePwdMap { } /// Get the current working directory for a drive letter - /// If no PWD is set, return the root of the drive (e.g., `C:\`) pub fn get_pwd(&self, drive: char) -> Option { Self::drive_to_index(drive).map(|index| { self.map[index] @@ -43,10 +42,10 @@ impl DrivePwdMap { let is_absolute = path_str.contains(":\\") || path_str.starts_with("\\"); if is_absolute { // Already an absolute path - Some(PathBuf::from(path_str)) + Some(PathBuf::from(Self::ensure_trailing_separator(path_str))) } else if let Some(pwd) = self.get_pwd(drive_letter) { // Combine current PWD with the relative path - let mut base = PathBuf::from(pwd); + let mut base = PathBuf::from(Self::ensure_trailing_separator(&pwd)); base.push(path_str.split_at(2).1); // Skip the "C:" part of the relative path Some(base) } else { @@ -67,17 +66,35 @@ impl DrivePwdMap { } } - /// Extract the drive letter from a path (e.g., `C:\Users` -> `C`) + /// Extract the drive letter from a path (e.g., `C:test` -> `C`) fn extract_drive_letter(path: &Path) -> Option { path.to_str() .and_then(|s| s.chars().next()) .filter(|c| c.is_ascii_alphabetic()) } + + /// Normalize a path by removing any trailing `\` or `/` + fn normalize_path(path: &Path) -> PathBuf { + let mut normalized = path.to_path_buf(); + while normalized.to_string_lossy().ends_with(&['\\', '/'][..]) { + normalized.pop(); + } + normalized + } + + /// Ensure a path has a trailing `\` + fn ensure_trailing_separator(path: &str) -> String { + if !path.ends_with('\\') && !path.ends_with('/') { + format!("{}\\", path) + } else { + path.to_string() + } + } } use once_cell::sync::Lazy; use std::sync::Mutex; -use crate::ShellError; +//use nu_protocol::errors::shell_error::ShellError; /// Global singleton instance of DrivePwdMap static DRIVE_PWD_MAP: Lazy> = Lazy::new(|| Mutex::new(DrivePwdMap::new())); @@ -192,12 +209,11 @@ mod tests { } } -mod current_directory_specific { - use crate::ShellError; +pub mod current_directory_specific { use std::path::Path; #[cfg(target_os = "windows")] - fn need_expand_current_directory(path: &Path) -> bool { + pub fn need_expand_current_directory_per_drive(path: &Path) -> bool { if let Some(path_str) = path.to_str() { let chars: Vec = path_str.chars().collect(); if chars.len() >= 2 { @@ -208,12 +224,12 @@ mod current_directory_specific { } #[cfg(not(target_os = "windows"))] - fn need_expand_current_directory(path: &Path) -> bool { + pub fn need_expand_current_directory(path: &Path) -> bool { false } #[cfg(target_os = "windows")] - fn get_windows_absolute_path(path: &Path) -> Option { + pub fn get_windows_absolute_path(path: &Path) -> Option { use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; use std::os::windows::ffi::OsStrExt; @@ -247,12 +263,16 @@ mod current_directory_specific { None } + pub enum DrivePwdError { + InvalidPath, + SystemError, + } #[cfg(not(target_os = "windows"))] fn get_windows_absolute_path(path: &Path) -> Option { None } #[cfg(target_os = "windows")] - pub fn set_current_directory_windows(path: &Path) -> Result<(), ShellError> { + pub fn set_current_directory_windows(path: &Path) -> Result<(), DrivePwdError> { use std::ffi::OsString; use std::os::windows::ffi::OsStrExt; use windows_sys::Win32::System::Environment::SetCurrentDirectoryW; @@ -266,27 +286,16 @@ mod current_directory_specific { return Ok(()) } else { return - Err(ShellError::GenericError { - error: "Failed to set current directory".into(), - msg: "SetCurrentDirectoryW() failed".into(), - span: None, - help: None, - inner: vec![], - }) + Err(DrivePwdError::SystemError) + }; } } - Err(ShellError::GenericError { - error: "Failed to set current directory".into(), - msg: "Invalid path".into(), - span: None, - help: None, - inner: vec![], - }) + Err(DrivePwdError::InvalidPath) } #[cfg(not(target_os = "windows"))] - pub fn set_current_directory_windows(_path: &Path) -> Result<(), ShellError>{ + pub fn set_current_directory_windows(_path: &Path) -> Result<(), Error>{ Ok(()) } } \ No newline at end of file diff --git a/crates/nu-path/src/tilde.rs b/crates/nu-path/src/tilde.rs index 02324ea8da..360810b86c 100644 --- a/crates/nu-path/src/tilde.rs +++ b/crates/nu-path/src/tilde.rs @@ -21,17 +21,21 @@ fn expand_tilde_with_home(path: impl AsRef, home: Option) -> Path let path = path.as_ref(); if !path.starts_with("~") { - use nu_protocol::engine::state_driver_pwd:: { - need_expand_current_directory, - get_windows_absolute_path, + use crate::state_driver_pwd:: { + current_directory_specific::need_expand_current_directory_per_drive, + //current_directory_specific::get_windows_absolute_path, + get_drive_pwd_map }; - if need_expand_current_directory(path) { - if let Some(current_dir) = get_windows_absolute_path(path) { - //println!("Absolute path for {} is: {}", path.display(), current_dir); - return PathBuf::from(¤t_dir) - } else { - println!("Failed to get absolute path for {}", path.display()); + if need_expand_current_directory_per_drive(path) { + if let Some(expanded_dir) = get_drive_pwd_map().lock().unwrap().expand_path(path) { + return PathBuf::from(&expanded_dir) } + // if let Some(current_dir) = get_windows_absolute_path(path) { + // //println!("Absolute path for {} is: {}", path.display(), current_dir); + // return PathBuf::from(¤t_dir) + // } else { + // println!("Failed to get absolute path for {}", path.display()); + // } } let string = path.to_string_lossy(); let mut path_as_string = string.as_ref().bytes();