Cargo.lock 🔗
@@ -20340,6 +20340,7 @@ dependencies = [
"tendril",
"unicase",
"walkdir",
+ "which 6.0.3",
"workspace-hack",
"zed-collections",
"zed-util-macros",
Lukas Wirth created
Release Notes:
- When git bash is installed, agents will now use that over powershell
when invoking terminal commands
Cargo.lock | 1
crates/acp_thread/src/acp_thread.rs | 17 ++++++++--
crates/assistant_tools/src/terminal_tool.rs | 19 ++++++++---
crates/prompt_store/src/prompts.rs | 6 ++-
crates/util/Cargo.toml | 1
crates/util/src/shell.rs | 36 ++++++++++++++++++----
crates/util/src/util.rs | 4 +
7 files changed, 65 insertions(+), 19 deletions(-)
@@ -20340,6 +20340,7 @@ dependencies = [
"tendril",
"unicase",
"walkdir",
+ "which 6.0.3",
"workspace-hack",
"zed-collections",
"zed-util-macros",
@@ -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);
@@ -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();
@@ -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(),
}
}
}
@@ -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,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
@@ -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> {