Properly notify all language server worktree trust listeners (#46613)

Kirill Bulatov created

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


https://github.com/user-attachments/assets/8464a3b6-9258-4d15-9168-56178bf46437

`use smol::channel::{Receiver, Sender};` do not propagate the value
change to all receivers, hence only one language server received the
"worktree trusted" message.
The fix uses `watch` kind of channels instead, and cleans up its state
more eager to rely on the sender drop as another form of notification.

Release Notes:

- Fixed groups of language servers not starting after worktree trust
approval

Change summary

crates/project/src/lsp_store.rs | 85 ++++++++++++++++++++++------------
1 file changed, 55 insertions(+), 30 deletions(-)

Detailed changes

crates/project/src/lsp_store.rs 🔗

@@ -98,7 +98,6 @@ use serde::Serialize;
 use serde_json::Value;
 use settings::{Settings, SettingsLocation, SettingsStore};
 use sha2::{Digest, Sha256};
-use smol::channel::{Receiver, Sender};
 use snippet::Snippet;
 use std::{
     any::TypeId,
@@ -300,7 +299,7 @@ pub struct LocalLspStore {
         LanguageServerId,
         HashMap<Option<SharedString>, HashMap<PathBuf, Option<SharedString>>>,
     >,
-    restricted_worktrees_tasks: HashMap<WorktreeId, (Subscription, Receiver<()>)>,
+    restricted_worktrees_tasks: HashMap<WorktreeId, (Subscription, watch::Receiver<bool>)>,
 }
 
 impl LocalLspStore {
@@ -385,7 +384,7 @@ impl LocalLspStore {
             adapter.name.0
         );
 
-        let untrusted_worktree_task =
+        let wait_until_worktree_trust =
             TrustedWorktrees::try_get_global(cx).and_then(|trusted_worktrees| {
                 let can_trust = trusted_worktrees.update(cx, |trusted_worktrees, cx| {
                     trusted_worktrees.can_trust(&self.worktree_store, worktree_id, cx)
@@ -397,11 +396,23 @@ impl LocalLspStore {
                     match self.restricted_worktrees_tasks.entry(worktree_id) {
                         hash_map::Entry::Occupied(o) => Some(o.get().1.clone()),
                         hash_map::Entry::Vacant(v) => {
-                            let (tx, rx) = smol::channel::bounded::<()>(1);
-                            let subscription = cx.subscribe(&trusted_worktrees, move |_, e, _| {
+                            let (mut tx, rx) = watch::channel::<bool>();
+                            let lsp_store = self.weak.clone();
+                            let subscription = cx.subscribe(&trusted_worktrees, move |_, e, cx| {
                                 if let TrustedWorktreesEvent::Trusted(_, trusted_paths) = e {
                                     if trusted_paths.contains(&PathTrust::Worktree(worktree_id)) {
-                                        tx.send_blocking(()).ok();
+                                        tx.blocking_send(true).ok();
+                                        lsp_store
+                                            .update(cx, |lsp_store, _| {
+                                                if let Some(local_lsp_store) =
+                                                    lsp_store.as_local_mut()
+                                                {
+                                                    local_lsp_store
+                                                        .restricted_worktrees_tasks
+                                                        .remove(&worktree_id);
+                                                }
+                                            })
+                                            .ok();
                                     }
                                 }
                             });
@@ -411,7 +422,7 @@ impl LocalLspStore {
                     }
                 }
             });
-        let update_binary_status = untrusted_worktree_task.is_none();
+        let update_binary_status = wait_until_worktree_trust.is_none();
 
         let binary = self.get_language_server_binary(
             worktree_abs_path.clone(),
@@ -420,7 +431,7 @@ impl LocalLspStore {
             toolchain.clone(),
             delegate.clone(),
             true,
-            untrusted_worktree_task,
+            wait_until_worktree_trust,
             cx,
         );
         let pending_workspace_folders = Arc::<Mutex<BTreeSet<Uri>>>::default();
@@ -616,7 +627,7 @@ impl LocalLspStore {
         toolchain: Option<Toolchain>,
         delegate: Arc<dyn LspAdapterDelegate>,
         allow_binary_download: bool,
-        untrusted_worktree_task: Option<Receiver<()>>,
+        wait_until_worktree_trust: Option<watch::Receiver<bool>>,
         cx: &mut App,
     ) -> Task<Result<LanguageServerBinary>> {
         if let Some(settings) = &settings.binary
@@ -625,16 +636,23 @@ impl LocalLspStore {
             let settings = settings.clone();
             let languages = self.languages.clone();
             return cx.background_spawn(async move {
-                if let Some(untrusted_worktree_task) = untrusted_worktree_task {
-                    log::info!(
-                        "Waiting for worktree {worktree_abs_path:?} to be trusted, before starting language server {}",
-                        adapter.name(),
-                    );
-                    untrusted_worktree_task.recv().await.ok();
-                    log::info!(
-                        "Worktree {worktree_abs_path:?} is trusted, starting language server {}",
-                        adapter.name(),
-                    );
+                if let Some(mut wait_until_worktree_trust) = wait_until_worktree_trust {
+                    let already_trusted =  *wait_until_worktree_trust.borrow();
+                    if !already_trusted {
+                        log::info!(
+                            "Waiting for worktree {worktree_abs_path:?} to be trusted, before starting language server {}",
+                            adapter.name(),
+                        );
+                        while let Some(worktree_trusted) = wait_until_worktree_trust.recv().await {
+                            if worktree_trusted {
+                                break;
+                            }
+                        }
+                        log::info!(
+                            "Worktree {worktree_abs_path:?} is trusted, starting language server {}",
+                            adapter.name(),
+                        );
+                    }
                     languages
                         .update_lsp_binary_status(adapter.name(), BinaryStatus::Starting);
                 }
@@ -668,16 +686,23 @@ impl LocalLspStore {
         };
 
         cx.spawn(async move |cx| {
-            if let Some(untrusted_worktree_task) = untrusted_worktree_task {
-                log::info!(
-                    "Waiting for worktree {worktree_abs_path:?} to be trusted, before starting language server {}",
-                    adapter.name(),
-                );
-                untrusted_worktree_task.recv().await.ok();
-                log::info!(
-                    "Worktree {worktree_abs_path:?} is trusted, starting language server {}",
-                    adapter.name(),
-                );
+            if let Some(mut wait_until_worktree_trust) = wait_until_worktree_trust {
+                let already_trusted =  *wait_until_worktree_trust.borrow();
+                if !already_trusted {
+                    log::info!(
+                        "Waiting for worktree {worktree_abs_path:?} to be trusted, before starting language server {}",
+                        adapter.name(),
+                    );
+                    while let Some(worktree_trusted) = wait_until_worktree_trust.recv().await {
+                        if worktree_trusted {
+                            break;
+                        }
+                    }
+                    log::info!(
+                        "Worktree {worktree_abs_path:?} is trusted, starting language server {}",
+                            adapter.name(),
+                    );
+                }
             }
 
             let (existing_binary, maybe_download_binary) = adapter
@@ -13706,7 +13731,7 @@ pub struct LanguageServerPromptRequest {
     pub message: String,
     pub actions: Vec<MessageActionItem>,
     pub lsp_name: String,
-    pub(crate) response_channel: Sender<MessageActionItem>,
+    pub(crate) response_channel: smol::channel::Sender<MessageActionItem>,
 }
 
 impl LanguageServerPromptRequest {