Add messages and handlers for RPC log toggling

Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
Kirill Bulatov 2025-08-22 20:37:31 +03:00
parent 848d1101d3
commit ab5da3af83
8 changed files with 94 additions and 51 deletions

View file

@ -476,7 +476,8 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>) .add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>) .add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>) .add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
.add_message_handler(update_context); .add_message_handler(update_context)
.add_request_handler(forward_mutating_project_request::<proto::ToggleLspLogs>);
Arc::new(server) Arc::new(server)
} }

View file

@ -6,6 +6,7 @@ mod syntax_tree_view;
#[cfg(test)] #[cfg(test)]
mod lsp_log_tests; mod lsp_log_tests;
use client::AnyProtoClient;
use gpui::{App, AppContext, Entity}; use gpui::{App, AppContext, Entity};
pub use lsp_log::{LogStore, LspLogToolbarItemView, LspLogView}; pub use lsp_log::{LogStore, LspLogToolbarItemView, LspLogView};
@ -13,8 +14,8 @@ pub use syntax_tree_view::{SyntaxTreeToolbarItemView, SyntaxTreeView};
use ui::{Context, Window}; use ui::{Context, Window};
use workspace::{Item, ItemHandle, SplitDirection, Workspace}; use workspace::{Item, ItemHandle, SplitDirection, Workspace};
pub fn init(cx: &mut App) { pub fn init(client: AnyProtoClient, cx: &mut App) {
lsp_log::init(cx); lsp_log::init(client, cx);
syntax_tree_view::init(cx); syntax_tree_view::init(cx);
key_context_view::init(cx); key_context_view::init(cx);
} }

View file

