lsp_log: Add server capabilities view (#19448)

Hello, this PR adds a new view to the LSP servers menu for
displaying an LSP server capabilities.

When I work on LSP stuff, quite often I need to check what capabilities
an LSP server has. Currently there is no built-in way for checking that
in Zed, and I have to use [`LSP
DevTools`](https://lsp-devtools.readthedocs.io) project. LSP DevTools
works OK but it works as a proxy between the client and the server, so
setting it up is not that easy in Zed. Zed already has many goodies for
LSP like tracing and RPC messages, so I thought that a simple view with
server capabilities could be useful too. Thanks!

## Some screenshots:

### Ruby LSP

![CleanShot 2024-10-19 at 07 44
38@2x](https://github.com/user-attachments/assets/22c97b49-c539-4e39-a5f1-1c926347abca)


### New menu entry:

![CleanShot 2024-10-19 at 07 45
08@2x](https://github.com/user-attachments/assets/d3903d6e-c09a-40e2-b042-1abde490987d)


Release Notes:

- N/A
This commit is contained in:
Vitaly Slobodin 2024-10-23 12:53:49 +02:00 committed by GitHub
parent d53a86b01d
commit 375bc88f95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -9,7 +9,8 @@ use gpui::{
}; };
use language::{LanguageServerId, LanguageServerName}; use language::{LanguageServerId, LanguageServerName};
use lsp::{ use lsp::{
notification::SetTrace, IoKind, LanguageServer, MessageType, SetTraceParams, TraceValue, notification::SetTrace, IoKind, LanguageServer, MessageType, ServerCapabilities,
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};
@ -107,6 +108,7 @@ 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>,
} }
@ -176,6 +178,7 @@ pub enum LogKind {
Trace, Trace,
#[default] #[default]
Logs, Logs,
Capabilities,
} }
impl LogKind { impl LogKind {
@ -184,6 +187,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,
} }
} }
} }
@ -374,6 +378,7 @@ 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(),
} }
}); });
@ -384,7 +389,10 @@ impl LogStore {
server_state.worktree_id = Some(worktree_id); server_state.worktree_id = Some(worktree_id);
} }
if let Some(server) = server.filter(|_| server_state.io_logs_subscription.is_none()) { if let Some(server) = server
.clone()
.filter(|_| server_state.io_logs_subscription.is_none())
{
let io_tx = self.io_tx.clone(); let io_tx = self.io_tx.clone();
let server_id = server.server_id(); let server_id = server.server_id();
server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| { server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
@ -393,6 +401,11 @@ impl LogStore {
.ok(); .ok();
})); }));
} }
if let Some(server) = server {
server_state.capabilities = server.capabilities();
}
Some(server_state) Some(server_state)
} }
@ -477,6 +490,10 @@ 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>,
@ -602,6 +619,9 @@ 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)
}
} }
} else { } else {
this.current_server_id = None; this.current_server_id = None;
@ -618,6 +638,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),
} }
} }
@ -695,6 +716,33 @@ impl LspLogView {
(editor, vec![editor_subscription, search_subscription]) (editor, vec![editor_subscription, search_subscription])
} }
fn editor_for_capabilities(
capabilities: ServerCapabilities,
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);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor
});
let editor_subscription = cx.subscribe(
&editor,
|_, _, event: &EditorEvent, cx: &mut ViewContext<'_, LspLogView>| {
cx.emit(event.clone())
},
);
let search_subscription = cx.subscribe(
&editor,
|_, _, event: &SearchEvent, cx: &mut ViewContext<'_, LspLogView>| {
cx.emit(event.clone())
},
);
(editor, vec![editor_subscription, search_subscription])
}
pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> { pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
let log_store = self.log_store.read(cx); let log_store = self.log_store.read(cx);
@ -881,6 +929,7 @@ impl LspLogView {
cx.notify(); cx.notify();
} }
} }
fn update_trace_level( fn update_trace_level(
&self, &self,
server_id: LanguageServerId, server_id: LanguageServerId,
@ -899,6 +948,25 @@ impl LspLogView {
.ok(); .ok();
} }
} }
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();
}
cx.focus(&self.focus_handle);
}
} }
fn log_filter<T: Message>(line: &T, cmp: <T as Message>::Level) -> Option<&str> { fn log_filter<T: Message>(line: &T, cmp: <T as Message>::Level) -> Option<&str> {
@ -967,6 +1035,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),
} }
} }
new_view new_view
@ -1168,6 +1237,13 @@ impl Render for LspLogToolbarItemView {
view.show_rpc_trace_for_server(row.server_id, cx); view.show_rpc_trace_for_server(row.server_id, cx);
}), }),
); );
menu = menu.entry(
SERVER_CAPABILITIES,
None,
cx.handler_for(&log_view, move |view, cx| {
view.show_capabilities_for_server(row.server_id, cx);
}),
);
if server_selected && row.selected_entry == LogKind::Rpc { if server_selected && row.selected_entry == LogKind::Rpc {
let selected_ix = menu.select_last(); let selected_ix = menu.select_last();
debug_assert_eq!( debug_assert_eq!(
@ -1317,6 +1393,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";
impl Default for LspLogToolbarItemView { impl Default for LspLogToolbarItemView {
fn default() -> Self { fn default() -> Self {