diff --git a/Cargo.lock b/Cargo.lock index 6992c5f9ec6f83118870bc0a1e1220eb96bd9fd5..cbcf66a20301cfcff8752d2c7e38b5c2b66ea8c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20340,6 +20340,7 @@ dependencies = [ "tendril", "unicase", "walkdir", + "which 6.0.3", "workspace-hack", "zed-collections", "zed-util-macros", diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index ed4441b9cdd28978dd8a74385824193e7e3df2a5..61486a475c4601a9d9201d3a6920c63566a1ba36 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -12,7 +12,7 @@ use language::language_settings::FormatOnSave; pub use mention::*; use project::lsp_store::{FormatTrigger, LspFormatTarget}; use serde::{Deserialize, Serialize}; -use settings::Settings as _; +use settings::{Settings as _, SettingsLocation}; use task::{Shell, ShellBuilder}; pub use terminal::*; @@ -35,7 +35,7 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt::Display, mem, path::PathBuf, sync::Arc}; use ui::App; -use util::{ResultExt, get_default_system_shell}; +use util::{ResultExt, get_default_system_shell_preferring_bash}; use uuid::Uuid; #[derive(Debug)] @@ -2086,7 +2086,16 @@ impl AcpThread { ) -> Task>> { let env = match &cwd { Some(dir) => self.project.update(cx, |project, cx| { - let shell = TerminalSettings::get_global(cx).shell.clone(); + let worktree = project.find_worktree(dir.as_path(), cx); + let shell = TerminalSettings::get( + worktree.as_ref().map(|(worktree, path)| SettingsLocation { + worktree_id: worktree.read(cx).id(), + path: &path, + }), + cx, + ) + .shell + .clone(); project.directory_environment(&shell, dir.as_path().into(), cx) }), None => Task::ready(None).shared(), @@ -2115,7 +2124,7 @@ impl AcpThread { .remote_client() .and_then(|r| r.read(cx).default_system_shell()) })? - .unwrap_or_else(|| get_default_system_shell()); + .unwrap_or_else(|| get_default_system_shell_preferring_bash()); let (task_command, task_args) = ShellBuilder::new(&Shell::Program(shell)) .redirect_stdin_to_dev_null() .build(Some(command.clone()), &args); diff --git a/crates/assistant_tools/src/terminal_tool.rs b/crates/assistant_tools/src/terminal_tool.rs index b38c811faa494af8223a07d1f339934241956c47..db85863f2eca6c9970eecce33e164577007a3100 100644 --- a/crates/assistant_tools/src/terminal_tool.rs +++ b/crates/assistant_tools/src/terminal_tool.rs @@ -18,7 +18,7 @@ use portable_pty::{CommandBuilder, PtySize, native_pty_system}; use project::Project; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::Settings; +use settings::{Settings, SettingsLocation}; use std::{ env, path::{Path, PathBuf}, @@ -32,8 +32,8 @@ use terminal_view::TerminalView; use theme::ThemeSettings; use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*}; use util::{ - ResultExt, get_default_system_shell, markdown::MarkdownInlineCode, size::format_file_size, - time::duration_alt_display, + ResultExt, get_default_system_shell_preferring_bash, markdown::MarkdownInlineCode, + size::format_file_size, time::duration_alt_display, }; use workspace::Workspace; @@ -122,7 +122,16 @@ impl Tool for TerminalTool { let cwd = working_dir.clone(); let env = match &cwd { Some(dir) => project.update(cx, |project, cx| { - let shell = TerminalSettings::get_global(cx).shell.clone(); + let worktree = project.find_worktree(dir.as_path(), cx); + let shell = TerminalSettings::get( + worktree.as_ref().map(|(worktree, path)| SettingsLocation { + worktree_id: worktree.read(cx).id(), + path: &path, + }), + cx, + ) + .shell + .clone(); project.directory_environment(&shell, dir.as_path().into(), cx) }), None => Task::ready(None).shared(), @@ -133,7 +142,7 @@ impl Tool for TerminalTool { .remote_client() .and_then(|r| r.read(cx).default_system_shell()) }) - .unwrap_or_else(|| get_default_system_shell()); + .unwrap_or_else(|| get_default_system_shell_preferring_bash()); let env = cx.spawn(async move |_| { let mut env = env.await.unwrap_or_default(); diff --git a/crates/prompt_store/src/prompts.rs b/crates/prompt_store/src/prompts.rs index e95c63b8308c456a6e850f1610e33d8d185ff571..8790e8039957632dcc9508839ada1f6ac026e174 100644 --- a/crates/prompt_store/src/prompts.rs +++ b/crates/prompt_store/src/prompts.rs @@ -14,7 +14,9 @@ use std::{ time::Duration, }; use text::LineEnding; -use util::{ResultExt, get_default_system_shell, rel_path::RelPath}; +use util::{ + ResultExt, get_default_system_shell_preferring_bash, rel_path::RelPath, shell::ShellKind, +}; use crate::UserPromptId; @@ -43,7 +45,7 @@ impl ProjectContext { user_rules: default_user_rules, os: std::env::consts::OS.to_string(), arch: std::env::consts::ARCH.to_string(), - shell: get_default_system_shell(), + shell: ShellKind::new(&get_default_system_shell_preferring_bash()).to_string(), } } } diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index cb7aaec3745ff39108c13cd03a48ce4ed9232c9a..da500edd1bd6bfcb608804468fdc56c56c35395f 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -44,6 +44,7 @@ tempfile.workspace = true unicase.workspace = true util_macros = { workspace = true, optional = true } walkdir.workspace = true +which.workspace = true workspace-hack.workspace = true [target.'cfg(unix)'.dependencies] diff --git a/crates/util/src/shell.rs b/crates/util/src/shell.rs index 640d191edf6da943c1298a97531020d3a8464962..95f7953ede40729749592d173800f9ddf159ceda 100644 --- a/crates/util/src/shell.rs +++ b/crates/util/src/shell.rs @@ -29,6 +29,30 @@ pub fn get_default_system_shell() -> String { } } +/// Get the default system shell, preferring git-bash on Windows. +pub fn get_default_system_shell_preferring_bash() -> String { + if cfg!(windows) { + get_windows_git_bash().unwrap_or_else(|| get_windows_system_shell()) + } else { + "/bin/sh".to_string() + } +} + +pub fn get_windows_git_bash() -> Option { + static GIT_BASH: LazyLock> = LazyLock::new(|| { + // /path/to/git/cmd/git.exe/../../bin/bash.exe + let git = which::which("git").ok()?; + let git_bash = git.parent()?.parent()?.join("bin").join("bash.exe"); + if git_bash.is_file() { + Some(git_bash.to_string_lossy().to_string()) + } else { + None + } + }); + + (*GIT_BASH).clone() +} + pub fn get_windows_system_shell() -> String { use std::path::PathBuf; @@ -152,20 +176,16 @@ impl ShellKind { pub fn new(program: impl AsRef) -> Self { let program = program.as_ref(); - let Some(program) = program.file_name().and_then(|s| s.to_str()) else { + let Some(program) = program.file_stem().and_then(|s| s.to_str()) else { return if cfg!(windows) { ShellKind::PowerShell } else { ShellKind::Posix }; }; - if program == "powershell" - || program.ends_with("powershell.exe") - || program == "pwsh" - || program.ends_with("pwsh.exe") - { + if program == "powershell" || program == "pwsh" { ShellKind::PowerShell - } else if program == "cmd" || program.ends_with("cmd.exe") { + } else if program == "cmd" { ShellKind::Cmd } else if program == "nu" { ShellKind::Nushell @@ -177,6 +197,8 @@ impl ShellKind { ShellKind::Tcsh } else if program == "rc" { ShellKind::Rc + } else if program == "sh" || program == "bash" { + ShellKind::Posix } else { if cfg!(windows) { ShellKind::PowerShell diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index a54aa209cf425cbfa697c6bff13423c93b82cf47..1f21b9c10b539383b0efb2866a4da8819cceb20e 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -999,7 +999,9 @@ pub fn default() -> D { Default::default() } -pub use self::shell::{get_default_system_shell, get_system_shell}; +pub use self::shell::{ + get_default_system_shell, get_default_system_shell_preferring_bash, get_system_shell, +}; #[derive(Debug)] pub enum ConnectionResult {