Display language server info in the server logs tab (#22797)

Kirill Bulatov created

Follow-up of https://github.com/zed-industries/zed/pull/19448

When dealing with issues like
https://github.com/zed-industries/zed/issues/22749, it's quite tedious
to ask for logs and check them out.

This PR attempts to establish a single "diagnose my language server"
place in the server logs panel, where server capabilities were already
displayed after https://github.com/zed-industries/zed/pull/19448

The design is pretty brutal, but seems to be on par with the previous
version and it's a technical corner of Zed, so seems to be ok for now:


![image](https://github.com/user-attachments/assets/3471c83a-329e-475a-8cad-af95684da960)

Release Notes:

- Improved lsp logs view to display more language server data

Change summary

crates/language_tools/src/lsp_log.rs | 97 +++++++++++++++--------------
crates/lsp/src/lsp.rs                | 29 ++++++--
crates/project/src/project.rs        |  8 --
3 files changed, 71 insertions(+), 63 deletions(-)

Detailed changes

crates/language_tools/src/lsp_log.rs 🔗

@@ -10,7 +10,7 @@ use gpui::{
 use language::LanguageServerId;
 use lsp::{
     notification::SetTrace, IoKind, LanguageServer, LanguageServerName, MessageType,
-    ServerCapabilities, SetTraceParams, TraceValue,
+    SetTraceParams, TraceValue,
 };
 use project::{search::SearchQuery, Project, WorktreeId};
 use std::{borrow::Cow, sync::Arc};
@@ -108,7 +108,6 @@ struct LanguageServerState {
     rpc_state: Option<LanguageServerRpcState>,
     trace_level: TraceValue,
     log_level: MessageType,
-    capabilities: ServerCapabilities,
     io_logs_subscription: Option<lsp::Subscription>,
 }
 
@@ -178,7 +177,7 @@ pub enum LogKind {
     Trace,
     #[default]
     Logs,
-    Capabilities,
+    ServerInfo,
 }
 
 impl LogKind {
@@ -187,7 +186,7 @@ impl LogKind {
             LogKind::Rpc => RPC_MESSAGES,
             LogKind::Trace => SERVER_TRACE,
             LogKind::Logs => SERVER_LOGS,
-            LogKind::Capabilities => SERVER_CAPABILITIES,
+            LogKind::ServerInfo => SERVER_INFO,
         }
     }
 }
@@ -324,7 +323,11 @@ impl LogStore {
                                     *id,
                                     Some(name.clone()),
                                     *worktree_id,
-                                    project.read(cx).language_server_for_id(*id, cx),
+                                    project
+                                        .read(cx)
+                                        .lsp_store()
+                                        .read(cx)
+                                        .language_server_for_id(*id),
                                     cx,
                                 );
                             }
@@ -378,7 +381,6 @@ impl LogStore {
                 trace_level: TraceValue::Off,
                 log_level: MessageType::LOG,
                 io_logs_subscription: None,
-                capabilities: ServerCapabilities::default(),
             }
         });
 
@@ -402,10 +404,6 @@ impl LogStore {
             }));
         }
 
-        if let Some(server) = server {
-            server_state.capabilities = server.capabilities();
-        }
-
         Some(server_state)
     }
 
@@ -490,10 +488,6 @@ impl LogStore {
         Some(&self.language_servers.get(&server_id)?.trace_messages)
     }
 
-    fn server_capabilities(&self, server_id: LanguageServerId) -> Option<&ServerCapabilities> {
-        Some(&self.language_servers.get(&server_id)?.capabilities)
-    }
-
     fn server_ids_for_project<'a>(
         &'a self,
         lookup_project: &'a WeakModel<Project>,
@@ -619,9 +613,7 @@ impl LspLogView {
                             LogKind::Rpc => this.show_rpc_trace_for_server(server_id, cx),
                             LogKind::Trace => this.show_trace_for_server(server_id, cx),
                             LogKind::Logs => this.show_logs_for_server(server_id, cx),
-                            LogKind::Capabilities => {
-                                this.show_capabilities_for_server(server_id, cx)
-                            }
+                            LogKind::ServerInfo => this.show_server_info(server_id, cx),
                         }
                     } else {
                         this.current_server_id = None;
@@ -638,7 +630,7 @@ impl LspLogView {
                     LogKind::Rpc => this.show_rpc_trace_for_server(server_id, cx),
                     LogKind::Trace => this.show_trace_for_server(server_id, cx),
                     LogKind::Logs => this.show_logs_for_server(server_id, cx),
-                    LogKind::Capabilities => this.show_capabilities_for_server(server_id, cx),
+                    LogKind::ServerInfo => this.show_server_info(server_id, cx),
                 }
             }
 
