project: Send LSP metadata to remote ServerInfo (#42831)

Mayank Verma and Kirill Bulatov created

Closes #39582

Release Notes:

- Added LSP metadata to remote ServerInfo

Here's the before/after:


https://github.com/user-attachments/assets/1057faa5-82af-4975-abad-5e10e139fac1

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>

Change summary

crates/language_tools/src/lsp_log_view.rs | 82 ++++++++++++++----------
crates/project/src/lsp_store.rs           | 46 +++++++++++++
crates/project/src/project.rs             | 45 ++++++++++---
crates/proto/proto/lsp.proto              |  8 ++
4 files changed, 136 insertions(+), 45 deletions(-)

Detailed changes

crates/language_tools/src/lsp_log_view.rs 🔗

@@ -7,12 +7,15 @@ use gpui::{
 };
 use language::{LanguageServerId, language_settings::SoftWrap};
 use lsp::{
-    LanguageServer, LanguageServerBinary, LanguageServerName, LanguageServerSelector, MessageType,
-    SetTraceParams, TraceValue, notification::SetTrace,
+    LanguageServer, LanguageServerName, LanguageServerSelector, MessageType, SetTraceParams,
+    TraceValue, notification::SetTrace,
 };
 use project::{
-    Project,
-    lsp_store::log_store::{self, Event, LanguageServerKind, LogKind, LogStore, Message},
+    LanguageServerStatus, Project,
+    lsp_store::{
+        LanguageServerBinaryInfo,
+        log_store::{self, Event, LanguageServerKind, LogKind, LogStore, Message},
+    },
     search::SearchQuery,
 };
 use proto::toggle_lsp_logs::LogType;
@@ -337,16 +340,28 @@ impl LspLogView {
 * Capabilities: {CAPABILITIES}
 
 * Configuration: {CONFIGURATION}",
-            NAME = info.name,
+            NAME = info.status.name,
             ID = info.id,
-            BINARY = info
-                .binary
-                .as_ref()
-                .map_or_else(|| "Unknown".to_string(), |binary| format!("{binary:#?}")),
-            WORKSPACE_FOLDERS = info.workspace_folders.join(", "),
+            BINARY = info.status.binary.as_ref().map_or_else(
+                || "Unknown".to_string(),
+                |binary| serde_json::to_string_pretty(binary)
+                    .unwrap_or_else(|e| format!("Failed to serialize binary info: {e:#}"))
+            ),
+            WORKSPACE_FOLDERS = info
+                .status
+                .workspace_folders
+                .iter()
+                .filter_map(|uri| {
+                    uri.to_file_path()
+                        .ok()
+                        .map(|path| path.to_string_lossy().into_owned())
+                })
+                .collect::<Vec<_>>()
+                .join(", "),
             CAPABILITIES = serde_json::to_string_pretty(&info.capabilities)
                 .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")),
             CONFIGURATION = info
+                .status
                 .configuration
                 .map(|configuration| serde_json::to_string_pretty(&configuration))
                 .transpose()
@@ -633,17 +648,12 @@ impl LspLogView {
                     .or_else(move || {
                         let capabilities =
                             lsp_store.lsp_server_capabilities.get(&server_id)?.clone();
-                        let name = lsp_store
-                            .language_server_statuses
-                            .get(&server_id)
-                            .map(|status| status.name.clone())?;
+                        let status = lsp_store.language_server_statuses.get(&server_id)?.clone();
+
                         Some(ServerInfo {
                             id: server_id,
                             capabilities,
-                            binary: None,
-                            name,
-                            workspace_folders: Vec::new(),
-                            configuration: None,
+                            status,
                         })
                     })
             })
@@ -1314,10 +1324,7 @@ impl LspLogToolbarItemView {
 struct ServerInfo {
     id: LanguageServerId,
     capabilities: lsp::ServerCapabilities,
-    binary: Option<LanguageServerBinary>,
-    name: LanguageServerName,
-    workspace_folders: Vec<String>,
-    configuration: Option<serde_json::Value>,
+    status: LanguageServerStatus,
 }
 
 impl ServerInfo {
@@ -1325,18 +1332,25 @@ impl ServerInfo {
         Self {
             id: server.server_id(),
             capabilities: server.capabilities(),
-            binary: Some(server.binary().clone()),
-            name: server.name(),
-            workspace_folders: server
-                .workspace_folders()
-                .into_iter()
-                .filter_map(|path| {
-                    path.to_file_path()
-                        .ok()
-                        .map(|path| path.to_string_lossy().into_owned())
-                })
-                .collect::<Vec<_>>(),
-            configuration: Some(server.configuration().clone()),
+            status: LanguageServerStatus {
+                name: server.name(),
+                pending_work: Default::default(),
+                has_pending_diagnostic_updates: false,
+                progress_tokens: Default::default(),
+                worktree: None,
+                binary: Some(LanguageServerBinaryInfo {
+                    path: server.binary().path.to_string_lossy().into_owned(),
+                    arguments: server
+                        .binary()
+                        .arguments
+                        .iter()
+                        .map(|arg| arg.to_string_lossy().into_owned())
+                        .collect(),
+                    env: server.binary().env.clone(),
+                }),
+                configuration: Some(server.configuration().clone()),
+                workspace_folders: server.workspace_folders(),
+            },
         }
     }
 }

crates/project/src/lsp_store.rs 🔗

@@ -93,6 +93,7 @@ use rpc::{
     proto::{LspRequestId, LspRequestMessage as _},
 };
 use serde::Serialize;
+use serde_json::Value;
 use settings::{Settings, SettingsLocation, SettingsStore};
 use sha2::{Digest, Sha256};
 use smol::channel::Sender;
@@ -3557,6 +3558,21 @@ fn notify_server_capabilities_updated(server: &LanguageServer, cx: &mut Context<
             message: proto::update_language_server::Variant::MetadataUpdated(
                 proto::ServerMetadataUpdated {
                     capabilities: Some(capabilities),
+                    binary: Some(proto::LanguageServerBinaryInfo {
+                        path: server.binary().path.to_string_lossy().into_owned(),
+                        arguments: server
+                            .binary()
+                            .arguments
+                            .iter()
+                            .map(|arg| arg.to_string_lossy().into_owned())
+                            .collect(),
+                    }),
+                    configuration: serde_json::to_string(server.configuration()).ok(),
+                    workspace_folders: server
+                        .workspace_folders()
+                        .iter()
+                        .map(|uri| uri.to_string())
+                        .collect(),
                 },
             ),
         });
@@ -3713,13 +3729,23 @@ pub enum LspStoreEvent {
     },
 }
 