@ -1,9 +1,11 @@
use anyhow::Result;
use client::AnyProtoClient;
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
use copilot::Copilot; use copilot::Copilot;
use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll}; use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};
use futures::{StreamExt, channel::mpsc}; use futures::{StreamExt, channel::mpsc};
use gpui::{ use gpui::{
AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, Global, AnyView, App, AsyncApp, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, Global,
IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -13,6 +15,7 @@ use lsp::{
SetTraceParams, TraceValue, notification::SetTrace, SetTraceParams, TraceValue, notification::SetTrace,
}; };
use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery}; use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery};
use proto::TypedEnvelope;
use std::{any::TypeId, borrow::Cow, sync::Arc}; use std::{any::TypeId, borrow::Cow, sync::Arc};
use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*}; use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*};
use util::ResultExt as _; use util::ResultExt as _;
@ -103,7 +106,7 @@ impl Message for RpcMessage {
} }
pub(super) struct LanguageServerState { pub(super) struct LanguageServerState {
project: WeakEntity<Project>, project: Option<WeakEntity<Project>>,
name: Option<LanguageServerName>, name: Option<LanguageServerName>,
worktree_id: Option<WorktreeId>, worktree_id: Option<WorktreeId>,
kind: LanguageServerKind, kind: LanguageServerKind,
@ -226,7 +229,9 @@ pub struct GlobalLogStore(pub WeakEntity<LogStore>);
impl Global for GlobalLogStore {} impl Global for GlobalLogStore {}
// todo! do separate headless and local cases here: headless cares only about the downstream_client() part, NO log storage is needed // todo! do separate headless and local cases here: headless cares only about the downstream_client() part, NO log storage is needed
pub fn init(cx: &mut App) { pub fn init(client: AnyProtoClient, cx: &mut App) {
client.add_entity_message_handler(handle_toggle_lsp_logs);
let log_store = cx.new(LogStore::new); let log_store = cx.new(LogStore::new);
cx.set_global(GlobalLogStore(log_store.downgrade())); cx.set_global(GlobalLogStore(log_store.downgrade()));
@ -291,6 +296,7 @@ impl LogStore {
Some(name), Some(name),
None, None,
Some(server.clone()), Some(server.clone()),
None,
cx, cx,
); );
} }
@ -331,7 +337,8 @@ impl LogStore {
this.language_servers this.language_servers
.retain(|_, state| state.kind.project() != Some(&weak_project)); .retain(|_, state| state.kind.project() != Some(&weak_project));
}), }),
cx.subscribe(project, |this, project, event, cx| { cx.subscribe(project, move |log_store, project, event, cx| {
let subscription_weak_project = project.downgrade();
let server_kind = if project.read(cx).is_via_ssh() { let server_kind = if project.read(cx).is_via_ssh() {
LanguageServerKind::Remote { LanguageServerKind::Remote {
project: project.downgrade(), project: project.downgrade(),
@ -344,7 +351,7 @@ impl LogStore {
match event { match event {
project::Event::LanguageServerAdded(id, name, worktree_id) => { project::Event::LanguageServerAdded(id, name, worktree_id) => {
this.add_language_server( log_store.add_language_server(
server_kind, server_kind,
*id, *id,
Some(name.clone()), Some(name.clone()),
@ -354,21 +361,30 @@ impl LogStore {
.lsp_store() .lsp_store()
.read(cx) .read(cx)
.language_server_for_id(*id), .language_server_for_id(*id),
Some(subscription_weak_project),
cx, cx,
); );
} }
project::Event::LanguageServerRemoved(id) => { project::Event::LanguageServerRemoved(id) => {
this.remove_language_server(*id, cx); log_store.remove_language_server(*id, cx);
} }
project::Event::LanguageServerLog(id, typ, message) => { project::Event::LanguageServerLog(id, typ, message) => {
this.add_language_server(server_kind, *id, None, None, None, cx); log_store.add_language_server(
server_kind,
*id,
None,
None,
None,
Some(subscription_weak_project),
cx,
);
match typ { match typ {
project::LanguageServerLogType::Log(typ) => { project::LanguageServerLogType::Log(typ) => {
this.add_language_server_log(*id, *typ, message, cx); log_store.add_language_server_log(*id, *typ, message, cx);
} }
project::LanguageServerLogType::Trace(_) => { project::LanguageServerLogType::Trace(_) => {
// todo! do something with trace level // todo! do something with trace level
this.add_language_server_trace(*id, message, cx); log_store.add_language_server_trace(*id, message, cx);
} }
project::LanguageServerLogType::Rpc { received } => { project::LanguageServerLogType::Rpc { received } => {
let kind = if *received { let kind = if *received {
@ -376,7 +392,7 @@ impl LogStore {
} else { } else {
MessageKind::Send MessageKind::Send
}; };
this.add_language_server_rpc(*id, kind, message, cx); log_store.add_language_server_rpc(*id, kind, message, cx);
} }
} }
} }
@ -422,7 +438,7 @@ impl LogStore {
name: Option<LanguageServerName>, name: Option<LanguageServerName>,
worktree_id: Option<WorktreeId>, worktree_id: Option<WorktreeId>,
server: Option<Arc<LanguageServer>>, server: Option<Arc<LanguageServer>>,
project: WeakEntity<Project>, project: Option<WeakEntity<Project>>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Option<&mut LanguageServerState> { ) -> Option<&mut LanguageServerState> {
let server_state = self.language_servers.entry(server_id).or_insert_with(|| { let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
@ -609,7 +625,7 @@ impl LogStore {
}) })
} }
pub fn enable_rpc_trace_for_language_server( fn enable_rpc_trace_for_language_server(
&mut self, &mut self,
server_id: LanguageServerId, server_id: LanguageServerId,
) -> Option<&mut LanguageServerRpcState> { ) -> Option<&mut LanguageServerRpcState> {
@ -771,6 +787,23 @@ impl LogStore {
} }
} }
async fn handle_toggle_lsp_logs(
lsp_log: Entity<LogStore>,
envelope: TypedEnvelope<proto::ToggleLspLogs>,
mut cx: AsyncApp,
) -> Result<()> {
let server_id = LanguageServerId::from_proto(envelope.payload.server_id);
lsp_log.update(&mut cx, |lsp_log, _| {
// we do not support any other log toggling yet
if envelope.payload.enabled {
lsp_log.enable_rpc_trace_for_language_server(server_id);
} else {
lsp_log.disable_rpc_trace_for_language_server(server_id);
}
})?;
Ok(())
}
impl LspLogView { impl LspLogView {
pub fn new( pub fn new(
project: Entity<Project>, project: Entity<Project>,
@ -1130,26 +1163,32 @@ impl LspLogView {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.log_store.update(cx, |log_store, _| { self.log_store.update(cx, |log_store, cx| {
if enabled { if enabled {
log_store.enable_rpc_trace_for_language_server(server_id); log_store.enable_rpc_trace_for_language_server(server_id);
} else { } else {
log_store.disable_rpc_trace_for_language_server(server_id); log_store.disable_rpc_trace_for_language_server(server_id);
} }
if let Some(server_state) = log_store.language_servers.get(server_id) { if let Some(server_state) = log_store.language_servers.get(&server_id) {
server_state if let Some(project) = &server_state.project {
.project project
.update(cx, |project, cx| { .update(cx, |project, cx| {
if let Some((client, project)) = if let Some((client, project_id)) =
project.lsp_store().read(cx).upstream_client() project.lsp_store().read(cx).upstream_client()
{ {
// todo! client.send a new proto message to propagate the enabled client
// !!!! we have to have a handler on both headless and normal projects .send(proto::ToggleLspLogs {
// that handler has to touch the Global<LspLog> and amend the sending bit project_id,
} log_type: proto::toggle_lsp_logs::LogType::Rpc as i32,
}) server_id: server_id.to_proto(),
.ok(); enabled,
})
.log_err();
}
})
.ok();
}
}; };
}); });
if !enabled && Some(server_id) == self.current_server_id { if !enabled && Some(server_id) == self.current_server_id {

View file

@ -963,3 +963,16 @@ message MultiLspQuery {
message MultiLspQueryResponse { message MultiLspQueryResponse {
repeated LspResponse responses = 1; repeated LspResponse responses = 1;
} }
message ToggleLspLogs {
uint64 project_id = 1;
LogType log_type = 2;
uint64 server_id = 3;
bool enabled = 4;
enum LogType {
LOG = 0;
TRACE = 1;
RPC = 2;
}
}

View file

@ -396,7 +396,8 @@ message Envelope {
GitCloneResponse git_clone_response = 364; GitCloneResponse git_clone_response = 364;
LspQuery lsp_query = 365; LspQuery lsp_query = 365;
LspQueryResponse lsp_query_response = 366; // current max LspQueryResponse lsp_query_response = 366;
ToggleLspLogs toggle_lsp_logs = 367; // current max
} }
reserved 87 to 88; reserved 87 to 88;

View file

@ -312,7 +312,8 @@ messages!(
(GetDefaultBranch, Background), (GetDefaultBranch, Background),
(GetDefaultBranchResponse, Background), (GetDefaultBranchResponse, Background),
(GitClone, Background), (GitClone, Background),
(GitCloneResponse, Background) (GitCloneResponse, Background),
(ToggleLspLogs, Background),
); );
request_messages!( request_messages!(
@ -481,7 +482,8 @@ request_messages!(
(GetDocumentDiagnostics, GetDocumentDiagnosticsResponse), (GetDocumentDiagnostics, GetDocumentDiagnosticsResponse),
(PullWorkspaceDiagnostics, Ack), (PullWorkspaceDiagnostics, Ack),
(GetDefaultBranch, GetDefaultBranchResponse), (GetDefaultBranch, GetDefaultBranchResponse),
(GitClone, GitCloneResponse) (GitClone, GitCloneResponse),
(ToggleLspLogs, Ack),
); );
lsp_messages!( lsp_messages!(
@ -612,6 +614,7 @@ entity_messages!(
GitReset, GitReset,
GitCheckoutFiles, GitCheckoutFiles,
SetIndexText, SetIndexText,
ToggleLspLogs,
Push, Push,
Fetch, Fetch,

View file

@ -65,13 +65,6 @@ impl HeadlessProject {
settings::init(cx); settings::init(cx);
language::init(cx); language::init(cx);
project::Project::init_settings(cx); project::Project::init_settings(cx);
// todo! what to do with the RPC log spam?
// if we have not enabled RPC logging on the remote client, we do not need these
//
// Maybe, add another RPC message, proto::ToggleRpcLogging(bool)
// and send it into the upstream client from the remotes, so that the local/headless counterpart
// can access this Global<LspLog> and toggle the spam send
language_tools::lsp_log::init(cx);
} }
pub fn new( pub fn new(
@ -87,6 +80,8 @@ impl HeadlessProject {
) -> Self { ) -> Self {
debug_adapter_extension::init(proxy.clone(), cx); debug_adapter_extension::init(proxy.clone(), cx);
languages::init(languages.clone(), node_runtime.clone(), cx); languages::init(languages.clone(), node_runtime.clone(), cx);
// todo! avoid "memory leaks" here as we do not need to gather any logs locally, just proxy the to the client
language_tools::lsp_log::init(session.clone(), cx);
let worktree_store = cx.new(|cx| { let worktree_store = cx.new(|cx| {
let mut store = WorktreeStore::local(true, fs.clone()); let mut store = WorktreeStore::local(true, fs.clone());
@ -333,16 +328,6 @@ impl HeadlessProject {
}) })
.log_err(); .log_err();
} }
LspStoreEvent::LanguageServerLog(language_server_id, log_type, message) => {
self.session
.send(proto::LanguageServerLog {
project_id: SSH_PROJECT_ID,
language_server_id: language_server_id.to_proto(),
message: message.clone(),
log_type: Some(log_type.to_proto()),
})
.log_err();
}
LspStoreEvent::LanguageServerPrompt(prompt) => { LspStoreEvent::LanguageServerPrompt(prompt) => {
let request = self.session.request(proto::LanguageServerPromptRequest { let request = self.session.request(proto::LanguageServerPromptRequest {
project_id: SSH_PROJECT_ID, project_id: SSH_PROJECT_ID,

View file

@ -5,7 +5,7 @@ use agent_ui::AgentPanel;
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use clap::{Parser, command}; use clap::{Parser, command};
use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::{Client, ProxySettings, UserStore, parse_zed_link}; use client::{AnyProtoClient, Client, ProxySettings, UserStore, parse_zed_link};
use collab_ui::channel_view::ChannelView; use collab_ui::channel_view::ChannelView;
use collections::HashMap; use collections::HashMap;
use crashes::InitCrashHandler; use crashes::InitCrashHandler;
@ -621,7 +621,7 @@ pub fn main() {
toolchain_selector::init(cx); toolchain_selector::init(cx);
theme_selector::init(cx); theme_selector::init(cx);
settings_profile_selector::init(cx); settings_profile_selector::init(cx);
language_tools::init(cx); language_tools::init(AnyProtoClient::new(app_state.client.clone()), cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx); call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx); notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
collab_ui::init(&app_state, cx); collab_ui::init(&app_state, cx);