acp_thread: Properly use project terminal API (#38186)

Closes https://github.com/zed-industries/zed/issues/35603

Release Notes:

- Fixed shell selection for terminal tool
This commit is contained in:
Lukas Wirth
2025-09-15 12:43:41 +00:00
committed by GitHub
parent e9b4f59e0f
commit 92b946e8e5
13 changed files with 148 additions and 137 deletions
@@ -52,7 +52,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
assistant_tool::init(cx);
let registry = ToolRegistry::global(cx);
registry.register_tool(TerminalTool::new(cx));
registry.register_tool(TerminalTool);
registry.register_tool(CreateDirectoryTool);
registry.register_tool(CopyPathTool);
registry.register_tool(DeletePathTool);
+29 -52
View File
@@ -6,7 +6,7 @@ use action_log::ActionLog;
use agent_settings;
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{Tool, ToolCard, ToolResult, ToolUseStatus};
use futures::{FutureExt as _, future::Shared};
use futures::FutureExt as _;
use gpui::{
AnyWindowHandle, App, AppContext, Empty, Entity, EntityId, Task, TextStyleRefinement,
WeakEntity, Window,
@@ -26,11 +26,12 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use task::{Shell, ShellBuilder};
use terminal_view::TerminalView;
use theme::ThemeSettings;
use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*};
use util::{
ResultExt, get_system_shell, markdown::MarkdownInlineCode, size::format_file_size,
ResultExt, get_default_system_shell, markdown::MarkdownInlineCode, size::format_file_size,
time::duration_alt_display,
};
use workspace::Workspace;
@@ -45,29 +46,10 @@ pub struct TerminalToolInput {
cd: String,
}
pub struct TerminalTool {
determine_shell: Shared<Task<String>>,
}
pub struct TerminalTool;
impl TerminalTool {
pub const NAME: &str = "terminal";
pub(crate) fn new(cx: &mut App) -> Self {
let determine_shell = cx.background_spawn(async move {
if cfg!(windows) {
return get_system_shell();
}
if which::which("bash").is_ok() {
"bash".into()
} else {
get_system_shell()
}
});
Self {
determine_shell: determine_shell.shared(),
}
}
}
impl Tool for TerminalTool {
@@ -135,19 +117,6 @@ impl Tool for TerminalTool {
Ok(dir) => dir,
Err(err) => return Task::ready(Err(err)).into(),
};
let program = self.determine_shell.clone();
let command = if cfg!(windows) {
format!("$null | & {{{}}}", input.command.replace("\"", "'"))
} else if let Some(cwd) = working_dir
.as_ref()
.and_then(|cwd| cwd.as_os_str().to_str())
{
// Make sure once we're *inside* the shell, we cd into `cwd`
format!("(cd {cwd}; {}) </dev/null", input.command)
} else {
format!("({}) </dev/null", input.command)
};
let args = vec!["-c".into(), command];
let cwd = working_dir.clone();
let env = match &working_dir {
@@ -156,6 +125,11 @@ impl Tool for TerminalTool {
}),
None => Task::ready(None).shared(),
};
let remote_shell = project.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
});
let env = cx.spawn(async move |_| {
let mut env = env.await.unwrap_or_default();
@@ -171,8 +145,13 @@ impl Tool for TerminalTool {
let task = cx.background_spawn(async move {
let env = env.await;
let pty_system = native_pty_system();
let program = program.await;
let mut cmd = CommandBuilder::new(program);
let (command, args) = ShellBuilder::new(
remote_shell.as_deref(),
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(input.command.clone()), &[]);
let mut cmd = CommandBuilder::new(command);
cmd.args(args);
for (k, v) in env {
cmd.env(k, v);
@@ -208,16 +187,22 @@ impl Tool for TerminalTool {
};
};
let command = input.command.clone();
let terminal = cx.spawn({
let project = project.downgrade();
async move |cx| {
let program = program.await;
let (command, args) = ShellBuilder::new(
remote_shell.as_deref(),
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(input.command), &[]);
let env = env.await;
project
.update(cx, |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(program),
command: Some(command),
args,
cwd,
env,
@@ -230,14 +215,8 @@ impl Tool for TerminalTool {
}
});
let command_markdown = cx.new(|cx| {
Markdown::new(
format!("```bash\n{}\n```", input.command).into(),
None,
None,
cx,
)
});
let command_markdown =
cx.new(|cx| Markdown::new(format!("```bash\n{}\n```", command).into(), None, None, cx));
let card = cx.new(|cx| {
TerminalToolCard::new(
@@ -288,7 +267,7 @@ impl Tool for TerminalTool {
let previous_len = content.len();
let (processed_content, finished_with_empty_output) = process_content(
&content,
&input.command,
&command,
exit_status.map(portable_pty::ExitStatus::from),
);
@@ -740,7 +719,6 @@ mod tests {
if cfg!(windows) {
return;
}
init_test(&executor, cx);
let fs = Arc::new(RealFs::new(None, executor));
@@ -763,7 +741,7 @@ mod tests {
};
let result = cx.update(|cx| {
TerminalTool::run(
Arc::new(TerminalTool::new(cx)),
Arc::new(TerminalTool),
serde_json::to_value(input).unwrap(),
Arc::default(),
project.clone(),
@@ -783,7 +761,6 @@ mod tests {
if cfg!(windows) {
return;
}
init_test(&executor, cx);
let fs = Arc::new(RealFs::new(None, executor));
@@ -798,7 +775,7 @@ mod tests {
let check = |input, expected, cx: &mut App| {
let headless_result = TerminalTool::run(
Arc::new(TerminalTool::new(cx)),
Arc::new(TerminalTool),
serde_json::to_value(input).unwrap(),
Arc::default(),
project.clone(),