@@ -712,14 +704,28 @@ impl LspLogView {
         (editor, vec![editor_subscription, search_subscription])
     }
 
-    fn editor_for_capabilities(
-        capabilities: ServerCapabilities,
+    fn editor_for_server_info(
+        server: &LanguageServer,
         cx: &mut ViewContext<Self>,
     ) -> (View<Editor>, Vec<Subscription>) {
         let editor = cx.new_view(|cx| {
             let mut editor = Editor::multi_line(cx);
-            editor.set_text(serde_json::to_string_pretty(&capabilities).unwrap(), cx);
-            editor.move_to_end(&MoveToEnd, cx);
+            let server_info = format!(
+                "* Server: {NAME} (id {ID})
+
+* Binary: {BINARY:#?}
+
+* Running in project: {PATH:?}
+
+* Capabilities: {CAPABILITIES}",
+                NAME = server.name(),
+                ID = server.server_id(),
+                BINARY = server.binary(),
+                PATH = server.root_path(),
+                CAPABILITIES = serde_json::to_string_pretty(&server.capabilities())
+                    .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")),
+            );
+            editor.set_text(server_info, cx);
             editor.set_read_only(true);
             editor.set_show_inline_completions(Some(false), cx);
             editor
@@ -927,7 +933,13 @@ impl LspLogView {
         level: TraceValue,
         cx: &mut ViewContext<Self>,
     ) {
-        if let Some(server) = self.project.read(cx).language_server_for_id(server_id, cx) {
+        if let Some(server) = self
+            .project
+            .read(cx)
+            .lsp_store()
+            .read(cx)
+            .language_server_for_id(server_id)
+        {
             self.log_store.update(cx, |this, _| {
                 if let Some(state) = this.get_language_server_state(server_id) {
                     state.trace_level = level;
@@ -940,22 +952,17 @@ impl LspLogView {
         }
     }
 
-    fn show_capabilities_for_server(
-        &mut self,
-        server_id: LanguageServerId,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let capabilities = self.log_store.read(cx).server_capabilities(server_id);
-
-        if let Some(capabilities) = capabilities {
-            self.current_server_id = Some(server_id);
-            self.active_entry_kind = LogKind::Capabilities;
-            let (editor, editor_subscriptions) =
-                Self::editor_for_capabilities(capabilities.clone(), cx);
-            self.editor = editor;
-            self.editor_subscriptions = editor_subscriptions;
-            cx.notify();
-        }
+    fn show_server_info(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
+        let lsp_store = self.project.read(cx).lsp_store();
+        let Some(server) = lsp_store.read(cx).language_server_for_id(server_id) else {
+            return;
+        };
+        self.current_server_id = Some(server_id);
+        self.active_entry_kind = LogKind::ServerInfo;
+        let (editor, editor_subscriptions) = Self::editor_for_server_info(&server, cx);
+        self.editor = editor;
+        self.editor_subscriptions = editor_subscriptions;
+        cx.notify();
         cx.focus(&self.focus_handle);
     }
 }
@@ -1026,7 +1033,7 @@ impl Item for LspLogView {
                     LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, cx),
                     LogKind::Trace => new_view.show_trace_for_server(server_id, cx),
                     LogKind::Logs => new_view.show_logs_for_server(server_id, cx),
-                    LogKind::Capabilities => new_view.show_capabilities_for_server(server_id, cx),
+                    LogKind::ServerInfo => new_view.show_server_info(server_id, cx),
                 }
             }
             new_view
@@ -1187,9 +1194,7 @@ impl Render for LspLogToolbarItemView {
                                         }
                                         LogKind::Trace => view.show_trace_for_server(server_id, cx),
                                         LogKind::Logs => view.show_logs_for_server(server_id, cx),
-                                        LogKind::Capabilities => {
-                                            view.show_capabilities_for_server(server_id, cx)
-                                        }
+                                        LogKind::ServerInfo => view.show_server_info(server_id, cx),
                                     }
                                     cx.notify();
                                 }),
@@ -1272,10 +1277,10 @@ impl Render for LspLogToolbarItemView {
                             )
                         })
                         .entry(
-                            SERVER_CAPABILITIES,
+                            SERVER_INFO,
                             None,
                             cx.handler_for(&log_view, move |view, cx| {
-                                view.show_capabilities_for_server(server_id, cx);
+                                view.show_server_info(server_id, cx);
                             }),
                         )
                     }))
