diff --git a/Cargo.lock b/Cargo.lock index 852ace96a3dd893d97b44be210d963de39416f04..78160c100c81b0f524d8194a31f7e62f7c73d61e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8778,7 +8778,6 @@ dependencies = [ "serde_json", "serde_json_lenient", "settings", - "shlex", "smol", "task", "text", diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 073f3636baba8029411e1833c57846b2233999e2..650a785b4686b8afcd5cfe351d7b31ce76e87970 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -91,7 +91,6 @@ tree-sitter-typescript = { workspace = true, optional = true } tree-sitter-yaml = { workspace = true, optional = true } util.workspace = true workspace-hack.workspace = true -shlex.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 70cbb26db4eead8cdcf144c04173007bea6afcc8..e602aec841c0e734d12e9873f0cd3ad3e116b2e6 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -1180,15 +1180,7 @@ impl ToolchainLister for PythonToolchainProvider { } Some(PythonEnvironmentKind::Venv | PythonEnvironmentKind::VirtualEnv) => { if let Some(prefix) = &toolchain.prefix { - let activate_keyword = match shell { - ShellKind::Cmd => ".", - ShellKind::Nushell => "overlay use", - ShellKind::PowerShell => ".", - ShellKind::Fish => "source", - ShellKind::Csh => "source", - ShellKind::Tcsh => "source", - ShellKind::Posix | ShellKind::Rc => "source", - }; + let activate_keyword = shell.activate_keyword(); let activate_script_name = match shell { ShellKind::Posix | ShellKind::Rc => "activate", ShellKind::Csh => "activate.csh", @@ -1200,8 +1192,7 @@ impl ToolchainLister for PythonToolchainProvider { }; let path = prefix.join(BINARY_DIR).join(activate_script_name); - if let Ok(quoted) = - shlex::try_quote(&path.to_string_lossy()).map(Cow::into_owned) + if let Some(quoted) = shell.try_quote(&path.to_string_lossy()) && fs.is_file(&path).await { activation_script.push(format!("{activate_keyword} {quoted}")); diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index dba842bf94b9e372fe08e17273e158270145e294..db1b8197cfd9849c8eef0575fc08aedcce76547a 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -201,15 +201,24 @@ impl Project { }, None => match activation_script.clone() { activation_script if !activation_script.is_empty() => { - let activation_script = activation_script.join("; "); + let separator = shell_kind.sequential_commands_separator(); + let activation_script = + activation_script.join(&format!("{separator} ")); let to_run = format_to_run(); - let arg = format!("{activation_script}; {to_run}"); + let mut arg = format!("{activation_script}{separator} {to_run}"); + if shell_kind == ShellKind::Cmd { + // We need to put the entire command in quotes since otherwise CMD tries to execute them + // as separate commands rather than chaining one after another. + arg = format!("\"{arg}\""); + } + + let args = shell_kind.args_for_shell(false, arg); ( Shell::WithArguments { program: shell, - args: vec!["-c".to_owned(), arg], + args, title_override: None, }, env, diff --git a/crates/task/src/shell_builder.rs b/crates/task/src/shell_builder.rs index f46c6c754f33ba53bd9f5f18ad1a67a9efbd9732..a32e016df1f8b0650fd0c0b6dfddeb382dde48b8 100644 --- a/crates/task/src/shell_builder.rs +++ b/crates/task/src/shell_builder.rs @@ -49,7 +49,7 @@ impl ShellBuilder { format!("{} -C '{}'", self.program, command_to_use_in_label) } ShellKind::Cmd => { - format!("{} /C '{}'", self.program, command_to_use_in_label) + format!("{} /C \"{}\"", self.program, command_to_use_in_label) } ShellKind::Posix | ShellKind::Nushell diff --git a/crates/task/src/task.rs b/crates/task/src/task.rs index 41abcd622da400f85bce59a1f5e22d1b5726aa6a..9f7a10f2c5cace3a8449cf366fd08755f039cd5d 100644 --- a/crates/task/src/task.rs +++ b/crates/task/src/task.rs @@ -345,6 +345,7 @@ impl Shell { Shell::System => get_system_shell(), } } + pub fn program_and_args(&self) -> (String, &[String]) { match self { Shell::Program(program) => (program.clone(), &[]), @@ -352,6 +353,14 @@ impl Shell { Shell::System => (get_system_shell(), &[]), } } + + pub fn shell_kind(&self) -> ShellKind { + match self { + Shell::Program(program) => ShellKind::new(program), + Shell::WithArguments { program, .. } => ShellKind::new(program), + Shell::System => ShellKind::system(), + } + } } type VsCodeEnvVariable = String; diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index a4f2117a44e7ce95d63f451e9ce87b10a1baeee1..eca98a5eec3189349693af31d146f8e88d9e49ab 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -409,6 +409,7 @@ impl TerminalBuilder { events_rx, }) } + pub fn new( working_directory: Option, task: Option, @@ -507,8 +508,10 @@ impl TerminalBuilder { working_directory: working_directory.clone(), drain_on_exit: true, env: env.clone().into_iter().collect(), + // We do not want to escape arguments if we are using CMD as our shell. + // If we do we end up with too many quotes/escaped quotes for CMD to handle. #[cfg(windows)] - escape_args: true, + escape_args: shell.shell_kind() != util::shell::ShellKind::Cmd, } }; diff --git a/crates/util/src/shell.rs b/crates/util/src/shell.rs index 5c1837d822ddab4e3c1c73f95d34f71307ffccad..cde7c73b7ef6d36c47c383f8c38cd0f2a5fd642b 100644 --- a/crates/util/src/shell.rs +++ b/crates/util/src/shell.rs @@ -353,7 +353,7 @@ impl ShellKind { } } - pub fn command_prefix(&self) -> Option { + pub const fn command_prefix(&self) -> Option { match self { ShellKind::PowerShell => Some('&'), ShellKind::Nushell => Some('^'), @@ -361,6 +361,13 @@ impl ShellKind { } } + pub const fn sequential_commands_separator(&self) -> char { + match self { + ShellKind::Cmd => '&', + _ => ';', + } + } + pub fn try_quote<'a>(&self, arg: &'a str) -> Option> { shlex::try_quote(arg).ok().map(|arg| match self { // If we are running in PowerShell, we want to take extra care when escaping strings. @@ -370,4 +377,16 @@ impl ShellKind { _ => arg, }) } + + pub const fn activate_keyword(&self) -> &'static str { + match self { + ShellKind::Cmd => "", + ShellKind::Nushell => "overlay use", + ShellKind::PowerShell => ".", + ShellKind::Fish => "source", + ShellKind::Csh => "source", + ShellKind::Tcsh => "source", + ShellKind::Posix | ShellKind::Rc => "source", + } + } }