aboutsummaryrefslogtreecommitdiff
path: root/src/platform/windows.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-05 20:18:24 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-05 20:19:13 +0300
commit220b99a9fabf10fad83a570f641ad39737d85875 (patch)
treea27a3b791f110d091ca6ef9b194bb6c1fb4e2b55 /src/platform/windows.rs
parent2bc1724c2dae72184a5d2ec070e30c70dc519f9b (diff)
fix: support custom command keybindings on windows
refs #1041
Diffstat (limited to 'src/platform/windows.rs')
-rw-r--r--src/platform/windows.rs107
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()));