project: Use user configured shells for project env fetching

Lukas Wirth created

Change summary

crates/acp_thread/src/acp_thread.rs          |  17 --
crates/acp_thread/src/terminal.rs            |  16 --
crates/editor/src/lsp_ext.rs                 |  12 +
crates/project/src/agent_server_store.rs     |  16 +-
crates/project/src/debugger/dap_store.rs     |  16 +-
crates/project/src/environment.rs            | 128 ++++++++++++++++-----
crates/project/src/git_store.rs              |   2 
crates/project/src/lsp_store.rs              |  63 +++++-----
crates/project/src/project.rs                |  49 ++-----
crates/project/src/task_store.rs             |   2 
crates/project/src/toolchain_store.rs        |   4 
crates/remote_server/src/headless_project.rs |   5 
12 files changed, 177 insertions(+), 153 deletions(-)

Detailed changes

crates/acp_thread/src/acp_thread.rs 🔗

@@ -3,7 +3,6 @@ mod diff;
 mod mention;
 mod terminal;
 
-use ::terminal::terminal_settings::TerminalSettings;
 use agent_settings::AgentSettings;
 use collections::HashSet;
 pub use connection::*;
@@ -12,7 +11,7 @@ use language::language_settings::FormatOnSave;
 pub use mention::*;
 use project::lsp_store::{FormatTrigger, LspFormatTarget};
 use serde::{Deserialize, Serialize};
-use settings::{Settings as _, SettingsLocation};
+use settings::Settings as _;
 use task::{Shell, ShellBuilder};
 pub use terminal::*;
 
@@ -2141,17 +2140,9 @@ impl AcpThread {
     ) -> Task<Result<Entity<Terminal>>> {
         let env = match &cwd {
             Some(dir) => self.project.update(cx, |project, cx| {
-                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)
+                project.environment().update(cx, |env, cx| {
+                    env.directory_environment(dir.as_path().into(), cx)
+                })
             }),
             None => Task::ready(None).shared(),
         };

crates/acp_thread/src/terminal.rs 🔗

@@ -5,10 +5,8 @@ use gpui::{App, AppContext, AsyncApp, Context, Entity, Task};
 use language::LanguageRegistry;
 use markdown::Markdown;
 use project::Project;
-use settings::{Settings as _, SettingsLocation};
 use std::{path::PathBuf, process::ExitStatus, sync::Arc, time::Instant};
 use task::Shell;
