Cargo.lock 🔗
@@ -8778,7 +8778,6 @@ dependencies = [
  "serde_json",
  "serde_json_lenient",
  "settings",
- "shlex",
  "smol",
  "task",
  "text",
  Jakub Konka created
A couple of caveats:
- We should not auto-escape arguments with Alacritty's `escape_args`
option if using CMD otherwise, the generated command will have way too
many escaped characters for CMD to parse correctly.
- When composing a full command for CMD, we need to put it in double
quotes manually: `cmd /C "activate.bat& pwsh.exe -C do_something"` so
that CMD executes the entire string as a sequence of commands.
- CMD requires `&` as a chaining operator for commands (`;` for other
shells).
Release Notes:
- N/A
  
  
  
Cargo.lock                       |  1 -
crates/languages/Cargo.toml      |  1 -
crates/languages/src/python.rs   | 13 ++-----------
crates/project/src/terminals.rs  | 15 ++++++++++++---
crates/task/src/shell_builder.rs |  2 +-
crates/task/src/task.rs          |  9 +++++++++
crates/terminal/src/terminal.rs  |  5 ++++-
crates/util/src/shell.rs         | 21 ++++++++++++++++++++-
8 files changed, 48 insertions(+), 19 deletions(-)
@@ -8778,7 +8778,6 @@ dependencies = [
  "serde_json",
  "serde_json_lenient",
  "settings",
- "shlex",
  "smol",
  "task",
  "text",
  @@ -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
  @@ -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}"));
  @@ -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,
  @@ -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
  @@ -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;
  @@ -409,6 +409,7 @@ impl TerminalBuilder {
             events_rx,
         })
     }
+
     pub fn new(
         working_directory: Option<PathBuf>,
         task: Option<TaskState>,
@@ -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,
             }
         };
 
  @@ -353,7 +353,7 @@ impl ShellKind {
         }
     }
 
-    pub fn command_prefix(&self) -> Option<char> {
+    pub const fn command_prefix(&self) -> Option<char> {
         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<Cow<'a, str>> {
         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",
+        }
+    }
 }