Use project environment in LSP runnables context (#29761)

Stanislav Alekseev created

Release Notes:

- Fixed the tasks from LSP not inheriting the worktree environment

----

cc @SomeoneToIgnore

Change summary

crates/editor/src/lsp_ext.rs      | 41 +++++++++++++++++++++++++++++++-
crates/project/src/environment.rs |  4 +-
crates/project/src/lsp_store.rs   |  2 
crates/project/src/project.rs     | 13 +++++++++
crates/project/src/task_store.rs  |  6 ----
5 files changed, 55 insertions(+), 11 deletions(-)

Detailed changes

crates/editor/src/lsp_ext.rs 🔗

@@ -3,6 +3,7 @@ use std::sync::Arc;
 use crate::Editor;
 use collections::HashMap;
 use futures::stream::FuturesUnordered;
+use gpui::AsyncApp;
 use gpui::{App, AppContext as _, Entity, Task};
 use itertools::Itertools;
 use language::Buffer;
@@ -74,6 +75,39 @@ where
     })
 }
 
+async fn lsp_task_context(
+    project: &Entity<Project>,
+    buffer: &Entity<Buffer>,
+    cx: &mut AsyncApp,
+) -> Option<TaskContext> {
+    let worktree_store = project
+        .update(cx, |project, _| project.worktree_store())
+        .ok()?;
+
+    let worktree_abs_path = cx
+        .update(|cx| {
+            let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx));
+
+            worktree_id
+                .and_then(|worktree_id| worktree_store.read(cx).worktree_for_id(worktree_id, cx))
+                .and_then(|worktree| worktree.read(cx).root_dir())
+        })
+        .ok()?;
+
+    let project_env = project
+        .update(cx, |project, cx| {
+            project.buffer_environment(&buffer, &worktree_store, cx)
+        })
+        .ok()?
+        .await;
+
+    Some(TaskContext {
+        cwd: worktree_abs_path.map(|p| p.to_path_buf()),
+        project_env: project_env.unwrap_or_default(),
+        ..TaskContext::default()
+    })
+}
+
 pub fn lsp_tasks(
     project: Entity<Project>,
     task_sources: &HashMap<LanguageServerName, Vec<BufferId>>,
@@ -97,13 +131,16 @@ pub fn lsp_tasks(
 
     cx.spawn(async move |cx| {
         let mut lsp_tasks = Vec::new();
-        let lsp_task_context = TaskContext::default();
         while let Some(server_to_query) = lsp_task_sources.next().await {
             if let Some((server_id, buffers)) = server_to_query {
                 let source_kind = TaskSourceKind::Lsp(server_id);
                 let id_base = source_kind.to_id_base();
                 let mut new_lsp_tasks = Vec::new();
                 for buffer in buffers {
+                    let lsp_buffer_context = lsp_task_context(&project, &buffer, cx)
+                        .await
+                        .unwrap_or_default();
+
                     if let Ok(runnables_task) = project.update(cx, |project, cx| {
                         let buffer_id = buffer.read(cx).remote_id();
                         project.request_lsp(
@@ -120,7 +157,7 @@ pub fn lsp_tasks(
                             new_lsp_tasks.extend(new_runnables.runnables.into_iter().filter_map(
                                 |(location, runnable)| {
                                     let resolved_task =
-                                        runnable.resolve_task(&id_base, &lsp_task_context)?;
+                                        runnable.resolve_task(&id_base, &lsp_buffer_context)?;
                                     Some((location, resolved_task))
                                 },
                             ));

crates/project/src/environment.rs 🔗

@@ -59,8 +59,8 @@ impl ProjectEnvironment {
 
     pub(crate) fn get_buffer_environment(
         &mut self,
-        buffer: Entity<Buffer>,
-        worktree_store: Entity<WorktreeStore>,
+        buffer: &Entity<Buffer>,
+        worktree_store: &Entity<WorktreeStore>,
         cx: &mut Context<Self>,
     ) -> Shared<Task<Option<HashMap<String, String>>>> {
         if cfg!(any(test, feature = "test-support")) {

crates/project/src/lsp_store.rs 🔗

@@ -8190,7 +8190,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.clone(), self.worktree_store.clone(), cx)
+                env.get_buffer_environment(&buffer, &self.worktree_store, cx)
             })
         } else {
             Task::ready(None).shared()

crates/project/src/project.rs 🔗

@@ -56,7 +56,7 @@ use futures::future::join_all;
 use futures::{
     StreamExt,
     channel::mpsc::{self, UnboundedReceiver},
-    future::try_join_all,
+    future::{Shared, try_join_all},
 };
 pub use image_store::{ImageItem, ImageStore};
 use image_store::{ImageItemEvent, ImageStoreEvent};
@@ -1605,6 +1605,17 @@ 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 shell_environment_errors<'a>(
         &'a self,
         cx: &'a App,

crates/project/src/task_store.rs 🔗

@@ -315,11 +315,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.clone(),
-                    worktree_store.clone(),
-                    cx,
-                )
+                environment.get_buffer_environment(&location.buffer, &worktree_store, cx)
             })
             .ok()?
             .await;