-use terminal::terminal_settings::TerminalSettings;
 use util::get_default_system_shell_preferring_bash;
 
 pub struct Terminal {
@@ -187,17 +185,9 @@ pub async fn create_terminal_entity(
     let mut env = if let Some(dir) = &cwd {
         project
             .update(cx, |project, cx| {
-                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.clone().into(), cx)
+                project.environment().update(cx, |env, cx| {
+                    env.directory_environment(dir.clone().into(), cx)
+                })
             })?
             .await
             .unwrap_or_default()

crates/editor/src/lsp_ext.rs 🔗

@@ -60,8 +60,10 @@ async fn lsp_task_context(
     buffer: &Entity<Buffer>,
     cx: &mut AsyncApp,
 ) -> Option<TaskContext> {
-    let worktree_store = project
-        .read_with(cx, |project, _| project.worktree_store())
+    let (worktree_store, environment) = project
+        .read_with(cx, |project, _| {
+            (project.worktree_store(), project.environment().clone())
+        })
         .ok()?;
 
     let worktree_abs_path = cx
@@ -74,9 +76,9 @@ async fn lsp_task_context(
         })
         .ok()?;
 
-    let project_env = project
-        .update(cx, |project, cx| {
-            project.buffer_environment(buffer, &worktree_store, cx)
+    let project_env = environment
+        .update(cx, |environment, cx| {
+            environment.buffer_environment(buffer, &worktree_store, cx)
         })
         .ok()?
         .await;

crates/project/src/agent_server_store.rs 🔗

@@ -1037,7 +1037,7 @@ impl ExternalAgentServer for LocalGemini {
         cx.spawn(async move |cx| {
             let mut env = project_environment
                 .update(cx, |project_environment, cx| {
-                    project_environment.get_local_directory_environment(
+                    project_environment.local_directory_environment(
                         &Shell::System,
                         root_dir.clone(),
                         cx,
@@ -1133,7 +1133,7 @@ impl ExternalAgentServer for LocalClaudeCode {
         cx.spawn(async move |cx| {
             let mut env = project_environment
                 .update(cx, |project_environment, cx| {
-                    project_environment.get_local_directory_environment(
+                    project_environment.local_directory_environment(
                         &Shell::System,
                         root_dir.clone(),
                         cx,
@@ -1227,7 +1227,7 @@ impl ExternalAgentServer for LocalCodex {
         cx.spawn(async move |cx| {
             let mut env = project_environment
                 .update(cx, |project_environment, cx| {
-                    project_environment.get_local_directory_environment(
+                    project_environment.local_directory_environment(
                         &Shell::System,
                         root_dir.clone(),
                         cx,
@@ -1402,7 +1402,7 @@ impl ExternalAgentServer for LocalExtensionArchiveAgent {
             // Get project environment
             let mut env = project_environment
                 .update(cx, |project_environment, cx| {
-                    project_environment.get_local_directory_environment(
+                    project_environment.local_directory_environment(
                         &Shell::System,
                         root_dir.clone(),
                         cx,
@@ -1585,7 +1585,7 @@ impl ExternalAgentServer for LocalCustomAgent {
         cx.spawn(async move |cx| {
             let mut env = project_environment
                 .update(cx, |project_environment, cx| {
-                    project_environment.get_local_directory_environment(
+                    project_environment.local_directory_environment(
                         &Shell::System,
                         root_dir.clone(),
                         cx,
@@ -1702,6 +1702,8 @@ impl settings::Settings for AllAgentServersSettings {
 
 #[cfg(test)]
 mod extension_agent_tests {
+    use crate::worktree_store::WorktreeStore;
+
     use super::*;
     use gpui::TestAppContext;
     use std::sync::Arc;
@@ -1826,7 +1828,9 @@ mod extension_agent_tests {
     async fn archive_agent_uses_extension_and_agent_id_for_cache_key(cx: &mut TestAppContext) {
         let fs = fs::FakeFs::new(cx.background_executor.clone());
         let http_client = http_client::FakeHttpClient::with_404_response();
-        let project_environment = cx.new(|cx| crate::ProjectEnvironment::new(None, cx));
+        let worktree_store = cx.new(|_| WorktreeStore::local(false, fs.clone()));
+        let project_environment =
+            cx.new(|cx| crate::ProjectEnvironment::new(None, worktree_store.downgrade(), None, cx));
 
         let agent = LocalExtensionArchiveAgent {
             fs,

crates/project/src/debugger/dap_store.rs 🔗

@@ -49,7 +49,7 @@ use std::{
     path::{Path, PathBuf},
     sync::{Arc, Once},
 };
-use task::{DebugScenario, Shell, SpawnInTerminal, TaskContext, TaskTemplate};
+use task::{DebugScenario, SpawnInTerminal, TaskContext, TaskTemplate};
 use util::{ResultExt as _, rel_path::RelPath};
 use worktree::Worktree;
 
@@ -267,8 +267,8 @@ impl DapStore {
                 let user_env = dap_settings.map(|s| s.env.clone());
 
                 let delegate = self.delegate(worktree, console, cx);
-                let cwd: Arc<Path> = worktree.read(cx).abs_path().as_ref().into();
 
+                let worktree = worktree.clone();
                 cx.spawn(async move |this, cx| {
                     let mut binary = adapter
                         .get_binary(
@@ -287,11 +287,7 @@ impl DapStore {
                                 .unwrap()
                                 .environment
                                 .update(cx, |environment, cx| {
-                                    environment.get_local_directory_environment(
-                                        &Shell::System,
-                                        cwd,
-                                        cx,
-                                    )
+                                    environment.worktree_environment(worktree, cx)
                                 })
                         })?
                         .await;
@@ -607,9 +603,9 @@ impl DapStore {
             local_store.node_runtime.clone(),
             local_store.http_client.clone(),
             local_store.toolchain_store.clone(),
-            local_store.environment.update(cx, |env, cx| {
-                env.get_worktree_environment(worktree.clone(), cx)
-            }),
+            local_store
+                .environment
+                .update(cx, |env, cx| env.worktree_environment(worktree.clone(), cx)),
             local_store.is_headless,
         ))
     }

crates/project/src/environment.rs 🔗

@@ -5,11 +5,12 @@ use remote::RemoteClient;
 use rpc::proto::{self, REMOTE_SERVER_PROJECT_ID};
 use std::{collections::VecDeque, path::Path, sync::Arc};
 use task::{Shell, shell_to_proto};
-use util::ResultExt;
+use terminal::terminal_settings::TerminalSettings;
+use util::{ResultExt, rel_path::RelPath};
 use worktree::Worktree;
 
 use collections::HashMap;
-use gpui::{AppContext as _, Context, Entity, EventEmitter, Task};
+use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task, WeakEntity};
 use settings::Settings as _;
 
 use crate::{
@@ -23,6 +24,8 @@ pub struct ProjectEnvironment {
     remote_environments: HashMap<(Shell, Arc<Path>), Shared<Task<Option<HashMap<String, String>>>>>,
     environment_error_messages: VecDeque<String>,
     environment_error_messages_tx: mpsc::UnboundedSender<String>,
+    worktree_store: WeakEntity<WorktreeStore>,
+    remote_client: Option<WeakEntity<RemoteClient>>,
     _tasks: Vec<Task<()>>,
 }
 
@@ -33,7 +36,12 @@ pub enum ProjectEnvironmentEvent {
 impl EventEmitter<ProjectEnvironmentEvent> for ProjectEnvironment {}
 
 impl ProjectEnvironment {
-    pub fn new(cli_environment: Option<HashMap<String, String>>, cx: &mut Context<Self>) -> Self {
+    pub fn new(
+        cli_environment: Option<HashMap<String, String>>,
+        worktree_store: WeakEntity<WorktreeStore>,
+        remote_client: Option<WeakEntity<RemoteClient>>,
+        cx: &mut Context<Self>,
+    ) -> Self {
         let (tx, mut rx) = mpsc::unbounded();
         let task = cx.spawn(async move |this, cx| {
             while let Some(message) = rx.next().await {
@@ -50,12 +58,17 @@ impl ProjectEnvironment {
             remote_environments: Default::default(),
             environment_error_messages: Default::default(),
             environment_error_messages_tx: tx,
+            worktree_store,
+            remote_client,
             _tasks: vec![task],
         }
     }
 
     /// Returns the inherited CLI environment, if this project was opened from the Zed CLI.
     pub(crate) fn get_cli_environment(&self) -> Option<HashMap<String, String>> {
+        if cfg!(any(test, feature = "test-support")) {
+            return Some(HashMap::default());
+        }
         if let Some(mut env) = self.cli_environment.clone() {
             set_origin_marker(&mut env, EnvironmentOrigin::Cli);
             Some(env)
@@ -64,16 +77,12 @@ impl ProjectEnvironment {
         }
     }
 
-    pub(crate) fn get_buffer_environment(
+    pub fn buffer_environment(
         &mut self,
         buffer: &Entity<Buffer>,
         worktree_store: &Entity<WorktreeStore>,
         cx: &mut Context<Self>,
     ) -> Shared<Task<Option<HashMap<String, String>>>> {
-        if cfg!(any(test, feature = "test-support")) {
-            return Task::ready(Some(HashMap::default())).shared();
-        }
-
         if let Some(cli_environment) = self.get_cli_environment() {
             log::debug!("using project environment variables from CLI");
             return Task::ready(Some(cli_environment)).shared();
@@ -87,54 +96,105 @@ impl ProjectEnvironment {
         else {
             return Task::ready(None).shared();
         };
-
-        self.get_worktree_environment(worktree, cx)
+        self.worktree_environment(worktree, cx)
     }
 
-    pub fn get_worktree_environment(
+    pub fn worktree_environment(
         &mut self,
         worktree: Entity<Worktree>,
-        cx: &mut Context<Self>,
+        cx: &mut App,
     ) -> Shared<Task<Option<HashMap<String, String>>>> {
-        if cfg!(any(test, feature = "test-support")) {
-            return Task::ready(Some(HashMap::default())).shared();
-        }
-
         if let Some(cli_environment) = self.get_cli_environment() {
             log::debug!("using project environment variables from CLI");
             return Task::ready(Some(cli_environment)).shared();
         }
 
-        let mut abs_path = worktree.read(cx).abs_path();
-        if !worktree.read(cx).is_local() {
-            log::error!(
-                "attempted to get project environment for a non-local worktree at {abs_path:?}"
-            );
-            return Task::ready(None).shared();
-        } else if worktree.read(cx).is_single_file() {
+        let worktree = worktree.read(cx);
+        let mut abs_path = worktree.abs_path();
+        if worktree.is_single_file() {
             let Some(parent) = abs_path.parent() else {
                 return Task::ready(None).shared();
             };
             abs_path = parent.into();
         }
 
-        self.get_local_directory_environment(&Shell::System, abs_path, cx)
+        let remote_client = self.remote_client.as_ref().and_then(|it| it.upgrade());
+        match remote_client {
+            Some(remote_client) => remote_client.clone().read(cx).shell().map(|shell| {
+                self.remote_directory_environment(
+                    &Shell::Program(shell),
+                    abs_path,
+                    remote_client,
+                    cx,
+                )
+            }),
+            None => Some({
+                let shell = TerminalSettings::get(
+                    Some(settings::SettingsLocation {
+                        worktree_id: worktree.id(),
+                        path: RelPath::empty(),
+                    }),
+                    cx,
+                )
+                .shell
+                .clone();
+
+                self.local_directory_environment(&shell, abs_path, cx)
+            }),
+        }
+        .unwrap_or_else(|| Task::ready(None).shared())
+    }
+
+    pub fn directory_environment(
+        &mut self,
+        abs_path: Arc<Path>,
+        cx: &mut App,
+    ) -> Shared<Task<Option<HashMap<String, String>>>> {
+        let remote_client = self.remote_client.as_ref().and_then(|it| it.upgrade());
+        match remote_client {
+            Some(remote_client) => remote_client.clone().read(cx).shell().map(|shell| {
+                self.remote_directory_environment(
+                    &Shell::Program(shell),
+                    abs_path,
+                    remote_client,
+                    cx,
+                )
+            }),
+            None => self
+                .worktree_store
+                .read_with(cx, |worktree_store, cx| {
+                    worktree_store.find_worktree(&abs_path, cx)
+                })
+                .ok()
+                .map(|worktree| {
+                    let shell = terminal::terminal_settings::TerminalSettings::get(
+                        worktree
+                            .as_ref()
+                            .map(|(worktree, path)| settings::SettingsLocation {
+                                worktree_id: worktree.read(cx).id(),
+                                path: &path,
+                            }),
+                        cx,
+                    )
+                    .shell
+                    .clone();
+
+                    self.local_directory_environment(&shell, abs_path, cx)
+                }),
+        }
+        .unwrap_or_else(|| Task::ready(None).shared())
     }
 
     /// Returns the project environment, if possible.
     /// If the project was opened from the CLI, then the inherited CLI environment is returned.
     /// If it wasn't opened from the CLI, and an absolute path is given, then a shell is spawned in
     /// that directory, to get environment variables as if the user has `cd`'d there.
-    pub fn get_local_directory_environment(
+    pub fn local_directory_environment(
         &mut self,
         shell: &Shell,
         abs_path: Arc<Path>,
-        cx: &mut Context<Self>,
+        cx: &mut App,
     ) -> Shared<Task<Option<HashMap<String, String>>>> {
-        if cfg!(any(test, feature = "test-support")) {
-            return Task::ready(Some(HashMap::default())).shared();
-        }
-
         if let Some(cli_environment) = self.get_cli_environment() {
             log::debug!("using project environment variables from CLI");
             return Task::ready(Some(cli_environment)).shared();
@@ -146,7 +206,7 @@ impl ProjectEnvironment {
                 let load_direnv = ProjectSettings::get_global(cx).load_direnv.clone();
                 let shell = shell.clone();
                 let tx = self.environment_error_messages_tx.clone();
-                cx.spawn(async move |_, cx| {
+                cx.spawn(async move |cx| {
                     let mut shell_env = cx
                         .background_spawn(load_directory_shell_environment(
                             shell,
@@ -178,12 +238,12 @@ impl ProjectEnvironment {
             .clone()
     }
 
-    pub fn get_remote_directory_environment(
+    pub fn remote_directory_environment(
         &mut self,
         shell: &Shell,
         abs_path: Arc<Path>,
         remote_client: Entity<RemoteClient>,
-        cx: &mut Context<Self>,
+        cx: &mut App,
     ) -> Shared<Task<Option<HashMap<String, String>>>> {
         if cfg!(any(test, feature = "test-support")) {
             return Task::ready(Some(HashMap::default())).shared();
@@ -201,7 +261,7 @@ impl ProjectEnvironment {
                             shell: Some(shell_to_proto(shell.clone())),
                             directory: abs_path.to_string_lossy().to_string(),
                         });
-                cx.spawn(async move |_, _| {
+                cx.background_spawn(async move {
                     let environment = response.await.log_err()?;
                     Some(environment.environment.into_iter().collect())
                 })

crates/project/src/git_store.rs 🔗

@@ -4804,7 +4804,7 @@ impl Repository {
                 .upgrade()
                 .context("missing project environment")?
                 .update(cx, |project_environment, cx| {
-                    project_environment.get_local_directory_environment(&Shell::System, work_directory_abs_path.clone(), cx)
+                    project_environment.local_directory_environment(&Shell::System, work_directory_abs_path.clone(), cx)
                 })?
                 .await
                 .unwrap_or_else(|| {

crates/project/src/lsp_store.rs 🔗

@@ -5329,8 +5329,8 @@ impl LspStore {
                 request.to_proto(project_id, buffer.read(cx)),
             );
             let buffer = buffer.clone();
-            cx.spawn(async move |weak_project, cx| {
-                let Some(project) = weak_project.upgrade() else {
+            cx.spawn(async move |weak_lsp_store, cx| {
+                let Some(lsp_store) = weak_lsp_store.upgrade() else {
                     return Ok(None);
                 };
                 let Some(responses) = request_task.await? else {
@@ -5339,7 +5339,7 @@ impl LspStore {
                 let actions = join_all(responses.payload.into_iter().map(|response| {
                     GetDefinitions { position }.response_from_proto(
                         response.response,
-                        project.clone(),
+                        lsp_store.clone(),
                         buffer.clone(),
                         cx.clone(),
                     )
@@ -5395,8 +5395,8 @@ impl LspStore {
                 request.to_proto(project_id, buffer.read(cx)),
             );
             let buffer = buffer.clone();
-            cx.spawn(async move |weak_project, cx| {
-                let Some(project) = weak_project.upgrade() else {
+            cx.spawn(async move |weak_lsp_store, cx| {
+                let Some(lsp_store) = weak_lsp_store.upgrade() else {
                     return Ok(None);
                 };
                 let Some(responses) = request_task.await? else {
@@ -5405,7 +5405,7 @@ impl LspStore {
                 let actions = join_all(responses.payload.into_iter().map(|response| {
                     GetDeclarations { position }.response_from_proto(
                         response.response,
-                        project.clone(),
+                        lsp_store.clone(),
                         buffer.clone(),
                         cx.clone(),
                     )
@@ -5461,8 +5461,8 @@ impl LspStore {
                 request.to_proto(project_id, buffer.read(cx)),
             );
             let buffer = buffer.clone();
-            cx.spawn(async move |weak_project, cx| {
-                let Some(project) = weak_project.upgrade() else {
+            cx.spawn(async move |weak_lsp_store, cx| {
+                let Some(lsp_store) = weak_lsp_store.upgrade() else {
                     return Ok(None);
                 };
                 let Some(responses) = request_task.await? else {
@@ -5471,7 +5471,7 @@ impl LspStore {
                 let actions = join_all(responses.payload.into_iter().map(|response| {
                     GetTypeDefinitions { position }.response_from_proto(
                         response.response,
-                        project.clone(),
+                        lsp_store.clone(),
                         buffer.clone(),
                         cx.clone(),
                     )
@@ -5527,8 +5527,8 @@ impl LspStore {
                 request.to_proto(project_id, buffer.read(cx)),
             );
             let buffer = buffer.clone();
-            cx.spawn(async move |weak_project, cx| {
-                let Some(project) = weak_project.upgrade() else {
+            cx.spawn(async move |weak_lsp_store, cx| {
+                let Some(lsp_store) = weak_lsp_store.upgrade() else {
                     return Ok(None);
                 };
                 let Some(responses) = request_task.await? else {
@@ -5537,7 +5537,7 @@ impl LspStore {
                 let actions = join_all(responses.payload.into_iter().map(|response| {
                     GetImplementations { position }.response_from_proto(
                         response.response,
-                        project.clone(),
+                        lsp_store.clone(),
                         buffer.clone(),
                         cx.clone(),
                     )
@@ -5594,8 +5594,8 @@ impl LspStore {
                 request.to_proto(project_id, buffer.read(cx)),
             );
             let buffer = buffer.clone();
-            cx.spawn(async move |weak_project, cx| {
-                let Some(project) = weak_project.upgrade() else {
+            cx.spawn(async move |weak_lsp_store, cx| {
+                let Some(lsp_store) = weak_lsp_store.upgrade() else {
                     return Ok(None);
                 };
                 let Some(responses) = request_task.await? else {
@@ -5605,7 +5605,7 @@ impl LspStore {
                 let locations = join_all(responses.payload.into_iter().map(|lsp_response| {
                     GetReferences { position }.response_from_proto(
                         lsp_response.response,
-                        project.clone(),
+                        lsp_store.clone(),
                         buffer.clone(),
                         cx.clone(),
                     )
@@ -5662,8 +5662,8 @@ impl LspStore {
                 request.to_proto(project_id, buffer.read(cx)),
             );
             let buffer = buffer.clone();
-            cx.spawn(async move |weak_project, cx| {
-                let Some(project) = weak_project.upgrade() else {
+            cx.spawn(async move |weak_lsp_store, cx| {
+                let Some(lsp_store) = weak_lsp_store.upgrade() else {
                     return Ok(None);
                 };
                 let Some(responses) = request_task.await? else {
@@ -5676,7 +5676,7 @@ impl LspStore {
                     }
                     .response_from_proto(
                         response.response,
-                        project.clone(),
+                        lsp_store.clone(),
                         buffer.clone(),
                         cx.clone(),
                     )
@@ -7157,7 +7157,7 @@ impl LspStore {
             );
             let buffer = buffer.clone();
             cx.spawn(async move |lsp_store, cx| {
-                let Some(project) = lsp_store.upgrade() else {
+                let Some(lsp_store) = lsp_store.upgrade() else {
                     return Ok(None);
                 };
                 let colors = join_all(
@@ -7171,7 +7171,7 @@ impl LspStore {
                         .map(|color_response| {
                             let response = request.response_from_proto(
                                 color_response.response,
-                                project.clone(),
+                                lsp_store.clone(),
                                 buffer.clone(),
                                 cx.clone(),
                             );
@@ -7235,8 +7235,8 @@ impl LspStore {
                 request.to_proto(upstream_project_id, buffer.read(cx)),
             );
             let buffer = buffer.clone();
-            cx.spawn(async move |weak_project, cx| {
-                let project = weak_project.upgrade()?;
+            cx.spawn(async move |weak_lsp_store, cx| {
+                let lsp_store = weak_lsp_store.upgrade()?;
                 let signatures = join_all(
                     request_task
                         .await
@@ -7248,7 +7248,7 @@ impl LspStore {
                         .map(|response| {
                             let response = GetSignatureHelp { position }.response_from_proto(
                                 response.response,
-                                project.clone(),
+                                lsp_store.clone(),
                                 buffer.clone(),
                                 cx.clone(),
                             );
@@ -7299,8 +7299,8 @@ impl LspStore {
                 request.to_proto(upstream_project_id, buffer.read(cx)),
             );
             let buffer = buffer.clone();
-            cx.spawn(async move |weak_project, cx| {
-                let project = weak_project.upgrade()?;
+            cx.spawn(async move |weak_lsp_store, cx| {
+                let lsp_store = weak_lsp_store.upgrade()?;
                 let hovers = join_all(
                     request_task
                         .await
@@ -7312,7 +7312,7 @@ impl LspStore {
                         .map(|response| {
                             let response = GetHover { position }.response_from_proto(
                                 response.response,
-                                project.clone(),
+                                lsp_store.clone(),
                                 buffer.clone(),
                                 cx.clone(),
                             );
@@ -10130,7 +10130,7 @@ impl LspStore {
     ) -> Shared<Task<Option<HashMap<String, String>>>> {
         if let Some(environment) = &self.as_local().map(|local| local.environment.clone()) {
             environment.update(cx, |env, cx| {
-                env.get_buffer_environment(buffer, &self.worktree_store, cx)
+                env.buffer_environment(buffer, &self.worktree_store, cx)
             })
         } else {
             Task::ready(None).shared()
@@ -12890,7 +12890,7 @@ impl LanguageServerWatchedPathsBuilder {
         language_server_id: LanguageServerId,
         cx: &mut Context<LspStore>,
     ) -> LanguageServerWatchedPaths {
-        let project = cx.weak_entity();
+        let lsp_store = cx.weak_entity();
 
         const LSP_ABS_PATH_OBSERVE: Duration = Duration::from_millis(100);
         let abs_paths = self
@@ -12901,7 +12901,7 @@ impl LanguageServerWatchedPathsBuilder {
                     let abs_path = abs_path.clone();
                     let fs = fs.clone();
 
-                    let lsp_store = project.clone();
+                    let lsp_store = lsp_store.clone();
                     async move |_, cx| {
                         maybe!(async move {
                             let mut push_updates = fs.watch(&abs_path, LSP_ABS_PATH_OBSERVE).await;
@@ -13369,9 +13369,8 @@ impl LocalLspAdapterDelegate {
         fs: Arc<dyn Fs>,
         cx: &mut App,
     ) -> Arc<Self> {
-        let load_shell_env_task = environment.update(cx, |env, cx| {
-            env.get_worktree_environment(worktree.clone(), cx)
-        });
+        let load_shell_env_task =
+            environment.update(cx, |env, cx| env.worktree_environment(worktree.clone(), cx));
 
         Arc::new(Self {
             lsp_store,

crates/project/src/project.rs 🔗

@@ -33,7 +33,6 @@ pub mod search_history;
 mod yarn;
 
 use dap::inline_value::{InlineValueLocation, VariableLookupKind, VariableScope};
-use task::Shell;
 
 use crate::{
     agent_server_store::AllAgentServersSettings,
@@ -68,7 +67,7 @@ use futures::future::join_all;
 use futures::{
     StreamExt,
     channel::mpsc::{self, UnboundedReceiver},
-    future::{Shared, try_join_all},
+    future::try_join_all,
 };
 pub use image_store::{ImageItem, ImageStore};
 use image_store::{ImageItemEvent, ImageStoreEvent};
@@ -1070,9 +1069,10 @@ impl Project {
 
             let weak_self = cx.weak_entity();
             let context_server_store =
-                cx.new(|cx| ContextServerStore::new(worktree_store.clone(), weak_self, cx));
+                cx.new(|cx| ContextServerStore::new(worktree_store.clone(), weak_self.clone(), cx));
 
-            let environment = cx.new(|cx| ProjectEnvironment::new(env, cx));
+            let environment =
+                cx.new(|cx| ProjectEnvironment::new(env, worktree_store.downgrade(), None, cx));
             let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
             let toolchain_store = cx.new(|cx| {
                 ToolchainStore::local(
@@ -1261,7 +1261,7 @@ impl Project {
 
             let weak_self = cx.weak_entity();
             let context_server_store =
-                cx.new(|cx| ContextServerStore::new(worktree_store.clone(), weak_self, cx));
+                cx.new(|cx| ContextServerStore::new(worktree_store.clone(), weak_self.clone(), cx));
 
             let buffer_store = cx.new(|cx| {
                 BufferStore::remote(
@@ -1307,7 +1307,14 @@ impl Project {
             cx.subscribe(&settings_observer, Self::on_settings_observer_event)
                 .detach();
 
-            let environment = cx.new(|cx| ProjectEnvironment::new(None, cx));
+            let environment = cx.new(|cx| {
+                ProjectEnvironment::new(
+                    None,
+                    worktree_store.downgrade(),
+                    Some(remote.downgrade()),
+                    cx,
+                )
+            });
 
             let lsp_store = cx.new(|cx| {
                 LspStore::new_remote(
@@ -1520,8 +1527,8 @@ impl Project {
             ImageStore::remote(worktree_store.clone(), client.clone().into(), remote_id, cx)
         })?;
 
-        let environment = cx.new(|cx| ProjectEnvironment::new(None, cx))?;
-
+        let environment =
+            cx.new(|cx| ProjectEnvironment::new(None, worktree_store.downgrade(), None, cx))?;
         let breakpoint_store =
             cx.new(|_| BreakpointStore::remote(remote_id, client.clone().into()))?;
         let dap_store = cx.new(|cx| {
@@ -1925,32 +1932,6 @@ impl Project {
         self.environment.read(cx).get_cli_environment()
     }
 
-    pub fn buffer_environment<'a>(
-        &'a self,
-        buffer: &Entity<Buffer>,
-        worktree_store: &Entity<WorktreeStore>,
-        cx: &'a mut App,
-    ) -> Shared<Task<Option<HashMap<String, String>>>> {
-        self.environment.update(cx, |environment, cx| {
-            environment.get_buffer_environment(buffer, worktree_store, cx)
-        })
-    }
-
-    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| {
-            if let Some(remote_client) = self.remote_client.clone() {
-                environment.get_remote_directory_environment(shell, abs_path, remote_client, cx)
-            } else {
-                environment.get_local_directory_environment(shell, abs_path, cx)
-            }
-        })
-    }
-
     #[inline]
     pub fn peek_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a String> {
         self.environment.read(cx).peek_environment_error()

crates/project/src/task_store.rs 🔗

@@ -317,7 +317,7 @@ fn local_task_context_for_location(
     cx.spawn(async move |cx| {
         let project_env = environment
             .update(cx, |environment, cx| {
-                environment.get_buffer_environment(&location.buffer, &worktree_store, cx)
+                environment.buffer_environment(&location.buffer, &worktree_store, cx)
             })
             .ok()?
             .await;

crates/project/src/toolchain_store.rs 🔗

@@ -527,7 +527,7 @@ impl LocalToolchainStore {
 
             let project_env = environment
                 .update(cx, |environment, cx| {
-                    environment.get_local_directory_environment(
+                    environment.local_directory_environment(
                         &Shell::System,
                         abs_path.as_path().into(),
                         cx,
@@ -590,7 +590,7 @@ impl LocalToolchainStore {
 
             let project_env = environment
                 .update(cx, |environment, cx| {
-                    environment.get_local_directory_environment(
+                    environment.local_directory_environment(
                         &Shell::System,
                         path.as_path().into(),
                         cx,

crates/remote_server/src/headless_project.rs 🔗

@@ -94,7 +94,8 @@ impl HeadlessProject {
             store
         });
 
-        let environment = cx.new(|cx| ProjectEnvironment::new(None, cx));
+        let environment =
+            cx.new(|cx| ProjectEnvironment::new(None, worktree_store.downgrade(), None, cx));
         let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
         let toolchain_store = cx.new(|cx| {
             ToolchainStore::local(
@@ -786,7 +787,7 @@ impl HeadlessProject {
         let environment = this
             .update(&mut cx, |this, cx| {
                 this.environment.update(cx, |environment, cx| {
-                    environment.get_local_directory_environment(&shell, directory.into(), cx)
+                    environment.local_directory_environment(&shell, directory.into(), cx)
                 })
             })?
             .await