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

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
This commit is contained in:
Kirill Bulatov 2025-01-07 23:57:59 +02:00 committed by GitHub
parent a653e8adda
commit a331497367
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 63 deletions

View file

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

View file

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

View file

@ -4143,14 +4143,6 @@ impl Project {
self.lsp_store.read(cx).supplementary_language_servers() 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>( pub fn language_servers_for_local_buffer<'a>(
&'a self, &'a self,
buffer: &'a Buffer, buffer: &'a Buffer,