diff --git a/Cargo.lock b/Cargo.lock index c1c7e0b2ecf765b5243efd3229aa8d25a5c67b5c..1488874d6b2637203e1dfe5a3a73e447a38c3cad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9301,6 +9301,7 @@ dependencies = [ "serde_json_lenient", "settings", "sha2", + "shlex", "smol", "snippet_provider", "task", diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index f8bc6c353bad4cca5cf6c5dbaa14f0e2927a1800..a25b95701571421f2266034e20b39ac6dcaabebf 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -1607,7 +1607,7 @@ impl AcpThreadView { task.shell = shell; let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| { - terminal_panel.spawn_task(&login, window, cx) + terminal_panel.spawn_task(login.clone(), window, cx) })?; let terminal = terminal.await?; diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index e09b27d4742d4660cffb3d86905fb67268e617fa..9390c785f10e788a6ddea9efddca70f2b8514908 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -92,6 +92,7 @@ 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 fd887d1be5922f358c21e30d658b81645b47eaa5..f5ce2cee5e89e4141277d195849f6fe3dc95dc62 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -918,9 +918,12 @@ impl ToolchainLister for PythonToolchainProvider { ShellKind::Cmd => "activate.bat", }; let path = prefix.join(BINARY_DIR).join(activate_script_name); - if fs.is_file(&path).await { - activation_script - .push(format!("{activate_keyword} \"{}\"", path.display())); + + if let Ok(quoted) = + shlex::try_quote(&path.to_string_lossy()).map(Cow::into_owned) + && 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 e7733e53803c8d7073ab6fe0255da70264224258..3dffe85420a7a9997237052ad5a863d168e6c4b7 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -197,7 +197,7 @@ impl Project { )?, }, None => match activation_script.clone() { - #[cfg(not(target_os = "windows"))] + #[cfg(not(windows))] activation_script if !activation_script.is_empty() => { let activation_script = activation_script.join("; "); let to_run = if let Some(command) = spawn_task.command { @@ -214,7 +214,25 @@ impl Project { program: shell, args: vec![ "-c".to_owned(), - format!("{activation_script}; {to_run}",), + // alacritty formats all args into a single string literally without extra quoting before handing it off to powershell + // so we work around this here + if cfg!(windows) { + println!( + "{}", + shlex::try_quote(&format!( + "{activation_script}; {to_run}", + )) + .unwrap() + .into_owned() + ); + shlex::try_quote(&format!( + "{activation_script}; {to_run}", + )) + .unwrap() + .into_owned() + } else { + format!("{activation_script}; {to_run}",) + }, ], title_override: None, } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 96271ea771e3fdbe42b03504797ba78170d79096..b11948540874ec8205e8a9e5225e26a9eedf5f03 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -531,10 +531,14 @@ impl TerminalBuilder { }, }; - if cfg!(not(target_os = "windows")) && !activation_script.is_empty() && no_task { + if !activation_script.is_empty() && no_task { for activation_script in activation_script { terminal.input(activation_script.into_bytes()); - terminal.write_to_pty(b"\n"); + terminal.write_to_pty(if cfg!(windows) { + &b"\r\n"[..] + } else { + &b"\n"[..] + }); } terminal.clear(); } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 44d64c5fe3351d4c3e2a9342bfaf818445d78736..e72175572b1fcc239b6c1aee12c0a822b3f6c64c 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -19,7 +19,7 @@ use itertools::Itertools; use project::{Fs, Project, ProjectEntryId}; use search::{BufferSearchBar, buffer_search::DivRegistrar}; use settings::Settings; -use task::{RevealStrategy, RevealTarget, ShellBuilder, SpawnInTerminal, TaskId}; +use task::{RevealStrategy, RevealTarget, SpawnInTerminal, TaskId}; use terminal::{ Terminal, terminal_settings::{TerminalDockPosition, TerminalSettings}, @@ -496,42 +496,10 @@ impl TerminalPanel { pub fn spawn_task( &mut self, - task: &SpawnInTerminal, + task: SpawnInTerminal, window: &mut Window, cx: &mut Context, ) -> Task>> { - let remote_client = self - .workspace - .update(cx, |workspace, cx| { - let project = workspace.project().read(cx); - if project.is_via_collab() { - Err(anyhow!("cannot spawn tasks as a guest")) - } else { - Ok(project.remote_client()) - } - }) - .flatten(); - - let remote_client = match remote_client { - Ok(remote_client) => remote_client, - Err(e) => return Task::ready(Err(e)), - }; - - let remote_shell = remote_client - .as_ref() - .and_then(|remote_client| remote_client.read(cx).shell()); - - let builder = ShellBuilder::new(remote_shell.as_deref(), &task.shell); - let command_label = builder.command_label(&task.command_label); - let (command, args) = builder.build(task.command.clone(), &task.args); - - let task = SpawnInTerminal { - command_label, - command: Some(command), - args, - ..task.clone() - }; - if task.allow_concurrent_runs && task.use_new_terminal { return self.spawn_in_new_terminal(task, window, cx); } @@ -1565,7 +1533,7 @@ impl workspace::TerminalProvider for TerminalProvider { window.spawn(cx, async move |cx| { let terminal = terminal_panel .update_in(cx, |terminal_panel, window, cx| { - terminal_panel.spawn_task(&task, window, cx) + terminal_panel.spawn_task(task, window, cx) }) .ok()? .await;