+#[derive(Clone, Debug, Serialize)]
+pub struct LanguageServerBinaryInfo {
+    pub path: String,
+    pub arguments: Vec<String>,
+    pub env: Option<HashMap<String, String>>,
+}
+
 #[derive(Clone, Debug, Serialize)]
 pub struct LanguageServerStatus {
     pub name: LanguageServerName,
     pub pending_work: BTreeMap<ProgressToken, LanguageServerProgress>,
     pub has_pending_diagnostic_updates: bool,
-    progress_tokens: HashSet<ProgressToken>,
+    pub progress_tokens: HashSet<ProgressToken>,
     pub worktree: Option<WorktreeId>,
+    pub binary: Option<LanguageServerBinaryInfo>,
+    pub configuration: Option<Value>,
+    pub workspace_folders: BTreeSet<Uri>,
 }
 
 #[derive(Clone, Debug)]
@@ -8130,6 +8156,9 @@ impl LspStore {
                         has_pending_diagnostic_updates: false,
                         progress_tokens: Default::default(),
                         worktree,
+                        binary: None,
+                        configuration: None,
+                        workspace_folders: BTreeSet::new(),
                     },
                 )
             })
@@ -9139,6 +9168,9 @@ impl LspStore {
                     has_pending_diagnostic_updates: false,
                     progress_tokens: Default::default(),
                     worktree: server.worktree_id.map(WorktreeId::from_proto),
+                    binary: None,
+                    configuration: None,
+                    workspace_folders: BTreeSet::new(),
                 },
             );
             cx.emit(LspStoreEvent::LanguageServerAdded(
@@ -11155,6 +11187,18 @@ impl LspStore {
                 has_pending_diagnostic_updates: false,
                 progress_tokens: Default::default(),
                 worktree: Some(key.worktree_id),
+                binary: Some(LanguageServerBinaryInfo {
+                    path: language_server.binary().path.to_string_lossy().into_owned(),
+                    arguments: language_server
+                        .binary()
+                        .arguments
+                        .iter()
+                        .map(|arg| arg.to_string_lossy().into_owned())
+                        .collect(),
+                    env: language_server.binary().env.clone(),
+                }),
+                configuration: Some(language_server.configuration().clone()),
+                workspace_folders: language_server.workspace_folders(),
             },
         );
 

crates/project/src/project.rs 🔗

@@ -37,7 +37,7 @@ use dap::inline_value::{InlineValueLocation, VariableLookupKind, VariableScope};
 
 use crate::{
     git_store::GitStore,
-    lsp_store::{SymbolLocation, log_store::LogKind},
+    lsp_store::{LanguageServerBinaryInfo, SymbolLocation, log_store::LogKind},
     project_search::SearchResultsHandle,
 };
 pub use agent_server_store::{AgentServerStore, AgentServersUpdated, ExternalAgentServerName};
@@ -114,7 +114,7 @@ use std::{
     ops::{Not as _, Range},
     path::{Path, PathBuf},
     pin::pin,
-    str,
+    str::{self, FromStr},
     sync::Arc,
     time::Duration,
 };
@@ -3111,17 +3111,42 @@ impl Project {
 
                 match message {
                     proto::update_language_server::Variant::MetadataUpdated(update) => {
-                        if let Some(capabilities) = update
-                            .capabilities
-                            .as_ref()
-                            .and_then(|capabilities| serde_json::from_str(capabilities).ok())
-                        {
-                            self.lsp_store.update(cx, |lsp_store, _| {
+                        self.lsp_store.update(cx, |lsp_store, _| {
+                            if let Some(capabilities) = update
+                                .capabilities
+                                .as_ref()
+                                .and_then(|capabilities| serde_json::from_str(capabilities).ok())
+                            {
                                 lsp_store
                                     .lsp_server_capabilities
                                     .insert(*language_server_id, capabilities);
-                            });
-                        }
+                            }
+
+                            if let Some(language_server_status) = lsp_store
+                                .language_server_statuses
+                                .get_mut(language_server_id)
+                            {
+                                if let Some(binary) = &update.binary {
+                                    language_server_status.binary =
+                                        Some(LanguageServerBinaryInfo {
+                                            path: binary.path.clone(),
+                                            arguments: binary.arguments.clone(),
+                                            env: None,
+                                        });
+                                }
+
+                                language_server_status.configuration = update
+                                    .configuration
+                                    .as_ref()
+                                    .and_then(|config_str| serde_json::from_str(config_str).ok());
+
+                                language_server_status.workspace_folders = update
+                                    .workspace_folders
+                                    .iter()
+                                    .filter_map(|uri_str| lsp::Uri::from_str(uri_str).ok())
+                                    .collect();
+                            }
+                        });
                     }
                     proto::update_language_server::Variant::RegisteredForBuffer(update) => {
                         if let Some(buffer_id) = BufferId::new(update.buffer_id).ok() {

crates/proto/proto/lsp.proto 🔗

@@ -615,8 +615,16 @@ message RegisteredForBuffer {
     uint64 buffer_id = 2;
 }
 
+message LanguageServerBinaryInfo {
+    string path = 1;
+    repeated string arguments = 2;
+}
+
 message ServerMetadataUpdated {
     optional string capabilities = 1;
+    optional LanguageServerBinaryInfo binary = 2;
+    optional string configuration = 3;
+    repeated string workspace_folders = 4;
 }
 
 message LanguageServerLog {