Use language server ids for lsp tool

Lukas Wirth created

Change summary

crates/activity_indicator/src/activity_indicator.rs    | 22 ++
crates/extension_host/src/extension_host.rs            |  5 
crates/extension_host/src/extension_store_test.rs      | 11 +
crates/extension_host/src/headless_host.rs             |  8 
crates/language/src/language.rs                        | 70 +++++++--
crates/language/src/language_registry.rs               | 26 ++
crates/language_extension/src/extension_lsp_adapter.rs | 41 ++--
crates/language_tools/src/lsp_tool.rs                  | 86 +++--------
crates/project/src/lsp_store.rs                        | 55 +++++--
9 files changed, 186 insertions(+), 138 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -56,6 +56,7 @@ pub struct ActivityIndicator {
 #[derive(Debug)]
 struct ServerStatus {
     name: LanguageServerName,
+    id: LanguageServerId,
     status: LanguageServerStatusUpdate,
 }
 
@@ -86,11 +87,12 @@ impl ActivityIndicator {
         let this = cx.new(|cx| {
             let mut status_events = languages.language_server_binary_statuses();
             cx.spawn(async move |this, cx| {
-                while let Some((name, binary_status)) = status_events.next().await {
+                while let Some((id, name, binary_status)) = status_events.next().await {
                     this.update(cx, |this: &mut ActivityIndicator, cx| {
-                        this.statuses.retain(|s| s.name != name);
+                        this.statuses.retain(|s| s.id != id);
                         this.statuses.push(ServerStatus {
                             name,
+                            id,
                             status: LanguageServerStatusUpdate::Binary(binary_status),
                         });
                         cx.notify();
@@ -117,7 +119,13 @@ impl ActivityIndicator {
             cx.subscribe(
                 &project.read(cx).lsp_store(),
                 |activity_indicator, _, event, cx| {
-                    if let LspStoreEvent::LanguageServerUpdate { name, message, .. } = event {
+                    if let LspStoreEvent::LanguageServerUpdate {
+                        language_server_id,
+                        name,
+                        message,
+                        ..
+                    } = event
+                    {
                         if let proto::update_language_server::Variant::StatusUpdate(status_update) =
                             message
                         {
@@ -180,9 +188,11 @@ impl ActivityIndicator {
                             };
 
                             activity_indicator.statuses.retain(|s| s.name != name);
-                            activity_indicator
-                                .statuses
-                                .push(ServerStatus { name, status });
+                            activity_indicator.statuses.push(ServerStatus {
+                                name,
+                                id: *language_server_id,
+                                status,
+                            });
                         }
                         cx.notify()
                     }

crates/extension_host/src/extension_host.rs 🔗

@@ -1361,11 +1361,12 @@ impl ExtensionStore {
                 for (manifest, wasm_extension) in &wasm_extensions {
                     let extension = Arc::new(wasm_extension.clone());
 
-                    for (language_server_id, language_server_config) in &manifest.language_servers {
+                    for (language_server_name, language_server_config) in &manifest.language_servers
+                    {
                         for language in language_server_config.languages() {
                             this.proxy.register_language_server(
                                 extension.clone(),
-                                language_server_id.clone(),
+                                language_server_name.clone(),
                                 language.clone(),
                             );
                         }

crates/extension_host/src/extension_store_test.rs 🔗

@@ -12,7 +12,7 @@ use gpui::{AppContext as _, SemanticVersion, TestAppContext};
 use http_client::{FakeHttpClient, Response};
 use language::{BinaryStatus, LanguageMatcher, LanguageName, LanguageRegistry};
 use language_extension::LspAccess;
-use lsp::LanguageServerName;
+use lsp::{LanguageServerId, LanguageServerName};
 use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 use project::{DEFAULT_COMPLETION_CONTEXT, Project};
@@ -737,18 +737,25 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
         ],
         [
             (
+                LanguageServerId(0),
                 LanguageServerName::new_static("gleam"),
                 BinaryStatus::Starting
             ),
             (
+                LanguageServerId(0),
                 LanguageServerName::new_static("gleam"),
                 BinaryStatus::CheckingForUpdate
             ),
             (
+                LanguageServerId(0),
                 LanguageServerName::new_static("gleam"),
                 BinaryStatus::Downloading
             ),
-            (LanguageServerName::new_static("gleam"), BinaryStatus::None)
+            (
+                LanguageServerId(0),
+                LanguageServerName::new_static("gleam"),
+                BinaryStatus::None
+            )
         ]
     );
 

crates/extension_host/src/headless_host.rs 🔗

@@ -177,21 +177,21 @@ impl HeadlessExtensionStore {
         let wasm_extension: Arc<dyn Extension> =
             Arc::new(WasmExtension::load(&extension_dir, &manifest, wasm_host.clone(), cx).await?);
 
-        for (language_server_id, language_server_config) in &manifest.language_servers {
+        for (language_server_name, language_server_config) in &manifest.language_servers {
             for language in language_server_config.languages() {
                 this.update(cx, |this, _cx| {
                     this.loaded_language_servers
                         .entry(manifest.id.clone())
                         .or_default()
-                        .push((language_server_id.clone(), language.clone()));
+                        .push((language_server_name.clone(), language.clone()));
                     this.proxy.register_language_server(
                         wasm_extension.clone(),
-                        language_server_id.clone(),
+                        language_server_name.clone(),
                         language.clone(),
                     );
                 })?;
             }
-            log::info!("Loaded language server: {}", language_server_id);
+            log::info!("Loaded language server: {}", language_server_name);
         }
 
         for (debug_adapter, meta) in &manifest.debug_adapters {

crates/language/src/language.rs 🔗

@@ -214,12 +214,20 @@ impl CachedLspAdapter {
         delegate: Arc<dyn LspAdapterDelegate>,
         toolchains: Option<Toolchain>,
         binary_options: LanguageServerBinaryOptions,
+        server_id: LanguageServerId,
         cx: &mut AsyncApp,
     ) -> Result<LanguageServerBinary> {
         let cached_binary = self.cached_binary.lock().await;
         self.adapter
             .clone()
-            .get_language_server_command(delegate, toolchains, binary_options, cached_binary, cx)
+            .get_language_server_command(
+                delegate,
+                toolchains,
+                binary_options,
+                cached_binary,
+                server_id,
+                cx,
+            )
             .await
     }
 
@@ -291,7 +299,12 @@ pub trait LspAdapterDelegate: Send + Sync {
     fn http_client(&self) -> Arc<dyn HttpClient>;
     fn worktree_id(&self) -> WorktreeId;
     fn worktree_root_path(&self) -> &Path;
-    fn update_status(&self, language: LanguageServerName, status: BinaryStatus);
+    fn update_status(
+        &self,
+        language: LanguageServerId,
+        server_name: LanguageServerName,
+        status: BinaryStatus,
+    );
     fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>;
     async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
 
@@ -315,6 +328,7 @@ pub trait LspAdapter: 'static + Send + Sync {
         toolchains: Option<Toolchain>,
         binary_options: LanguageServerBinaryOptions,
         mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
+        server_id: LanguageServerId,
         cx: &'a mut AsyncApp,
     ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
         async move {
@@ -330,27 +344,41 @@ pub trait LspAdapter: 'static + Send + Sync {
             // because we don't want to download and overwrite our global one
             // for each worktree we might have open.
             if binary_options.allow_path_lookup
-                && let Some(binary) = self.check_if_user_installed(delegate.as_ref(), toolchains, cx).await {
-                    log::debug!(
-                        "found user-installed language server for {}. path: {:?}, arguments: {:?}",
-                        self.name().0,
-                        binary.path,
-                        binary.arguments
-                    );
-                    return Ok(binary);
-                }
+                && let Some(binary) = self
+                    .check_if_user_installed(delegate.as_ref(), toolchains, cx)
+                    .await
+            {
+                log::debug!(
+                    "found user-installed language server for {}. path: {:?}, arguments: {:?}",
+                    self.name().0,
+                    binary.path,
+                    binary.arguments
+                );
+                return Ok(binary);
+            }
 
-            anyhow::ensure!(binary_options.allow_binary_download, "downloading language servers disabled");
+            anyhow::ensure!(
+                binary_options.allow_binary_download,
+                "downloading language servers disabled"
+            );
 
             if let Some(cached_binary) = cached_binary.as_ref() {
                 return Ok(cached_binary.clone());
             }
 
-            let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await else {
+            let Some(container_dir) = delegate.language_server_download_dir(&self.name()).await
+            else {
                 anyhow::bail!("no language server download dir defined")
             };
 
-            let mut binary = try_fetch_server_binary(self.as_ref(), &delegate, container_dir.to_path_buf(), cx).await;
+            let mut binary = try_fetch_server_binary(
+                self.as_ref(),
+                &delegate,
+                container_dir.to_path_buf(),
+                server_id,
+                cx,
+            )
+            .await;
 
             if let Err(error) = binary.as_ref() {
                 if let Some(prev_downloaded_binary) = self
@@ -358,7 +386,8 @@ pub trait LspAdapter: 'static + Send + Sync {
                     .await
                 {
                     log::info!(
-                        "failed to fetch newest version of language server {:?}. error: {:?}, falling back to using {:?}",
+                        "failed to fetch newest version of language server {:?}. \
+                        error: {:?}, falling back to using {:?}",
                         self.name(),
                         error,
                         prev_downloaded_binary.path
@@ -366,6 +395,7 @@ pub trait LspAdapter: 'static + Send + Sync {
                     binary = Ok(prev_downloaded_binary);
                 } else {
                     delegate.update_status(
+                        server_id,
                         self.name(),
                         BinaryStatus::Failed {
                             error: format!("{error:?}"),
@@ -594,6 +624,7 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>
     adapter: &L,
     delegate: &Arc<dyn LspAdapterDelegate>,
     container_dir: PathBuf,
+    server_id: LanguageServerId,
     cx: &mut AsyncApp,
 ) -> Result<LanguageServerBinary> {
     if let Some(task) = adapter.will_fetch_server(delegate, cx) {
@@ -602,7 +633,7 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>
 
     let name = adapter.name();
     log::debug!("fetching latest version of language server {:?}", name.0);
-    delegate.update_status(name.clone(), BinaryStatus::CheckingForUpdate);
+    delegate.update_status(server_id, adapter.name(), BinaryStatus::CheckingForUpdate);
 
     let latest_version = adapter
         .fetch_latest_server_version(delegate.as_ref())
@@ -613,16 +644,16 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>
         .await
     {
         log::debug!("language server {:?} is already installed", name.0);
-        delegate.update_status(name.clone(), BinaryStatus::None);
+        delegate.update_status(server_id, adapter.name(), BinaryStatus::None);
         Ok(binary)
     } else {
         log::info!("downloading language server {:?}", name.0);
-        delegate.update_status(adapter.name(), BinaryStatus::Downloading);
+        delegate.update_status(server_id, adapter.name(), BinaryStatus::Downloading);
         let binary = adapter
             .fetch_server_binary(latest_version, container_dir, delegate.as_ref())
             .await;
 
-        delegate.update_status(name.clone(), BinaryStatus::None);
+        delegate.update_status(server_id, adapter.name(), BinaryStatus::None);
         binary
     }
 }
@@ -2197,6 +2228,7 @@ impl LspAdapter for FakeLspAdapter {
         _: Option<Toolchain>,
         _: LanguageServerBinaryOptions,
         _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
+        _: LanguageServerId,
         _: &'a mut AsyncApp,
     ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
         async move { Ok(self.language_server_binary.clone()) }.boxed_local()

crates/language/src/language_registry.rs 🔗

@@ -252,7 +252,9 @@ pub struct LanguageQueries {
 
 #[derive(Clone, Default)]
 struct ServerStatusSender {
-    txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, BinaryStatus)>>>>,
+    txs: Arc<
+        Mutex<Vec<mpsc::UnboundedSender<(LanguageServerId, LanguageServerName, BinaryStatus)>>>,
+    >,
 }
 
 pub struct LoadedLanguage {
@@ -1077,8 +1079,13 @@ impl LanguageRegistry {
         self.state.read().all_lsp_adapters.get(name).cloned()
     }
 
-    pub fn update_lsp_binary_status(&self, server_name: LanguageServerName, status: BinaryStatus) {
-        self.lsp_binary_status_tx.send(server_name, status);
+    pub fn update_lsp_binary_status(
+        &self,
+        server_id: LanguageServerId,
+        name: LanguageServerName,
+        status: BinaryStatus,
+    ) {
+        self.lsp_binary_status_tx.send(server_id, name, status);
     }
 
     pub fn next_language_server_id(&self) -> LanguageServerId {
@@ -1133,7 +1140,7 @@ impl LanguageRegistry {
 
     pub fn language_server_binary_statuses(
         &self,
-    ) -> mpsc::UnboundedReceiver<(LanguageServerName, BinaryStatus)> {
+    ) -> mpsc::UnboundedReceiver<(LanguageServerId, LanguageServerName, BinaryStatus)> {
         self.lsp_binary_status_tx.subscribe()
     }
 
@@ -1247,14 +1254,19 @@ impl LanguageRegistryState {
 }
 
 impl ServerStatusSender {
-    fn subscribe(&self) -> mpsc::UnboundedReceiver<(LanguageServerName, BinaryStatus)> {
+    fn subscribe(
+        &self,
+    ) -> mpsc::UnboundedReceiver<(LanguageServerId, LanguageServerName, BinaryStatus)> {
         let (tx, rx) = mpsc::unbounded();
         self.txs.lock().push(tx);
         rx
     }
 
-    fn send(&self, name: LanguageServerName, status: BinaryStatus) {
+    fn send(&self, id: LanguageServerId, name: LanguageServerName, status: BinaryStatus) {
         let mut txs = self.txs.lock();
-        txs.retain(|tx| tx.unbounded_send((name.clone(), status.clone())).is_ok());
+        txs.retain(|tx| {
+            tx.unbounded_send((id, name.clone(), status.clone()))
+                .is_ok()
+        });
     }
 }

crates/language_extension/src/extension_lsp_adapter.rs 🔗

@@ -16,8 +16,8 @@ use language::{
     Toolchain,
 };
 use lsp::{
-    CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName,
-    LanguageServerSelector,
+    CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId,
+    LanguageServerName, LanguageServerSelector,
 };
 use serde::Serialize;
 use serde_json::Value;
@@ -58,14 +58,14 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
     fn register_language_server(
         &self,
         extension: Arc<dyn Extension>,
-        language_server_id: LanguageServerName,
+        language_server_name: LanguageServerName,
         language: LanguageName,
     ) {
         self.language_registry.register_lsp_adapter(
             language.clone(),
             Arc::new(ExtensionLspAdapter::new(
                 extension,
-                language_server_id,
+                language_server_name,
                 language,
             )),
         );
@@ -122,29 +122,29 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
 
     fn update_language_server_status(
         &self,
-        language_server_id: LanguageServerName,
+        language_server_name: LanguageServerName,
         status: BinaryStatus,
     ) {
-        self.language_registry
-            .update_lsp_binary_status(language_server_id, status);
+        // self.language_registry
+        //     .update_lsp_binary_status(language_server_name, status);
     }
 }
 
 struct ExtensionLspAdapter {
     extension: Arc<dyn Extension>,
-    language_server_id: LanguageServerName,
+    language_server_name: LanguageServerName,
     language_name: LanguageName,
 }
 
 impl ExtensionLspAdapter {
     fn new(
         extension: Arc<dyn Extension>,
-        language_server_id: LanguageServerName,
+        language_server_name: LanguageServerName,
         language_name: LanguageName,
     ) -> Self {
         Self {
             extension,
-            language_server_id,
+            language_server_name,
             language_name,
         }
     }
@@ -153,7 +153,7 @@ impl ExtensionLspAdapter {
 #[async_trait(?Send)]
 impl LspAdapter for ExtensionLspAdapter {
     fn name(&self) -> LanguageServerName {
-        self.language_server_id.clone()
+        self.language_server_name.clone()
     }
 
     fn get_language_server_command<'a>(
@@ -162,6 +162,7 @@ impl LspAdapter for ExtensionLspAdapter {
         _: Option<Toolchain>,
         _: LanguageServerBinaryOptions,
         _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
+        _: LanguageServerId,
         _: &'a mut AsyncApp,
     ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
         async move {
@@ -169,7 +170,7 @@ impl LspAdapter for ExtensionLspAdapter {
             let command = self
                 .extension
                 .language_server_command(
-                    self.language_server_id.clone(),
+                    self.language_server_name.clone(),
                     self.language_name.clone(),
                     delegate,
                 )
@@ -230,7 +231,7 @@ impl LspAdapter for ExtensionLspAdapter {
             .extension
             .manifest()
             .language_servers
-            .get(&self.language_server_id)
+            .get(&self.language_server_name)
             .and_then(|server| server.code_action_kinds.clone());
 
         code_action_kinds.or(Some(vec![
@@ -256,7 +257,7 @@ impl LspAdapter for ExtensionLspAdapter {
         self.extension
             .manifest()
             .language_servers
-            .get(&self.language_server_id)
+            .get(&self.language_server_name)
             .map(|server| server.language_ids.clone())
             .unwrap_or_default()
     }
@@ -270,7 +271,7 @@ impl LspAdapter for ExtensionLspAdapter {
         let json_options = self
             .extension
             .language_server_initialization_options(
-                self.language_server_id.clone(),
+                self.language_server_name.clone(),
                 self.language_name.clone(),
                 delegate,
             )
@@ -294,7 +295,7 @@ impl LspAdapter for ExtensionLspAdapter {
         let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
         let json_options: Option<String> = self
             .extension
-            .language_server_workspace_configuration(self.language_server_id.clone(), delegate)
+            .language_server_workspace_configuration(self.language_server_name.clone(), delegate)
             .await?;
         Ok(if let Some(json_options) = json_options {
             serde_json::from_str(&json_options).with_context(|| {
@@ -315,7 +316,7 @@ impl LspAdapter for ExtensionLspAdapter {
         let json_options: Option<String> = self
             .extension
             .language_server_additional_initialization_options(
-                self.language_server_id.clone(),
+                self.language_server_name.clone(),
                 target_language_server_id.clone(),
                 delegate,
             )
@@ -343,7 +344,7 @@ impl LspAdapter for ExtensionLspAdapter {
         let json_options: Option<String> = self
             .extension
             .language_server_additional_workspace_configuration(
-                self.language_server_id.clone(),
+                self.language_server_name.clone(),
                 target_language_server_id.clone(),
                 delegate,
             )
@@ -370,7 +371,7 @@ impl LspAdapter for ExtensionLspAdapter {
 
         let labels = self
             .extension
-            .labels_for_completions(self.language_server_id.clone(), completions)
+            .labels_for_completions(self.language_server_name.clone(), completions)
             .await?;
 
         Ok(labels_from_extension(labels, language))
@@ -392,7 +393,7 @@ impl LspAdapter for ExtensionLspAdapter {
 
         let labels = self
             .extension
-            .labels_for_symbols(self.language_server_id.clone(), symbols)
+            .labels_for_symbols(self.language_server_name.clone(), symbols)
             .await?;
 
         Ok(labels_from_extension(labels, language))

crates/language_tools/src/lsp_tool.rs 🔗

@@ -65,7 +65,7 @@ impl std::fmt::Debug for ActiveEditor {
 #[derive(Debug, Default, Clone)]
 struct LanguageServers {
     health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,
-    binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,
+    binary_statuses: HashMap<LanguageServerId, LanguageServerBinaryStatus>,
     servers_per_buffer_abs_path: HashMap<PathBuf, ServersForPath>,
 }
 
@@ -85,6 +85,7 @@ struct LanguageServerHealthStatus {
 struct LanguageServerBinaryStatus {
     status: BinaryStatus,
     message: Option<SharedString>,
+    name: LanguageServerName,
 }
 
 #[derive(Debug)]
@@ -370,17 +371,19 @@ impl LanguageServers {
         binary_status: BinaryStatus,
         message: Option<&str>,
         name: LanguageServerName,
+        id: LanguageServerId,
     ) {
         let binary_status_message = message.map(SharedString::new);
         if matches!(
             binary_status,
             BinaryStatus::Stopped | BinaryStatus::Failed { .. }
         ) {
-            self.health_statuses.retain(|_, server| server.name != name);
+            self.health_statuses.remove(&id);
         }
         self.binary_statuses.insert(
-            name,
+            id,
             LanguageServerBinaryStatus {
+                name,
                 status: binary_status,
                 message: binary_status_message,
             },
@@ -583,18 +586,16 @@ impl LspTool {
                             proto::ServerBinaryStatus::Starting => BinaryStatus::Starting,
                             proto::ServerBinaryStatus::Stopping => BinaryStatus::Stopping,
                             proto::ServerBinaryStatus::Stopped => BinaryStatus::Stopped,
-                            proto::ServerBinaryStatus::Failed => {
-                                let Some(error) = status_update.message.clone() else {
-                                    return;
-                                };
-                                BinaryStatus::Failed { error }
-                            }
+                            proto::ServerBinaryStatus::Failed => BinaryStatus::Failed {
+                                error: status_update.message.clone().unwrap_or_default(),
+                            },
                         };
                         self.server_state.update(cx, |state, _| {
                             state.language_servers.update_binary_status(
                                 binary_status,
                                 status_update.message.as_deref(),
                                 name.clone(),
+                                *language_server_id,
                             );
                         });
                         updated = true;
@@ -684,27 +685,16 @@ impl LspTool {
                         })
                 })
                 .collect::<HashSet<_>>();
-
             let mut server_ids_to_worktrees =
                 HashMap::<LanguageServerId, Entity<Worktree>>::default();
-            let mut server_names_to_worktrees = HashMap::<
-                LanguageServerName,
-                HashSet<(Entity<Worktree>, LanguageServerId)>,
-            >::default();
             for servers_for_path in state.language_servers.servers_per_buffer_abs_path.values() {
                 if let Some(worktree) = servers_for_path
                     .worktree
                     .as_ref()
                     .and_then(|worktree| worktree.upgrade())
                 {
-                    for (server_id, server_name) in &servers_for_path.servers {
+                    for (server_id, _) in &servers_for_path.servers {
                         server_ids_to_worktrees.insert(*server_id, worktree.clone());
-                        if let Some(server_name) = server_name {
-                            server_names_to_worktrees
-                                .entry(server_name.clone())
-                                .or_default()
-                                .insert((worktree.clone(), *server_id));
-                        }
                     }
                 }
             }
@@ -714,19 +704,12 @@ impl LspTool {
             let mut servers_with_health_checks = HashSet::default();
 
             for (server_id, health) in &state.language_servers.health_statuses {
-                let worktree = server_ids_to_worktrees.get(server_id).or_else(|| {
-                    let worktrees = server_names_to_worktrees.get(&health.name)?;
-                    worktrees
-                        .iter()
-                        .find(|(worktree, _)| active_worktrees.contains(worktree))
-                        .or_else(|| worktrees.iter().next())
-                        .map(|(worktree, _)| worktree)
-                });
-                servers_with_health_checks.insert(&health.name);
+                let worktree = server_ids_to_worktrees.get(server_id);
+                servers_with_health_checks.insert(*server_id);
                 let worktree_name =
                     worktree.map(|worktree| SharedString::new(worktree.read(cx).root_name()));
 
-                let binary_status = state.language_servers.binary_statuses.get(&health.name);
+                let binary_status = state.language_servers.binary_statuses.get(server_id);
                 let server_data = ServerData::WithHealthCheck {
                     server_id: *server_id,
                     health,
@@ -743,11 +726,11 @@ impl LspTool {
 
             let mut can_stop_all = !state.language_servers.health_statuses.is_empty();
             let mut can_restart_all = state.language_servers.health_statuses.is_empty();
-            for (server_name, binary_status) in state
+            for (server_id, binary_status) in state
                 .language_servers
                 .binary_statuses
                 .iter()
-                .filter(|(name, _)| !servers_with_health_checks.contains(name))
+                .filter(|&(id, _)| !servers_with_health_checks.contains(id))
             {
                 match binary_status.status {
                     BinaryStatus::None => {
@@ -774,40 +757,25 @@ impl LspTool {
                     BinaryStatus::Failed { .. } => {}
                 }
 
-                match server_names_to_worktrees.get(server_name) {
-                    Some(worktrees_for_name) => {
-                        match worktrees_for_name
-                            .iter()
-                            .find(|(worktree, _)| active_worktrees.contains(worktree))
-                            .or_else(|| worktrees_for_name.iter().next())
-                        {
-                            Some((worktree, server_id)) => {
-                                let worktree_name =
-                                    SharedString::new(worktree.read(cx).root_name());
-                                servers_per_worktree
-                                    .entry(worktree_name.clone())
-                                    .or_default()
-                                    .push(ServerData::WithBinaryStatus {
-                                        server_name,
-                                        binary_status,
-                                        server_id: Some(*server_id),
-                                    });
-                            }
-                            None => servers_without_worktree.push(ServerData::WithBinaryStatus {
+                let server_name = &binary_status.name;
+                match server_ids_to_worktrees.get(server_id) {
+                    Some(worktree) if active_worktrees.contains(worktree) => {
+                        let worktree_name = SharedString::new(worktree.read(cx).root_name());
+                        servers_per_worktree.entry(worktree_name).or_default().push(
+                            ServerData::WithBinaryStatus {
                                 server_name,
                                 binary_status,
-                                server_id: None,
-                            }),
-                        }
+                                server_id: Some(*server_id),
+                            },
+                        );
                     }
-                    None => servers_without_worktree.push(ServerData::WithBinaryStatus {
+                    _ => servers_without_worktree.push(ServerData::WithBinaryStatus {
                         server_name,
                         binary_status,
-                        server_id: None,
+                        server_id: Some(*server_id),
                     }),
                 }
             }
-
             let mut new_lsp_items =
                 Vec::with_capacity(servers_per_worktree.len() + servers_without_worktree.len() + 2);
             for (worktree_name, worktree_servers) in servers_per_worktree {

crates/project/src/lsp_store.rs 🔗

@@ -308,6 +308,7 @@ impl LocalLspStore {
             toolchain.clone(),
             delegate.clone(),
             true,
+            server_id,
             cx,
         );
         let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
@@ -351,11 +352,11 @@ impl LocalLspStore {
             }
         });
 
+        let server_name = adapter.name.clone();
         let startup = {
-            let server_name = adapter.name.0.clone();
+            let server_name = server_name.clone();
             let delegate = delegate as Arc<dyn LspAdapterDelegate>;
             let key = key.clone();
-            let adapter = adapter.clone();
             let lsp_store = self.weak.clone();
             let pending_workspace_folders = pending_workspace_folders.clone();
             let fs = self.fs.clone();
@@ -460,8 +461,10 @@ impl LocalLspStore {
 
                     Err(err) => {
                         let log = stderr_capture.lock().take().unwrap_or_default();
+                        log::error!("Failed to start language server {server_name:?}: {err:?}");
                         delegate.update_status(
-                            adapter.name(),
+                            server_id,
+                            server_name,
                             BinaryStatus::Failed {
                                 error: if log.is_empty() {
                                     format!("{err:#}")
@@ -470,7 +473,6 @@ impl LocalLspStore {
                                 },
                             },
                         );
-                        log::error!("Failed to start language server {server_name:?}: {err:?}");
                         if !log.is_empty() {
                             log::error!("server stderr: {log}");
                         }
@@ -485,7 +487,7 @@ impl LocalLspStore {
         };
 
         self.languages
-            .update_lsp_binary_status(adapter.name(), BinaryStatus::Starting);
+            .update_lsp_binary_status(server_id, server_name, BinaryStatus::Starting);
 
         self.language_servers.insert(server_id, state);
         self.language_server_ids
@@ -504,6 +506,7 @@ impl LocalLspStore {
         toolchain: Option<Toolchain>,
         delegate: Arc<dyn LspAdapterDelegate>,
         allow_binary_download: bool,
+        server_id: LanguageServerId,
         cx: &mut App,
     ) -> Task<Result<LanguageServerBinary>> {
         if let Some(settings) = settings.binary.as_ref()
@@ -539,10 +542,16 @@ impl LocalLspStore {
         cx.spawn(async move |cx| {
             let binary_result = adapter
                 .clone()
-                .get_language_server_command(delegate.clone(), toolchain, lsp_binary_options, cx)
+                .get_language_server_command(
+                    delegate.clone(),
+                    toolchain,
+                    lsp_binary_options,
+                    server_id,
+                    cx,
+                )
                 .await;
 
-            delegate.update_status(adapter.name.clone(), BinaryStatus::None);
+            delegate.update_status(server_id, adapter.name(), BinaryStatus::None);
 
             let mut binary = binary_result?;
             let mut shell_env = delegate.shell_env().await;
@@ -10409,17 +10418,22 @@ impl LspStore {
 
         if let Some(name) = name {
             log::info!("stopping language server {name}");
-            self.languages
-                .update_lsp_binary_status(name.clone(), BinaryStatus::Stopping);
+            self.languages.update_lsp_binary_status(
+                server_id,
+                name.clone(),
+                BinaryStatus::Stopping,
+            );
             cx.notify();
 
             return cx.spawn(async move |lsp_store, cx| {
                 Self::shutdown_language_server(server_state, name.clone(), cx).await;
                 lsp_store
                     .update(cx, |lsp_store, cx| {
-                        lsp_store
-                            .languages
-                            .update_lsp_binary_status(name, BinaryStatus::Stopped);
+                        lsp_store.languages.update_lsp_binary_status(
+                            server_id,
+                            name.clone(),
+                            BinaryStatus::Stopped,
+                        );
                         cx.emit(LspStoreEvent::LanguageServerRemoved(server_id));
                         cx.notify();
                     })
@@ -10882,7 +10896,7 @@ impl LspStore {
         );
         local
             .languages
-            .update_lsp_binary_status(adapter.name(), BinaryStatus::None);
+            .update_lsp_binary_status(server_id, adapter.name(), BinaryStatus::None);
         if let Some(file_ops_caps) = language_server
             .capabilities()
             .workspace
@@ -12197,7 +12211,7 @@ fn subscribe_to_binary_statuses(
 ) -> Task<()> {
     let mut server_statuses = languages.language_server_binary_statuses();
     cx.spawn(async move |lsp_store, cx| {
-        while let Some((server_name, binary_status)) = server_statuses.next().await {
+        while let Some((server_id, server_name, binary_status)) = server_statuses.next().await {
             if lsp_store
                 .update(cx, |_, cx| {
                     let mut message = None;
@@ -12216,9 +12230,7 @@ fn subscribe_to_binary_statuses(
                         }
                     };
                     cx.emit(LspStoreEvent::LanguageServerUpdate {
-                        // Binary updates are about the binary that might not have any language server id at that point.
-                        // Reuse `LanguageServerUpdate` for them and provide a fake id that won't be used on the receiver side.
-                        language_server_id: LanguageServerId(0),
+                        language_server_id: server_id,
                         name: Some(server_name),
                         message: proto::update_language_server::Variant::StatusUpdate(
                             proto::StatusUpdate {
@@ -13154,9 +13166,14 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
         Ok(())
     }
 
-    fn update_status(&self, server_name: LanguageServerName, status: language::BinaryStatus) {
+    fn update_status(
+        &self,
+        server_id: LanguageServerId,
+        server_name: LanguageServerName,
+        status: language::BinaryStatus,
+    ) {
         self.language_registry
-            .update_lsp_binary_status(server_name, status);
+            .update_lsp_binary_status(server_id, server_name, status);
     }
 
     fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>> {