diff options
| author | JJ Liebig <jonathan.liebig@gmail.com> | 2026-09-15 17:17:47 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-15 17:17:47 +0200 |
| commit | 9db1eed391afbcb5574d060463e149f14acf40fa (patch) | |
| tree | 19396cf657596309340c9e3126a8527e0e4a1ef8 /src/platform | |
| parent | ee064a0c798dd5617d253be7d26aa41f924bab96 (diff) | |
fix(windows): normalize pane launch cwd casing (#4199)
Diffstat (limited to 'src/platform')
| -rw-r--r-- | src/platform/mod.rs | 9 | ||||
| -rw-r--r-- | src/platform/windows.rs | 63 |
2 files changed, 67 insertions, 5 deletions
diff --git a/src/platform/mod.rs b/src/platform/mod.rs index a578b42d..70047fef 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -82,6 +82,15 @@ pub(crate) fn plugin_runtime_path(path: &std::path::Path) -> std::path::PathBuf plugin_runtime_path_platform(path) } +pub(crate) fn normalize_cwd_for_launch(path: &std::path::Path) -> std::path::PathBuf { + normalize_cwd_for_launch_platform(path) +} + +#[cfg(not(windows))] +fn normalize_cwd_for_launch_platform(path: &std::path::Path) -> std::path::PathBuf { + path.to_path_buf() +} + #[cfg(not(windows))] fn plugin_runtime_path_platform(path: &std::path::Path) -> std::path::PathBuf { path.to_path_buf() diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 080bbf34..9742b52c 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -380,6 +380,54 @@ pub(crate) fn prepare_paste_text_for_pty_platform(text: String) -> String { text.replace("\r\n", "\n").replace('\n', "\r\n") } +pub(crate) fn normalize_cwd_for_launch_platform(path: &std::path::Path) -> PathBuf { + use std::os::windows::ffi::{OsStrExt, OsStringExt}; + use std::path::{Component, Prefix}; + use windows_sys::Win32::{ + Foundation::INVALID_HANDLE_VALUE, + Storage::FileSystem::{FindClose, FindFirstFileW, WIN32_FIND_DATAW}, + }; + + fn stored_name(path: &std::path::Path) -> Option<std::ffi::OsString> { + let input = path + .as_os_str() + .encode_wide() + .chain(std::iter::once(0)) + .collect::<Vec<_>>(); + let mut data = std::mem::MaybeUninit::<WIN32_FIND_DATAW>::uninit(); + let handle = unsafe { FindFirstFileW(input.as_ptr(), data.as_mut_ptr()) }; + if handle == INVALID_HANDLE_VALUE { + return None; + } + let data = unsafe { data.assume_init() }; + unsafe { FindClose(handle) }; + let len = data + .cFileName + .iter() + .position(|&ch| ch == 0) + .unwrap_or(data.cFileName.len()); + Some(std::ffi::OsString::from_wide(&data.cFileName[..len])) + } + + let mut normalized = PathBuf::new(); + for component in path.components() { + match component { + Component::Prefix(prefix) => match prefix.kind() { + Prefix::Disk(drive) => { + normalized.push(format!("{}:", char::from(drive).to_ascii_uppercase())) + } + _ => normalized.push(prefix.as_os_str()), + }, + Component::Normal(name) => { + let candidate = normalized.join(name); + normalized.push(stored_name(&candidate).unwrap_or_else(|| name.to_os_string())); + } + _ => normalized.push(component.as_os_str()), + } + } + normalized +} + pub(crate) fn plugin_runtime_path_platform(path: &std::path::Path) -> PathBuf { use std::os::windows::ffi::OsStrExt; @@ -3399,15 +3447,17 @@ mod tests { } #[test] - fn windows_process_cwd_reads_child_launch_directory() { - let cwd = std::env::temp_dir().join(format!("herdr-cwd-test-{}", std::process::id())); + fn windows_process_cwd_reads_normalized_child_launch_directory() { + let name = format!("Herdr-Cwd-Case-{}", std::process::id()); + let cwd = std::env::temp_dir().join(&name); fs::create_dir_all(&cwd).expect("create cwd fixture"); + let launch_cwd = cwd.with_file_name(name.to_ascii_lowercase()); let shell = std::env::var_os("ComSpec").unwrap_or_else(|| r"C:\Windows\System32\cmd.exe".into()); let mut child = Command::new(shell) .args(["/D", "/Q", "/C", "ping -n 11 127.0.0.1 > NUL"]) - .current_dir(&cwd) + .current_dir(super::normalize_cwd_for_launch_platform(&launch_cwd)) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -3418,7 +3468,7 @@ mod tests { let mut observed = None; while Instant::now() < deadline { observed = super::process_cwd(child.id()); - if observed.as_deref() == Some(cwd.as_path()) { + if observed.as_ref().and_then(|path| path.file_name()) == Some(name.as_ref()) { break; } thread::sleep(Duration::from_millis(100)); @@ -3428,7 +3478,10 @@ mod tests { let _ = child.wait(); let _ = fs::remove_dir_all(&cwd); - assert_eq!(observed.as_deref(), Some(cwd.as_path())); + assert_eq!( + observed.as_ref().and_then(|path| path.file_name()), + Some(name.as_ref()) + ); } #[test] |
