acp_thread: Respect terminal settings shell for terminal tool environment (#39349)

When sourcing the project environment for the terminal tool, we will now
do so by spawning the shell specified by the users `terminal.shell`
setting (or as usual fall back to the login shell).

Closes #37687 

Release Notes:

- N/A
This commit is contained in:
Lukas Wirth
2025-10-02 22:10:55 +02:00
committed by GitHub
parent 7c3a21f732
commit bf48a95344
13 changed files with 615 additions and 499 deletions
-2
View File
@@ -1,7 +1,6 @@
use crate::environment::EnvironmentErrorMessage;
use std::process::ExitStatus;
#[cfg(not(any(target_os = "windows", test, feature = "test-support")))]
use {collections::HashMap, std::path::Path, util::ResultExt};
#[derive(Clone)]
@@ -28,7 +27,6 @@ impl From<DirenvError> for Option<EnvironmentErrorMessage> {
}
}
#[cfg(not(any(target_os = "windows", test, feature = "test-support")))]
pub async fn load_direnv_environment(
env: &HashMap<String, String>,
dir: &Path,
+87 -79
View File
@@ -1,6 +1,7 @@
use futures::{FutureExt, future::Shared};
use language::Buffer;
use std::{path::Path, sync::Arc};
use task::Shell;
use util::ResultExt;
use worktree::Worktree;
@@ -16,6 +17,8 @@ use crate::{
pub struct ProjectEnvironment {
cli_environment: Option<HashMap<String, String>>,
environments: HashMap<Arc<Path>, Shared<Task<Option<HashMap<String, String>>>>>,
shell_based_environments:
HashMap<(Shell, Arc<Path>), Shared<Task<Option<HashMap<String, String>>>>>,
environment_error_messages: HashMap<Arc<Path>, EnvironmentErrorMessage>,
}
@@ -30,6 +33,7 @@ impl ProjectEnvironment {
Self {
cli_environment,
environments: Default::default(),
shell_based_environments: Default::default(),
environment_error_messages: Default::default(),
}
}
@@ -134,7 +138,22 @@ impl ProjectEnvironment {
self.environments
.entry(abs_path.clone())
.or_insert_with(|| get_directory_env_impl(abs_path.clone(), cx).shared())
.or_insert_with(|| {
get_directory_env_impl(&Shell::System, abs_path.clone(), cx).shared()
})
.clone()
}
/// Returns the project environment, if possible, with the given shell.
pub fn get_directory_environment_for_shell(
&mut self,
shell: &Shell,
abs_path: Arc<Path>,
cx: &mut Context<Self>,
) -> Shared<Task<Option<HashMap<String, String>>>> {
self.shell_based_environments
.entry((shell.clone(), abs_path.clone()))
.or_insert_with(|| get_directory_env_impl(shell, abs_path.clone(), cx).shared())
.clone()
}
}
@@ -176,6 +195,7 @@ impl EnvironmentErrorMessage {
}
async fn load_directory_shell_environment(
shell: &Shell,
abs_path: &Path,
load_direnv: &DirenvSettings,
) -> (
@@ -198,7 +218,7 @@ async fn load_directory_shell_environment(
);
};
load_shell_environment(dir, load_direnv).await
load_shell_environment(shell, dir, load_direnv).await
}
Err(err) => (
None,
@@ -211,51 +231,8 @@ async fn load_directory_shell_environment(
}
}
#[cfg(any(test, feature = "test-support"))]
async fn load_shell_environment(
_dir: &Path,
_load_direnv: &DirenvSettings,
) -> (
Option<HashMap<String, String>>,
Option<EnvironmentErrorMessage>,
) {
let fake_env = [("ZED_FAKE_TEST_ENV".into(), "true".into())]
.into_iter()
.collect();
(Some(fake_env), None)
}
#[cfg(all(target_os = "windows", not(any(test, feature = "test-support"))))]
async fn load_shell_environment(
dir: &Path,
_load_direnv: &DirenvSettings,
) -> (
Option<HashMap<String, String>>,
Option<EnvironmentErrorMessage>,
) {
use util::shell_env;
let envs = match shell_env::capture(dir).await {
Ok(envs) => envs,
Err(err) => {
util::log_err(&err);
return (
None,
Some(EnvironmentErrorMessage(format!(
"Failed to load environment variables: {}",
err
))),
);
}
};
// Note: direnv is not available on Windows, so we skip direnv processing
// and just return the shell environment
(Some(envs), None)
}
#[cfg(not(any(target_os = "windows", test, feature = "test-support")))]
async fn load_shell_environment(
shell: &Shell,
dir: &Path,
load_direnv: &DirenvSettings,
) -> (
@@ -265,55 +242,86 @@ async fn load_shell_environment(
use crate::direnv::load_direnv_environment;
use util::shell_env;
let dir_ = dir.to_owned();
let mut envs = match shell_env::capture(&dir_).await {
Ok(envs) => envs,
Err(err) => {
util::log_err(&err);
return (
None,
Some(EnvironmentErrorMessage::from_str(
"Failed to load environment variables. See log for details",
)),
);
}
};
if cfg!(any(test, feature = "test-support")) {
let fake_env = [("ZED_FAKE_TEST_ENV".into(), "true".into())]
.into_iter()
.collect();
(Some(fake_env), None)
} else if cfg!(target_os = "windows",) {
let (shell, args) = shell.program_and_args();
let envs = match shell_env::capture(shell, args, dir).await {
Ok(envs) => envs,
Err(err) => {
util::log_err(&err);
return (
None,
Some(EnvironmentErrorMessage(format!(
"Failed to load environment variables: {}",
err
))),
);
}
};
// If the user selects `Direct` for direnv, it would set an environment
// variable that later uses to know that it should not run the hook.
// We would include in `.envs` call so it is okay to run the hook
// even if direnv direct mode is enabled.
let (direnv_environment, direnv_error) = match load_direnv {
DirenvSettings::ShellHook => (None, None),
DirenvSettings::Direct => match load_direnv_environment(&envs, dir).await {
Ok(env) => (Some(env), None),
Err(err) => (None, err.into()),
},
};
if let Some(direnv_environment) = direnv_environment {
for (key, value) in direnv_environment {
if let Some(value) = value {
envs.insert(key, value);
} else {
envs.remove(&key);
// Note: direnv is not available on Windows, so we skip direnv processing
// and just return the shell environment
(Some(envs), None)
} else {
let dir_ = dir.to_owned();
let (shell, args) = shell.program_and_args();
let mut envs = match shell_env::capture(shell, args, &dir_).await {
Ok(envs) => envs,
Err(err) => {
util::log_err(&err);
return (
None,
Some(EnvironmentErrorMessage::from_str(
"Failed to load environment variables. See log for details",
)),
);
}
};
// If the user selects `Direct` for direnv, it would set an environment
// variable that later uses to know that it should not run the hook.
// We would include in `.envs` call so it is okay to run the hook
// even if direnv direct mode is enabled.
let (direnv_environment, direnv_error) = match load_direnv {
DirenvSettings::ShellHook => (None, None),
DirenvSettings::Direct => match load_direnv_environment(&envs, dir).await {
Ok(env) => (Some(env), None),
Err(err) => (None, err.into()),
},
};
if let Some(direnv_environment) = direnv_environment {
for (key, value) in direnv_environment {
if let Some(value) = value {
envs.insert(key, value);
} else {
envs.remove(&key);
}
}
}
}
(Some(envs), direnv_error)
(Some(envs), direnv_error)
}
}
fn get_directory_env_impl(
shell: &Shell,
abs_path: Arc<Path>,
cx: &Context<ProjectEnvironment>,
) -> Task<Option<HashMap<String, String>>> {
let load_direnv = ProjectSettings::get_global(cx).load_direnv.clone();
let shell = shell.clone();
cx.spawn(async move |this, cx| {
let (mut shell_env, error_message) = cx
.background_spawn({
let abs_path = abs_path.clone();
async move { load_directory_shell_environment(&abs_path, &load_direnv).await }
async move {
load_directory_shell_environment(&shell, &abs_path, &load_direnv).await
}
})
.await;
+3 -1
View File
@@ -33,6 +33,7 @@ pub mod search_history;
mod yarn;
use dap::inline_value::{InlineValueLocation, VariableLookupKind, VariableScope};
use task::Shell;
use crate::{
agent_server_store::{AgentServerStore, AllAgentServersSettings},
@@ -1894,11 +1895,12 @@ impl Project {
pub fn directory_environment(
&self,
shell: &Shell,
abs_path: Arc<Path>,
cx: &mut App,
) -> Shared<Task<Option<HashMap<String, String>>>> {
self.environment.update(cx, |environment, cx| {
environment.get_directory_environment(abs_path, cx)
environment.get_directory_environment_for_shell(shell, abs_path, cx)
})
}
+3 -19
View File
@@ -16,7 +16,7 @@ use task::{Shell, ShellBuilder, ShellKind, SpawnInTerminal};
use terminal::{
TaskState, TaskStatus, Terminal, TerminalBuilder, terminal_settings::TerminalSettings,
};
use util::{get_default_system_shell, get_system_shell, maybe, rel_path::RelPath};
use util::{get_default_system_shell, maybe, rel_path::RelPath};
use crate::{Project, ProjectPath};
@@ -98,15 +98,7 @@ impl Project {
.read(cx)
.shell()
.unwrap_or_else(get_default_system_shell),
None => match &settings.shell {
Shell::Program(program) => program.clone(),
Shell::WithArguments {
program,
args: _,
title_override: _,
} => program.clone(),
Shell::System => get_system_shell(),
},
None => settings.shell.program(),
};
let project_path_contexts = self
@@ -332,15 +324,7 @@ impl Project {
.read(cx)
.shell()
.unwrap_or_else(get_default_system_shell),
None => match &settings.shell {
Shell::Program(program) => program.clone(),
Shell::WithArguments {
program,
args: _,
title_override: _,
} => program.clone(),
Shell::System => get_system_shell(),
},
None => settings.shell.program(),
});
let lang_registry = self.languages.clone();