launcher: log when a mode-row action fails to launch

run_mode_action discarded spawn()'s Result with 'let _ =' for both
RunShell and OpenUrl, with no log line either way. If xdg-open is
missing, or the shell/spawn fails for any reason, pressing Enter on a
'>'-command or '.'-URL row did nothing at all with zero diagnostic —
worse than logging nothing being silent, there was no way to even
suspect what happened.

Log to stderr on Err for both variants. Also pull the URL
scheme-adding logic out into a pure url_open_target() helper, covered
by two new tests, so it's exercised without spawning a real process.
This commit is contained in:
Breadway 2026-08-25 16:21:23 +08:00
parent 2cbdb58f37
commit 030cf28096

View file

@ -2588,31 +2588,44 @@ fn mode_row_action(row: &gtk4::ListBoxRow) -> Option<ModeAction> {
unsafe { row.data::<ModeAction>("mode_action").map(|p| p.as_ref().clone()) } unsafe { row.data::<ModeAction>("mode_action").map(|p| p.as_ref().clone()) }
} }
/// Pure half of the `.`-mode URL action: adds a scheme when the user typed
/// a bare host (`example.com` -> `https://example.com`), leaves anything
/// that already looks like `scheme://...` untouched.
fn url_open_target(url: &str) -> String {
if url.contains("://") {
url.to_string()
} else {
format!("https://{url}")
}
}
fn run_mode_action(action: &ModeAction) { fn run_mode_action(action: &ModeAction) {
match action { match action {
ModeAction::RunShell(cmd) => { ModeAction::RunShell(cmd) => {
let _ = std::process::Command::new("bash") if let Err(e) = std::process::Command::new("bash")
.args(["-c", cmd]) .args(["-c", cmd])
.stdin(std::process::Stdio::null()) .stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null()) .stderr(std::process::Stdio::null())
.spawn(); .spawn()
{
eprintln!("breadbar: failed to run mode command {cmd:?}: {e}");
}
} }
ModeAction::OpenUrl(url) => { ModeAction::OpenUrl(url) => {
// No scheme-adding shell involved — `xdg-open` gets the raw // No scheme-adding shell involved — `xdg-open` gets the raw
// argument, so nothing in a `.`-mode query is ever parsed as // argument, so nothing in a `.`-mode query is ever parsed as
// shell syntax. // shell syntax.
let target = if url.contains("://") { let target = url_open_target(url);
url.clone() if let Err(e) = std::process::Command::new("xdg-open")
} else { .arg(&target)
format!("https://{url}")
};
let _ = std::process::Command::new("xdg-open")
.arg(target)
.stdin(std::process::Stdio::null()) .stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null()) .stderr(std::process::Stdio::null())
.spawn(); .spawn()
{
eprintln!("breadbar: failed to open url {target:?}: {e}");
}
} }
} }
} }
@ -3029,3 +3042,22 @@ mod launcher_route_tests {
); );
} }
} }
#[cfg(test)]
mod url_open_target_tests {
use super::url_open_target;
#[test]
fn bare_host_gets_https_scheme() {
assert_eq!(url_open_target("example.com"), "https://example.com");
}
#[test]
fn existing_scheme_is_left_untouched() {
assert_eq!(url_open_target("http://example.com"), "http://example.com");
assert_eq!(
url_open_target("ftp://example.com/file"),
"ftp://example.com/file"
);
}
}