mirror of
https://github.com/alacritty/alacritty.git
synced 2025-11-06 22:44:18 -05:00
Add alacritty_terminal option to escape pty args
This adds a new `escape_args` option to `tty::Options` on Windows, which can be used to automatically escape all arguments passed to the shell. While useful to automatically make most arguments work on Windows, there are some scenarios where it is not possible for users to properly specify arguments with this option enabled (e.g.: `cmd /c`). An option should always be present to disable this option when used. The implementation is based on the `Command` code in Rust's STD lib.
This commit is contained in:
parent
f07622e908
commit
84377a45a8
5 changed files with 122 additions and 5 deletions
|
|
@ -200,6 +200,8 @@ impl From<TerminalOptions> for PtyOptions {
|
|||
shell: options.command().map(Into::into),
|
||||
drain_on_exit: options.hold,
|
||||
env: HashMap::new(),
|
||||
#[cfg(target_os = "windows")]
|
||||
escape_args: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,14 @@ impl UiConfig {
|
|||
let shell = self.terminal.shell.clone().or_else(|| self.shell.clone()).map(Into::into);
|
||||
let working_directory =
|
||||
self.working_directory.clone().or_else(|| self.general.working_directory.clone());
|
||||
PtyOptions { working_directory, shell, drain_on_exit: false, env: HashMap::new() }
|
||||
PtyOptions {
|
||||
working_directory,
|
||||
shell,
|
||||
drain_on_exit: false,
|
||||
env: HashMap::new(),
|
||||
#[cfg(target_os = "windows")]
|
||||
escape_args: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## 0.25.1-dev
|
||||
|
||||
### Added
|
||||
|
||||
- New `escape_args` field on `tty::Options` for Windows shell argument escaping control
|
||||
|
||||
### Changed
|
||||
|
||||
- Pass `-q` to `login` on macOS if `~/.hushlogin` is present
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ pub struct Options {
|
|||
|
||||
/// Extra environment variables.
|
||||
pub env: HashMap<String, String>,
|
||||
|
||||
/// Specifies whether the Windows shell arguments should be escaped.
|
||||
///
|
||||
/// - When `true`: Arguments will be escaped according to the standard C runtime rules.
|
||||
/// - When `false`: Arguments will be passed raw without additional escaping.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub escape_args: bool,
|
||||
}
|
||||
|
||||
/// Shell options.
|
||||
|
|
|
|||
|
|
@ -125,14 +125,52 @@ impl OnResize for Pty {
|
|||
}
|
||||
}
|
||||
|
||||
// Modified per stdlib implementation.
|
||||
// https://github.com/rust-lang/rust/blob/6707bf0f59485cf054ac1095725df43220e4be20/library/std/src/sys/args/windows.rs#L174
|
||||
fn push_escaped_arg(cmd: &mut String, arg: &str) {
|
||||
let arg_bytes = arg.as_bytes();
|
||||
let quote = arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') || arg_bytes.is_empty();
|
||||
if quote {
|
||||
cmd.push('"');
|
||||
}
|
||||
|
||||
let mut backslashes: usize = 0;
|
||||
for x in arg.chars() {
|
||||
if x == '\\' {
|
||||
backslashes += 1;
|
||||
} else {
|
||||
if x == '"' {
|
||||
// Add n+1 backslashes to total 2n+1 before internal '"'.
|
||||
cmd.extend((0..=backslashes).map(|_| '\\'));
|
||||
}
|
||||
backslashes = 0;
|
||||
}
|
||||
cmd.push(x);
|
||||
}
|
||||
|
||||
if quote {
|
||||
// Add n backslashes to total 2n before ending '"'.
|
||||
cmd.extend((0..backslashes).map(|_| '\\'));
|
||||
cmd.push('"');
|
||||
}
|
||||
}
|
||||
|
||||
fn cmdline(config: &Options) -> String {
|
||||
let default_shell = Shell::new("powershell".to_owned(), Vec::new());
|
||||
let shell = config.shell.as_ref().unwrap_or(&default_shell);
|
||||
|
||||
once(shell.program.as_str())
|
||||
.chain(shell.args.iter().map(|s| s.as_str()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
let mut cmd = String::new();
|
||||
cmd.push_str(&shell.program);
|
||||
|
||||
for arg in &shell.args {
|
||||
cmd.push(' ');
|
||||
if config.escape_args {
|
||||
push_escaped_arg(&mut cmd, arg);
|
||||
} else {
|
||||
cmd.push_str(arg)
|
||||
}
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Converts the string slice into a Windows-standard representation for "W"-
|
||||
|
|
@ -140,3 +178,62 @@ fn cmdline(config: &Options) -> String {
|
|||
pub fn win32_string<S: AsRef<OsStr> + ?Sized>(value: &S) -> Vec<u16> {
|
||||
OsStr::new(value).encode_wide().chain(once(0)).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::tty::windows::{cmdline, push_escaped_arg};
|
||||
use crate::tty::{Options, Shell};
|
||||
|
||||
#[test]
|
||||
fn test_escape() {
|
||||
let test_set = vec![
|
||||
// Basic cases - no escaping needed
|
||||
("abc", "abc"),
|
||||
// Cases requiring quotes (space/tab)
|
||||
("", "\"\""),
|
||||
(" ", "\" \""),
|
||||
("ab c", "\"ab c\""),
|
||||
("ab\tc", "\"ab\tc\""),
|
||||
// Cases with backslashes only (no spaces, no quotes) - no quotes added
|
||||
("ab\\c", "ab\\c"),
|
||||
// Cases with quotes only (no spaces) - quotes escaped but no outer quotes
|
||||
("ab\"c", "ab\\\"c"),
|
||||
("\"", "\\\""),
|
||||
("a\"b\"c", "a\\\"b\\\"c"),
|
||||
// Cases requiring both quotes and escaping (contains spaces)
|
||||
("ab \"c", "\"ab \\\"c\""),
|
||||
("a \"b\" c", "\"a \\\"b\\\" c\""),
|
||||
// Complex real-world cases
|
||||
("C:\\Program Files\\", "\"C:\\Program Files\\\\\""),
|
||||
("C:\\Program Files\\a.txt", "\"C:\\Program Files\\a.txt\""),
|
||||
(
|
||||
r#"sh -c "cd /home/user; ARG='abc' \""'${SHELL:-sh}" -i -c '"'echo hello'""#,
|
||||
r#""sh -c \"cd /home/user; ARG='abc' \\\"\"'${SHELL:-sh}\" -i -c '\"'echo hello'\"""#,
|
||||
),
|
||||
];
|
||||
|
||||
for (input, expected) in test_set {
|
||||
let mut escaped_arg = String::new();
|
||||
push_escaped_arg(&mut escaped_arg, input);
|
||||
assert_eq!(escaped_arg, expected, "Failed for input: {}", input);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmdline() {
|
||||
let mut options = Options {
|
||||
shell: Some(Shell {
|
||||
program: "echo".to_string(),
|
||||
args: vec!["hello world".to_string()],
|
||||
}),
|
||||
working_directory: None,
|
||||
drain_on_exit: true,
|
||||
env: Default::default(),
|
||||
escape_args: false,
|
||||
};
|
||||
assert_eq!(cmdline(&options), "echo hello world");
|
||||
|
||||
options.escape_args = true;
|
||||
assert_eq!(cmdline(&options), "echo \"hello world\"");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue