diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-05 20:18:24 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-05 20:19:13 +0300 |
| commit | 220b99a9fabf10fad83a570f641ad39737d85875 (patch) | |
| tree | a27a3b791f110d091ca6ef9b194bb6c1fb4e2b55 /src/platform/windows.rs | |
| parent | 2bc1724c2dae72184a5d2ec070e30c70dc519f9b (diff) | |
fix: support custom command keybindings on windows
refs #1041
Diffstat (limited to 'src/platform/windows.rs')
| -rw-r--r-- | src/platform/windows.rs | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 50e0e905..cc486ace 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -46,6 +46,44 @@ struct WindowsProcessEntry { pub fn raise_server_nofile_limit() {} +fn raw_command_shell(comspec: Option<std::ffi::OsString>) -> std::ffi::OsString { + comspec + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| r"C:\Windows\System32\cmd.exe".into()) +} + +pub(crate) fn detached_custom_command_process_platform(command: &str) -> std::process::Command { + detached_custom_command_process_with_comspec(command, std::env::var_os("ComSpec")) +} + +fn detached_custom_command_process_with_comspec( + command: &str, + comspec: Option<std::ffi::OsString>, +) -> std::process::Command { + use std::os::windows::process::CommandExt; + + let mut process = std::process::Command::new(raw_command_shell(comspec)); + process.arg("/d").arg("/c").raw_arg(command); + process +} + +pub(crate) fn pane_custom_command_pty_builder_platform( + command: &str, +) -> portable_pty::CommandBuilder { + pane_custom_command_pty_builder_with_comspec(command, std::env::var_os("ComSpec")) +} + +fn pane_custom_command_pty_builder_with_comspec( + command: &str, + comspec: Option<std::ffi::OsString>, +) -> portable_pty::CommandBuilder { + let mut builder = portable_pty::CommandBuilder::new(raw_command_shell(comspec)); + builder.arg("/d"); + builder.arg("/c"); + builder.raw_arg(command); + builder +} + pub(crate) fn scrollback_editor_argv(path: &std::path::Path) -> std::io::Result<Vec<String>> { let editor = std::env::var("VISUAL") .ok() @@ -537,6 +575,75 @@ mod tests { time::{Duration, Instant}, }; + fn argv_strings(argv: &[std::ffi::OsString]) -> Vec<String> { + argv.into_iter() + .map(|arg| arg.to_string_lossy().into_owned()) + .collect() + } + + #[test] + fn pane_custom_command_uses_cmd() { + let builder = super::pane_custom_command_pty_builder_with_comspec( + "echo hello", + Some(r"C:\Windows\System32\cmd.exe".into()), + ); + + assert_eq!( + argv_strings(builder.get_argv()), + [r"C:\Windows\System32\cmd.exe", "/d", "/c"] + ); + } + + #[test] + fn detached_custom_command_uses_cmd() { + let expected_shell = std::env::var_os("ComSpec") + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| r"C:\Windows\System32\cmd.exe".into()) + .to_string_lossy() + .into_owned(); + + let process = super::detached_custom_command_process_platform("echo hello"); + + assert_eq!(process.get_program().to_string_lossy(), expected_shell); + assert_eq!( + process + .get_args() + .map(|arg| arg.to_string_lossy().into_owned()) + .collect::<Vec<_>>(), + ["/d", "/c", "echo hello"] + ); + } + + #[test] + fn custom_command_falls_back_when_comspec_is_empty() { + let builder = + super::pane_custom_command_pty_builder_with_comspec("echo hello", Some("".into())); + + assert_eq!( + argv_strings(builder.get_argv()), + [r"C:\Windows\System32\cmd.exe", "/d", "/c"] + ); + } + + #[test] + fn detached_custom_command_preserves_quoted_command_tail() { + let path = std::env::temp_dir().join(format!( + "herdr-raw-command-quotes-{}.txt", + std::process::id() + )); + let command = format!(r#"echo "hi" > "{}""#, path.display()); + + let status = super::detached_custom_command_process_platform(&command) + .status() + .expect("spawn raw command"); + + assert!(status.success(), "{status:?}"); + let content = std::fs::read_to_string(&path).expect("read command output"); + let _ = std::fs::remove_file(&path); + assert!(content.contains(r#""hi""#), "{content:?}"); + assert!(!content.contains(r#"\"hi\""#), "{content:?}"); + } + #[test] fn windows_process_cwd_reads_child_launch_directory() { let cwd = std::env::temp_dir().join(format!("herdr-cwd-test-{}", std::process::id())); |
