Windows: fix "Show in Explorer" opening Documents instead of the app's folder (#54)
* Fix Show in Explorer opening Documents for paths with spaces reveal() built `/select,<path>` as one argument and passed it with Command::arg, which quotes any argument containing a space. The Craft apps' MSIs install under C:\Program Files, so Explorer received "/select,C:\Program Files\...\app.exe", didn't recognise the switch and opened its default folder, Documents. The argument is now `/select,"<path>"`, with only the path quoted and forward slashes turned into backslashes, passed with raw_arg so Rust leaves it alone. The argument building is a plain function with unit tests that run on every platform. Fixes #39 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Refuse a path with a quote in it; give the Explorer tests their own module A " would end the quoted /select path early and pass the rest to Explorer as switches. The module name clashed with the pgrep tests that landed in #81. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
92e23e0348
commit
5b7d4781f5
|
|
@ -160,16 +160,45 @@ pub fn reveal(path: &Path) -> Result<(), String> {
|
|||
cmd.arg("-R").arg(path);
|
||||
spawn_detached(cmd)
|
||||
} else if cfg!(windows) {
|
||||
let mut arg = std::ffi::OsString::from("/select,");
|
||||
arg.push(path);
|
||||
let mut cmd = command("explorer");
|
||||
cmd.arg(arg);
|
||||
spawn_detached(cmd)
|
||||
reveal_in_explorer(path)
|
||||
} else {
|
||||
open(path.parent().unwrap_or(path).as_os_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn reveal_in_explorer(path: &Path) -> Result<(), String> {
|
||||
use std::os::windows::process::CommandExt;
|
||||
let mut cmd = command("explorer");
|
||||
// Passed raw: `arg` would wrap all of `/select,...` in quotes when the path has a space
|
||||
// (`C:\Program Files\...`), and Explorer then ignores the switch and opens Documents.
|
||||
// A quote in the path would end the quoted part early and let the rest reach Explorer as
|
||||
// switches; no Windows path has one, so refuse it.
|
||||
let arg = explorer_select_arg(path).ok_or_else(|| format!("{} isn't a usable path", path.display()))?;
|
||||
cmd.raw_arg(arg);
|
||||
spawn_detached(cmd)
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn reveal_in_explorer(_path: &Path) -> Result<(), String> {
|
||||
Err("Explorer is only on Windows".into())
|
||||
}
|
||||
|
||||
/// Explorer's `/select,"<path>"` argument as it goes on the command line: only the path is quoted,
|
||||
/// and forward slashes become backslashes, which Explorer needs to find the item. `None` for a path
|
||||
/// with a `"` in it.
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
fn explorer_select_arg(path: &Path) -> Option<std::ffi::OsString> {
|
||||
let s = path.to_str()?;
|
||||
if s.contains('"') {
|
||||
return None;
|
||||
}
|
||||
let mut arg = std::ffi::OsString::from("/select,\"");
|
||||
arg.push(s.replace('/', "\\"));
|
||||
arg.push("\"");
|
||||
Some(arg)
|
||||
}
|
||||
|
||||
/// Whether a process started from inside `path` (an exe, an AppImage, or a `.app`) is running.
|
||||
pub fn is_running(path: &Path) -> bool {
|
||||
if cfg!(windows) {
|
||||
|
|
@ -252,3 +281,27 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod explorer_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn explorer_select_quotes_only_the_path() {
|
||||
let arg = explorer_select_arg(Path::new(r"C:\Program Files\PhotoCraft\photocraft.exe"));
|
||||
assert_eq!(arg.unwrap(), r#"/select,"C:\Program Files\PhotoCraft\photocraft.exe""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explorer_select_without_spaces() {
|
||||
let arg = explorer_select_arg(Path::new(r"C:\Apps\photocraft.exe"));
|
||||
assert_eq!(arg.unwrap(), r#"/select,"C:\Apps\photocraft.exe""#);
|
||||
assert_eq!(explorer_select_arg(Path::new(r#"C:\Apps" /root,C:\x.exe"#)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explorer_select_uses_backslashes() {
|
||||
let arg = explorer_select_arg(Path::new("C:/Users/A B/AppData/Local/ArtCraft Launcher/apps/photocraft.exe"));
|
||||
assert_eq!(arg.unwrap(), r#"/select,"C:\Users\A B\AppData\Local\ArtCraft Launcher\apps\photocraft.exe""#);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue