Remote LSP logs (#36709)

Enables LSP log tracing in both remote collab and remote ssh
environments.
Server logs and server RPC traces can now be viewed remotely, and the
LSP button is now shown in such projects too.

Closes https://github.com/zed-industries/zed/issues/28557

Co-Authored-By: Kirill <kirill@zed.dev>
Co-Authored-By: Lukas <lukas@zed.dev>

Release Notes:

- Enabled LSP log tracing in both remote collab and remote ssh
environments

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
Ben Kunkle
2025-08-27 21:55:34 +03:00
committed by GitHub
co-authored by Kirill Bulatov Lukas Wirth
parent c158eb2442
commit e2bf8e5d9c
29 changed files with 956 additions and 349 deletions
+65 -27
View File
@@ -977,7 +977,9 @@ impl LocalLspStore {
this.update(&mut cx, |_, cx| {
cx.emit(LspStoreEvent::LanguageServerLog(
server_id,
LanguageServerLogType::Trace(params.verbose),
LanguageServerLogType::Trace {
verbose_info: params.verbose,
},
params.message,
));
})
@@ -3482,13 +3484,13 @@ pub struct LspStore {
buffer_store: Entity<BufferStore>,
worktree_store: Entity<WorktreeStore>,
pub languages: Arc<LanguageRegistry>,
language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
pub language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
active_entry: Option<ProjectEntryId>,
_maintain_workspace_config: (Task<Result<()>>, watch::Sender<()>),
_maintain_buffer_languages: Task<()>,
diagnostic_summaries:
HashMap<WorktreeId, HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>>,
pub(super) lsp_server_capabilities: HashMap<LanguageServerId, lsp::ServerCapabilities>,
pub lsp_server_capabilities: HashMap<LanguageServerId, lsp::ServerCapabilities>,
lsp_document_colors: HashMap<BufferId, DocumentColorData>,
lsp_code_lens: HashMap<BufferId, CodeLensData>,
running_lsp_requests: HashMap<TypeId, (Global, HashMap<LspRequestId, Task<()>>)>,
@@ -3565,6 +3567,7 @@ pub struct LanguageServerStatus {
pub pending_work: BTreeMap<String, LanguageServerProgress>,
pub has_pending_diagnostic_updates: bool,
progress_tokens: HashSet<String>,
pub worktree: Option<WorktreeId>,
}
#[derive(Clone, Debug)]
@@ -7483,7 +7486,7 @@ impl LspStore {
server: Some(proto::LanguageServer {
id: server_id.to_proto(),
name: status.name.to_string(),
worktree_id: None,
worktree_id: status.worktree.map(|id| id.to_proto()),
}),
capabilities: serde_json::to_string(&server.capabilities())
.expect("serializing server LSP capabilities"),
@@ -7527,6 +7530,7 @@ impl LspStore {
pending_work: Default::default(),
has_pending_diagnostic_updates: false,
progress_tokens: Default::default(),
worktree: server.worktree_id.map(WorktreeId::from_proto),
},
)
})
@@ -8892,6 +8896,7 @@ impl LspStore {
pending_work: Default::default(),
has_pending_diagnostic_updates: false,
progress_tokens: Default::default(),
worktree: server.worktree_id.map(WorktreeId::from_proto),
},
);
cx.emit(LspStoreEvent::LanguageServerAdded(
@@ -10905,6 +10910,7 @@ impl LspStore {
pending_work: Default::default(),
has_pending_diagnostic_updates: false,
progress_tokens: Default::default(),
worktree: Some(key.worktree_id),
},
);
@@ -12190,6 +12196,14 @@ impl LspStore {
let data = self.lsp_code_lens.get_mut(&buffer_id)?;
Some(data.update.take()?.1)
}
pub fn downstream_client(&self) -> Option<(AnyProtoClient, u64)> {
self.downstream_client.clone()
}
pub fn worktree_store(&self) -> Entity<WorktreeStore> {
self.worktree_store.clone()
}
}
// Registration with registerOptions as null, should fallback to true.
@@ -12699,45 +12713,69 @@ impl PartialEq for LanguageServerPromptRequest {
#[derive(Clone, Debug, PartialEq)]
pub enum LanguageServerLogType {
Log(MessageType),
Trace(Option<String>),
Trace { verbose_info: Option<String> },
Rpc { received: bool },
}
impl LanguageServerLogType {
pub fn to_proto(&self) -> proto::language_server_log::LogType {
match self {
Self::Log(log_type) => {
let message_type = match *log_type {
MessageType::ERROR => 1,
MessageType::WARNING => 2,
MessageType::INFO => 3,
MessageType::LOG => 4,
use proto::log_message::LogLevel;
let level = match *log_type {
MessageType::ERROR => LogLevel::Error,
MessageType::WARNING => LogLevel::Warning,
MessageType::INFO => LogLevel::Info,
MessageType::LOG => LogLevel::Log,
other => {
log::warn!("Unknown lsp log message type: {:?}", other);
4
log::warn!("Unknown lsp log message type: {other:?}");
LogLevel::Log
}
};
proto::language_server_log::LogType::LogMessageType(message_type)
}
Self::Trace(message) => {
proto::language_server_log::LogType::LogTrace(proto::LspLogTrace {
message: message.clone(),
proto::language_server_log::LogType::Log(proto::LogMessage {
level: level as i32,
})
}
Self::Trace { verbose_info } => {
proto::language_server_log::LogType::Trace(proto::TraceMessage {
verbose_info: verbose_info.to_owned(),
})
}
Self::Rpc { received } => {
let kind = if *received {
proto::rpc_message::Kind::Received
} else {
proto::rpc_message::Kind::Sent
};
let kind = kind as i32;
proto::language_server_log::LogType::Rpc(proto::RpcMessage { kind })
}
}
}
pub fn from_proto(log_type: proto::language_server_log::LogType) -> Self {
use proto::log_message::LogLevel;
use proto::rpc_message;
match log_type {
proto::language_server_log::LogType::LogMessageType(message_type) => {
Self::Log(match message_type {
1 => MessageType::ERROR,
2 => MessageType::WARNING,
3 => MessageType::INFO,
4 => MessageType::LOG,
_ => MessageType::LOG,
})
}
proto::language_server_log::LogType::LogTrace(trace) => Self::Trace(trace.message),
proto::language_server_log::LogType::Log(message_type) => Self::Log(
match LogLevel::from_i32(message_type.level).unwrap_or(LogLevel::Log) {
LogLevel::Error => MessageType::ERROR,
LogLevel::Warning => MessageType::WARNING,
LogLevel::Info => MessageType::INFO,
LogLevel::Log => MessageType::LOG,
},
),
proto::language_server_log::LogType::Trace(trace_message) => Self::Trace {
verbose_info: trace_message.verbose_info,
},
proto::language_server_log::LogType::Rpc(message) => Self::Rpc {
received: match rpc_message::Kind::from_i32(message.kind)
.unwrap_or(rpc_message::Kind::Received)
{
rpc_message::Kind::Received => true,
rpc_message::Kind::Sent => false,
},
},
}
}
}
+21
View File
@@ -280,6 +280,11 @@ pub enum Event {
server_id: LanguageServerId,
buffer_id: BufferId,
buffer_abs_path: PathBuf,
name: Option<LanguageServerName>,
},
ToggleLspLogs {
server_id: LanguageServerId,
enabled: bool,
},
Toast {
notification_id: SharedString,
@@ -1001,6 +1006,7 @@ impl Project {
client.add_entity_request_handler(Self::handle_open_buffer_by_path);
client.add_entity_request_handler(Self::handle_open_new_buffer);
client.add_entity_message_handler(Self::handle_create_buffer_for_peer);
client.add_entity_message_handler(Self::handle_toggle_lsp_logs);
WorktreeStore::init(&client);
BufferStore::init(&client);
@@ -2971,6 +2977,7 @@ impl Project {
buffer_id,
server_id: *language_server_id,
buffer_abs_path: PathBuf::from(&update.buffer_abs_path),
name: name.clone(),
});
}
}
@@ -4697,6 +4704,20 @@ impl Project {
})?
}
async fn handle_toggle_lsp_logs(
project: Entity<Self>,
envelope: TypedEnvelope<proto::ToggleLspLogs>,
mut cx: AsyncApp,
) -> Result<()> {
project.update(&mut cx, |_, cx| {
cx.emit(Event::ToggleLspLogs {
server_id: LanguageServerId::from_proto(envelope.payload.server_id),
enabled: envelope.payload.enabled,
})
})?;
Ok(())
}
async fn handle_synchronize_buffers(
this: Entity<Self>,
envelope: TypedEnvelope<proto::SynchronizeBuffers>,
+1
View File
@@ -1951,6 +1951,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
server_id: LanguageServerId(1),
buffer_id,
buffer_abs_path: PathBuf::from(path!("/dir/a.rs")),
name: Some(fake_server.server.name())
}
);
assert_eq!(