This commit is contained in:
Brandon Thomas
2026-10-09 09:25:58 -04:00
3 changed files with 54 additions and 3 deletions
+51 -2
View File
@@ -290,6 +290,39 @@ fn windows_open_url_command(url: &str) -> std::process::Command {
c
}
/// Explorer's `/select,<path>` switch (issue #272). Explorer parses its own command line and only
/// selects the file when the path after the comma is quoted (`/select,"C:\My Photos\a.jpg"`),
/// spelled with backslashes and free of the verbatim prefix (`\\?\`, `\\?\UNC\`) that
/// `canonicalize` adds; given anything else it opens Documents. Windows file names can't contain
/// `"`; a path that does anyway (a hostile catalog) has them dropped, so the raw command line
/// always holds exactly this one argument.
fn explorer_select_arg(path: &str) -> String {
let path = path.replace('/', "\\").replace('"', "");
let path = match path.strip_prefix(r"\\?\UNC\") {
Some(unc) => format!(r"\\{unc}"),
None => path.strip_prefix(r"\\?\").unwrap_or(path.as_str()).to_string(),
};
format!("/select,\"{path}\"")
}
/// `explorer /select,"<path>"`. Rust's own argument quoting wraps the whole switch in quotes
/// (`"/select,C:\My Photos\a.jpg"`) whenever the path has a space, which Explorer doesn't
/// recognise, so on Windows the switch goes on the command line exactly as built.
fn explorer_select_command(path: &str) -> std::process::Command {
let mut c = std::process::Command::new("explorer");
let arg = explorer_select_arg(path);
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
c.raw_arg(arg);
}
#[cfg(not(windows))]
{
c.arg(arg);
}
c
}
/// The platform services; `ctx` repaints when a file dialog closes, and `log_file` is
/// for Help ▸ Open Log Folder.
fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services {
@@ -377,8 +410,7 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services
let status = if cfg!(target_os = "macos") {
std::process::Command::new("open").args(["-R", path]).status()
} else if cfg!(target_os = "windows") {
let win_path = path.replace('/', "\\");
std::process::Command::new("explorer").arg(format!("/select,{win_path}")).status()
explorer_select_command(path).status()
} else {
let dir = std::path::Path::new(path).parent().map(|d| d.to_string_lossy().to_string()).unwrap_or_else(|| ".".into());
std::process::Command::new("xdg-open").arg(dir).status()
@@ -928,6 +960,23 @@ mod tests {
assert_eq!(args, ["url.dll,FileProtocolHandler", url]);
}
/// Issue #272: Explorer gets the file to select quoted after `/select,`, with backslashes and
/// without the verbatim prefix, even when the path has spaces (else it opens Documents).
#[test]
fn explorer_select_quotes_the_path() {
assert_eq!(explorer_select_arg(r"C:\Users\Me\My Photos\IMG 1.jpg"), r#"/select,"C:\Users\Me\My Photos\IMG 1.jpg""#);
assert_eq!(explorer_select_arg("D:/Example/Photos/IMG_0001.CR3"), r#"/select,"D:\Example\Photos\IMG_0001.CR3""#);
assert_eq!(explorer_select_arg(r"\\server\share\My Photos\a.jpg"), r#"/select,"\\server\share\My Photos\a.jpg""#);
assert_eq!(explorer_select_arg(r"\\?\D:\My Photos\a.jpg"), r#"/select,"D:\My Photos\a.jpg""#);
assert_eq!(explorer_select_arg(r"\\?\UNC\server\share\a.jpg"), r#"/select,"\\server\share\a.jpg""#);
// a `"` can't end the quoted path and add arguments to the raw command line
assert_eq!(explorer_select_arg(r#"C:\a" "C:\b.jpg"#), r#"/select,"C:\a C:\b.jpg""#);
let c = explorer_select_command(r"C:\My Photos\a.jpg");
assert_eq!(c.get_program(), "explorer");
let args: Vec<&std::ffi::OsStr> = c.get_args().collect();
assert_eq!(args, [r#"/select,"C:\My Photos\a.jpg""#]);
}
#[test]
fn a_control_port_that_is_not_a_number_is_ignored() {
assert_eq!(control_port_from("--control", Some("7980".into())), Some(7980));
+2
View File
@@ -1103,6 +1103,8 @@
"Show destination gamut warning": "Показывать предупреждение о выходе за гамму устройства вывода",
"Show display gamut warning": "Показывать предупреждение о выходе за гамму дисплея",
"Show file names": "Показывать имена файлов",
"Show in Explorer": "Показать в Проводнике",
"Show in File Manager": "Показать в файловом менеджере",
"Show in Finder": "Показать в Finder",
"Show on Map": "Показать на карте",
"Show overlay (O)": "Показать наложение (O)",
+1 -1
View File
@@ -1246,7 +1246,7 @@ fn folder_menu_for_library(app: &mut LightcraftApp, resp: &egui::Response, n: &F
ui.close();
}
if app.services.reveal.is_some()
&& ui.button(crate::i18n::tr("Show in Finder")).clicked()
&& ui.button(crate::i18n::tr(crate::menus::reveal_label())).clicked()
&& let Some(f) = app.services.reveal.as_mut()
{
let _ = f(path);