diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index 9d4973c94dc952eb421a93b14137458c4040810d..41aff48a2092645764d598684d13c1ce61704c44 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -11,8 +11,6 @@ use project::agent_server_store::AgentServerCommand; use serde::Deserialize; use settings::Settings as _; use task::ShellBuilder; -#[cfg(windows)] -use task::ShellKind; use util::ResultExt as _; use std::path::PathBuf; @@ -92,23 +90,8 @@ impl AcpConnection { ) -> Result { let shell = cx.update(|cx| TerminalSettings::get(None, cx).shell.clone())?; let builder = ShellBuilder::new(&shell, cfg!(windows)); - #[cfg(windows)] - let kind = builder.kind(); - let (cmd, args) = builder.build(Some(command.path.display().to_string()), &command.args); - - let mut child = util::command::new_smol_command(cmd); - #[cfg(windows)] - if kind == ShellKind::Cmd { - use smol::process::windows::CommandExt; - for arg in args { - child.raw_arg(arg); - } - } else { - child.args(args); - } - #[cfg(not(windows))] - child.args(args); - + let mut child = + builder.build_command(Some(command.path.display().to_string()), &command.args); child .envs(command.env.iter().flatten()) .stdin(std::process::Stdio::piped()) diff --git a/crates/context_server/src/transport/stdio_transport.rs b/crates/context_server/src/transport/stdio_transport.rs index 035a1ccb9d413d0bfab2e5c06b87ff293360f8f0..031f348294c04381f1e259b20c7cc818844953b4 100644 --- a/crates/context_server/src/transport/stdio_transport.rs +++ b/crates/context_server/src/transport/stdio_transport.rs @@ -33,12 +33,10 @@ impl StdioTransport { ) -> Result { let shell = cx.update(|cx| TerminalSettings::get(None, cx).shell.clone())?; let builder = ShellBuilder::new(&shell, cfg!(windows)); - let (command, args) = - builder.build(Some(binary.executable.display().to_string()), &binary.args); + let mut command = + builder.build_command(Some(binary.executable.display().to_string()), &binary.args); - let mut command = util::command::new_smol_command(command); command - .args(args) .envs(binary.env.unwrap_or_default()) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) diff --git a/crates/project/src/debugger/locators/cargo.rs b/crates/project/src/debugger/locators/cargo.rs index 1bafd256ad8589e354b0df332715904914d608dd..2f7d8cdc5f20d2ae8c463fada572a89d3dec2da7 100644 --- a/crates/project/src/debugger/locators/cargo.rs +++ b/crates/project/src/debugger/locators/cargo.rs @@ -115,18 +115,17 @@ impl DapLocator for CargoLocator { .clone() .context("Couldn't get cwd from debug config which is needed for locators")?; let builder = ShellBuilder::new(&build_config.shell, cfg!(windows)).non_interactive(); - let (program, args) = builder.build( - Some("cargo".into()), - &build_config - .args - .iter() - .cloned() - .take_while(|arg| arg != "--") - .chain(Some("--message-format=json".to_owned())) - .collect::>(), - ); - let mut child = util::command::new_smol_command(program) - .args(args) + let mut child = builder + .build_command( + Some("cargo".into()), + &build_config + .args + .iter() + .cloned() + .take_while(|arg| arg != "--") + .chain(Some("--message-format=json".to_owned())) + .collect::>(), + ) .envs(build_config.env.iter().map(|(k, v)| (k.clone(), v.clone()))) .current_dir(cwd) .stdout(Stdio::piped()) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 4ccf639507cd53ba9779ce18c1550fdc4c50556e..ab89787fc88510f4c92e929d96b51c682ff0af61 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -550,7 +550,7 @@ impl TerminalPanel { let builder = ShellBuilder::new(&shell, is_windows); let command_label = builder.command_label(task.command.as_deref().unwrap_or("")); - let (command, args) = builder.build(task.command.clone(), &task.args); + let (command, args) = builder.build_no_quote(task.command.clone(), &task.args); let task = SpawnInTerminal { command_label, diff --git a/crates/util/src/shell.rs b/crates/util/src/shell.rs index a956a39446fbae9cdd74601c2a3c42fa6d9c408b..d51cb39aedd89908db9608f5961688d4b30afc9b 100644 --- a/crates/util/src/shell.rs +++ b/crates/util/src/shell.rs @@ -702,7 +702,10 @@ impl ShellKind { .map(|quoted| Cow::Owned(self.prepend_command_prefix("ed).into_owned())); } } - self.try_quote(arg) + self.try_quote(arg).map(|quoted| match quoted { + unquoted @ Cow::Borrowed(_) => unquoted, + Cow::Owned(quoted) => Cow::Owned(self.prepend_command_prefix("ed).into_owned()), + }) } pub fn split(&self, input: &str) -> Option> { @@ -916,7 +919,7 @@ mod tests { .try_quote_prefix_aware("'uname'") .unwrap() .into_owned(), - "\"'uname'\"".to_string() + "^\"'uname'\"".to_string() ); assert_eq!( shell_kind.try_quote("^uname").unwrap().into_owned(), @@ -949,7 +952,7 @@ mod tests { .try_quote_prefix_aware("'uname a'") .unwrap() .into_owned(), - "\"'uname a'\"".to_string() + "^\"'uname a'\"".to_string() ); assert_eq!( shell_kind.try_quote("^'uname a'").unwrap().into_owned(), diff --git a/crates/util/src/shell_builder.rs b/crates/util/src/shell_builder.rs index 3b9e53eb8e1aab69fc6e2115a432832325e8acb7..436c07172368793e685d1ba4b1014ac38be13b73 100644 --- a/crates/util/src/shell_builder.rs +++ b/crates/util/src/shell_builder.rs @@ -80,27 +80,23 @@ impl ShellBuilder { task_args: &[String], ) -> (String, Vec) { if let Some(task_command) = task_command { - let task_command = self.kind.prepend_command_prefix(&task_command); let task_command = if !task_args.is_empty() { match self.kind.try_quote_prefix_aware(&task_command) { - Some(task_command) => task_command, + Some(task_command) => task_command.into_owned(), None => task_command, } } else { task_command }; - let mut combined_command = - task_args - .iter() - .fold(task_command.into_owned(), |mut command, arg| { - command.push(' '); - let shell_variable = self.kind.to_shell_variable(arg); - command.push_str(&match self.kind.try_quote(&shell_variable) { - Some(shell_variable) => shell_variable, - None => Cow::Owned(shell_variable), - }); - command - }); + let mut combined_command = task_args.iter().fold(task_command, |mut command, arg| { + command.push(' '); + let shell_variable = self.kind.to_shell_variable(arg); + command.push_str(&match self.kind.try_quote(&shell_variable) { + Some(shell_variable) => shell_variable, + None => Cow::Owned(shell_variable), + }); + command + }); if self.redirect_stdin { match self.kind { ShellKind::Fish => { @@ -134,6 +130,90 @@ impl ShellBuilder { (self.program, self.args) } + // This should not exist, but our task infra is broken beyond repair right now + #[doc(hidden)] + pub fn build_no_quote( + mut self, + task_command: Option, + task_args: &[String], + ) -> (String, Vec) { + if let Some(task_command) = task_command { + let mut combined_command = task_args.iter().fold(task_command, |mut command, arg| { + command.push(' '); + command.push_str(&self.kind.to_shell_variable(arg)); + command + }); + if self.redirect_stdin { + match self.kind { + ShellKind::Fish => { + combined_command.insert_str(0, "begin; "); + combined_command.push_str("; end { + combined_command.insert(0, '('); + combined_command.push_str(") { + combined_command.insert_str(0, "$null | & {"); + combined_command.push_str("}"); + } + ShellKind::Cmd => { + combined_command.push_str("< NUL"); + } + } + } + + self.args + .extend(self.kind.args_for_shell(self.interactive, combined_command)); + } + + (self.program, self.args) + } + + /// Builds a command with the given task command and arguments. + /// + /// Prefer this over manually constructing a command with the output of `Self::build`, + /// as this method handles `cmd` weirdness on windows correctly. + pub fn build_command( + self, + mut task_command: Option, + task_args: &[String], + ) -> smol::process::Command { + #[cfg(windows)] + let kind = self.kind; + if task_args.is_empty() { + task_command = task_command + .as_ref() + .map(|cmd| self.kind.try_quote_prefix_aware(&cmd).map(Cow::into_owned)) + .unwrap_or(task_command); + } + let (program, args) = self.build(task_command, task_args); + + let mut child = crate::command::new_smol_command(program); + + #[cfg(windows)] + if kind == ShellKind::Cmd { + use smol::process::windows::CommandExt; + + for arg in args { + child.raw_arg(arg); + } + } else { + child.args(args); + } + + #[cfg(not(windows))] + child.args(args); + + child + } + pub fn kind(&self) -> ShellKind { self.kind } @@ -166,7 +246,7 @@ mod test { vec![ "-i", "-c", - "^echo '$env.hello' '$env.world' nothing '--($env.something)' '$' '${test'" + "echo '$env.hello' '$env.world' nothing '--($env.something)' '$' '${test'" ] ); } @@ -181,7 +261,7 @@ mod test { .build(Some("echo".into()), &["nothing".to_string()]); assert_eq!(program, "nu"); - assert_eq!(args, vec!["-i", "-c", "(^echo nothing)