acp_thread: If available, use git bash over powershell in terminal tool (#39466)

Release Notes:

- When git bash is installed, agents will now use that over powershell
when invoking terminal commands
This commit is contained in:
Lukas Wirth
2025-10-06 13:39:19 +02:00
committed by GitHub
parent d1e2a1f20c
commit 4bd7ef8bad
7 changed files with 65 additions and 19 deletions
Generated
+1
View File
@@ -20340,6 +20340,7 @@ dependencies = [
"tendril",
"unicase",
"walkdir",
"which 6.0.3",
"workspace-hack",
"zed-collections",
"zed-util-macros",
+13 -4
View File
@@ -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<Result<Entity<Terminal>>> {
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);
+14 -5
View File
@@ -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();
+4 -2
View File
@@ -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(),
}
}
}
+1
View File
@@ -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]
+29 -7
View File
@@ -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<String> {
static GIT_BASH: LazyLock<Option<String>> = 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<Path>) -> 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
+3 -1
View File
@@ -999,7 +999,9 @@ pub fn default<D: 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<O> {