@@ -1434,7 +1439,7 @@ impl Render for LspLogToolbarItemView {
 const RPC_MESSAGES: &str = "RPC Messages";
 const SERVER_LOGS: &str = "Server Logs";
 const SERVER_TRACE: &str = "Server Trace";
-const SERVER_CAPABILITIES: &str = "Server Capabilities";
+const SERVER_INFO: &str = "Server Info";
 
 impl Default for LspLogToolbarItemView {
     fn default() -> Self {

crates/lsp/src/lsp.rs 🔗

@@ -82,6 +82,7 @@ pub struct LanguageServer {
     outbound_tx: channel::Sender<String>,
     name: LanguageServerName,
     process_name: Arc<str>,
+    binary: LanguageServerBinary,
     capabilities: RwLock<ServerCapabilities>,
     code_action_kinds: Option<Vec<CodeActionKind>>,
     notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
@@ -347,7 +348,7 @@ impl LanguageServer {
         let mut server = util::command::new_smol_command(&binary.path)
             .current_dir(working_dir)
             .args(&binary.arguments)
-            .envs(binary.env.unwrap_or_default())
+            .envs(binary.env.clone().unwrap_or_default())
             .stdin(Stdio::piped())
             .stdout(Stdio::piped())
             .stderr(Stdio::piped())
@@ -363,7 +364,7 @@ impl LanguageServer {
         let stdin = server.stdin.take().unwrap();
         let stdout = server.stdout.take().unwrap();
         let stderr = server.stderr.take().unwrap();
-        let mut server = Self::new_internal(
+        let server = Self::new_internal(
             server_id,
             server_name,
             stdin,
@@ -374,6 +375,7 @@ impl LanguageServer {
             root_path,
             working_dir,
             code_action_kinds,
+            binary,
             cx,
             move |notification| {
                 log::info!(
@@ -385,10 +387,6 @@ impl LanguageServer {
             },
         );
 
-        if let Some(name) = binary.path.file_name() {
-            server.process_name = name.to_string_lossy().into();
-        }
-
         Ok(server)
     }
 
@@ -404,6 +402,7 @@ impl LanguageServer {
         root_path: &Path,
         working_dir: &Path,
         code_action_kinds: Option<Vec<CodeActionKind>>,
+        binary: LanguageServerBinary,
         cx: AsyncAppContext,
         on_unhandled_notification: F,
     ) -> Self
@@ -466,7 +465,12 @@ impl LanguageServer {
             response_handlers,
             io_handlers,
             name: server_name,
-            process_name: Arc::default(),
+            process_name: binary
+                .path
+                .file_name()
+                .map(|name| Arc::from(name.to_string_lossy()))
+                .unwrap_or_default(),
+            binary,
             capabilities: Default::default(),
             code_action_kinds,
             next_id: Default::default(),
@@ -1055,6 +1059,11 @@ impl LanguageServer {
         &self.root_path
     }
 
+    /// Language server's binary information.
+    pub fn binary(&self) -> &LanguageServerBinary {
+        &self.binary
+    }
+
     /// Sends a RPC request to the language server.
     ///
     /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage)
@@ -1278,12 +1287,13 @@ impl FakeLanguageServer {
             root,
             root,
             None,
+            binary.clone(),
             cx.clone(),
             |_| {},
         );
         server.process_name = process_name;
         let fake = FakeLanguageServer {
-            binary,
+            binary: binary.clone(),
             server: Arc::new({
                 let mut server = LanguageServer::new_internal(
                     server_id,
@@ -1296,7 +1306,8 @@ impl FakeLanguageServer {
                     root,
                     root,
                     None,
-                    cx,
+                    binary,
+                    cx.clone(),
                     move |msg| {
                         notifications_tx
                             .try_send((

crates/project/src/project.rs 🔗

@@ -4143,14 +4143,6 @@ impl Project {
         self.lsp_store.read(cx).supplementary_language_servers()
     }
 
-    pub fn language_server_for_id(
-        &self,
-        id: LanguageServerId,
-        cx: &AppContext,
-    ) -> Option<Arc<LanguageServer>> {
-        self.lsp_store.read(cx).language_server_for_id(id)
-    }
-
     pub fn language_servers_for_local_buffer<'a>(
         &'a self,
         buffer: &'a Buffer,