From 848d1101d3173290218860edcfa2c63a147627bf Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Thu, 21 Aug 2025 10:56:51 -0500 Subject: [PATCH 01/13] wip --- Cargo.lock | 2 + crates/language_tools/Cargo.toml | 1 + crates/language_tools/src/language_tools.rs | 2 +- crates/language_tools/src/lsp_log.rs | 196 +++++++++++++------ crates/language_tools/src/lsp_tool.rs | 8 +- crates/project/src/lsp_store.rs | 102 +++++++--- crates/proto/proto/lsp.proto | 37 +++- crates/remote_server/Cargo.toml | 1 + crates/remote_server/src/headless_project.rs | 7 + 9 files changed, 263 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42649b137f..c68c2eae3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9213,6 +9213,7 @@ dependencies = [ "language", "lsp", "project", + "proto", "release_channel", "serde_json", "settings", @@ -13500,6 +13501,7 @@ dependencies = [ "language", "language_extension", "language_model", + "language_tools", "languages", "libc", "log", diff --git a/crates/language_tools/Cargo.toml b/crates/language_tools/Cargo.toml index 5aa914311a..b8f85d8d90 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -24,6 +24,7 @@ itertools.workspace = true language.workspace = true lsp.workspace = true project.workspace = true +proto.workspace = true serde_json.workspace = true settings.workspace = true theme.workspace = true diff --git a/crates/language_tools/src/language_tools.rs b/crates/language_tools/src/language_tools.rs index cbf5756875..34c3d4b9eb 100644 --- a/crates/language_tools/src/language_tools.rs +++ b/crates/language_tools/src/language_tools.rs @@ -1,5 +1,5 @@ mod key_context_view; -mod lsp_log; +pub mod lsp_log; pub mod lsp_tool; mod syntax_tree_view; diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index d5206c1f26..2b1c82cd1f 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -12,9 +12,10 @@ use lsp::{ IoKind, LanguageServer, LanguageServerName, LanguageServerSelector, MessageType, SetTraceParams, TraceValue, notification::SetTrace, }; -use project::{Project, WorktreeId, search::SearchQuery}; +use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery}; use std::{any::TypeId, borrow::Cow, sync::Arc}; use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*}; +use util::ResultExt as _; use workspace::{ SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId, item::{Item, ItemHandle}, @@ -36,7 +37,7 @@ pub struct LogStore { } struct ProjectState { - _subscriptions: [gpui::Subscription; 2], + _subscriptions: [gpui::Subscription; 3], } trait Message: AsRef { @@ -102,6 +103,7 @@ impl Message for RpcMessage { } pub(super) struct LanguageServerState { + project: WeakEntity, name: Option, worktree_id: Option, kind: LanguageServerKind, @@ -183,6 +185,13 @@ pub enum LogKind { } impl LogKind { + fn from_server_log_type(log_type: &LanguageServerLogType) -> Self { + match log_type { + LanguageServerLogType::Log(_) => Self::Logs, + LanguageServerLogType::Trace(_) => Self::Trace, + LanguageServerLogType::Rpc { .. } => Self::Rpc, + } + } fn label(&self) -> &'static str { match self { LogKind::Rpc => RPC_MESSAGES, @@ -212,10 +221,11 @@ actions!( ] ); -pub(super) struct GlobalLogStore(pub WeakEntity); +pub struct GlobalLogStore(pub WeakEntity); 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 pub fn init(cx: &mut App) { let log_store = cx.new(LogStore::new); cx.set_global(GlobalLogStore(log_store.downgrade())); @@ -311,6 +321,7 @@ impl LogStore { pub fn add_project(&mut self, project: &Entity, cx: &mut Context) { let weak_project = project.downgrade(); + let subscription_weak_project = weak_project.clone(); self.projects.insert( project.downgrade(), ProjectState { @@ -356,13 +367,42 @@ impl LogStore { this.add_language_server_log(*id, *typ, message, cx); } project::LanguageServerLogType::Trace(_) => { + // todo! do something with trace level this.add_language_server_trace(*id, message, cx); } + project::LanguageServerLogType::Rpc { received } => { + let kind = if *received { + MessageKind::Receive + } else { + MessageKind::Send + }; + this.add_language_server_rpc(*id, kind, message, cx); + } } } _ => {} } }), + cx.subscribe_self(move |_, e, cx| match e { + Event::NewServerLogEntry { id, kind, text } => { + subscription_weak_project + .update(cx, |project, cx| { + if let Some((client, project_id)) = + project.lsp_store().read(cx).downstream_client() + { + client + .send(proto::LanguageServerLog { + project_id, + language_server_id: id.to_proto(), + message: text.clone(), + log_type: Some(kind.to_proto()), + }) + .log_err(); + }; + }) + .ok(); + } + }), ], }, ); @@ -382,6 +422,7 @@ impl LogStore { name: Option, worktree_id: Option, server: Option>, + project: WeakEntity, cx: &mut Context, ) -> Option<&mut LanguageServerState> { let server_state = self.language_servers.entry(server_id).or_insert_with(|| { @@ -390,6 +431,7 @@ impl LogStore { name: None, worktree_id: None, kind, + project, rpc_state: None, log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), @@ -429,17 +471,21 @@ impl LogStore { let language_server_state = self.get_language_server_state(id)?; let log_lines = &mut language_server_state.log_messages; - Self::add_language_server_message( + if let Some(new_message) = Self::push_new_message( log_lines, - id, LogMessage { message: message.trim_end().to_string(), typ, }, language_server_state.log_level, - LogKind::Logs, - cx, - ); + ) { + cx.emit(Event::NewServerLogEntry { + id, + kind: LanguageServerLogType::Log(typ), + text: new_message, + }); + } + Some(()) } @@ -452,38 +498,81 @@ impl LogStore { let language_server_state = self.get_language_server_state(id)?; let log_lines = &mut language_server_state.trace_messages; - Self::add_language_server_message( + if let Some(new_message) = Self::push_new_message( log_lines, - id, TraceMessage { message: message.trim().to_string(), }, (), - LogKind::Trace, - cx, - ); + ) { + cx.emit(Event::NewServerLogEntry { + id, + // todo! Ben, fix this here too! + kind: LanguageServerLogType::Trace(project::lsp_store::TraceLevel::Verbose), + text: new_message, + }); + } + Some(()) } - fn add_language_server_message( + fn push_new_message( log_lines: &mut VecDeque, - id: LanguageServerId, message: T, current_severity: ::Level, - kind: LogKind, - cx: &mut Context, - ) { + ) -> Option { while log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { log_lines.pop_front(); } - let text = message.as_ref().to_string(); let visible = message.should_include(current_severity); - log_lines.push_back(message); - if visible { - cx.emit(Event::NewServerLogEntry { id, kind, text }); - cx.notify(); + let re = visible.then(|| message.as_ref().to_string()); + log_lines.push_back(message); + re + } + + fn add_language_server_rpc( + &mut self, + language_server_id: LanguageServerId, + kind: MessageKind, + message: &str, + cx: &mut Context<'_, LogStore>, + ) { + let Some(state) = self + .get_language_server_state(language_server_id) + .and_then(|state| state.rpc_state.as_mut()) + else { + return; + }; + + let rpc_log_lines = &mut state.rpc_messages; + if state.last_message_kind != Some(kind) { + while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { + rpc_log_lines.pop_front(); + } + let line_before_message = match kind { + MessageKind::Send => SEND_LINE, + MessageKind::Receive => RECEIVE_LINE, + }; + rpc_log_lines.push_back(RpcMessage { + message: line_before_message.to_string(), + }); + cx.emit(Event::NewServerLogEntry { + id: language_server_id, + kind: LanguageServerLogType::Rpc { + received: kind == MessageKind::Receive, + }, + text: line_before_message.to_string(), + }); } + + while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { + rpc_log_lines.pop_front(); + } + + rpc_log_lines.push_back(RpcMessage { + message: message.trim().to_owned(), + }); } fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context) { @@ -520,7 +609,7 @@ impl LogStore { }) } - fn enable_rpc_trace_for_language_server( + pub fn enable_rpc_trace_for_language_server( &mut self, server_id: LanguageServerId, ) -> Option<&mut LanguageServerRpcState> { @@ -663,47 +752,19 @@ impl LogStore { } }; - let state = self - .get_language_server_state(language_server_id)? - .rpc_state - .as_mut()?; let kind = if is_received { MessageKind::Receive } else { MessageKind::Send }; - let rpc_log_lines = &mut state.rpc_messages; - if state.last_message_kind != Some(kind) { - while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { - rpc_log_lines.pop_front(); - } - let line_before_message = match kind { - MessageKind::Send => SEND_LINE, - MessageKind::Receive => RECEIVE_LINE, - }; - rpc_log_lines.push_back(RpcMessage { - message: line_before_message.to_string(), - }); - cx.emit(Event::NewServerLogEntry { - id: language_server_id, - kind: LogKind::Rpc, - text: line_before_message.to_string(), - }); - } - - while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { - rpc_log_lines.pop_front(); - } - - let message = message.trim(); - rpc_log_lines.push_back(RpcMessage { - message: message.to_string(), - }); + self.add_language_server_rpc(language_server_id, kind, message, cx); cx.emit(Event::NewServerLogEntry { id: language_server_id, - kind: LogKind::Rpc, - text: message.to_string(), + kind: LanguageServerLogType::Rpc { + received: is_received, + }, + text: message.to_owned(), }); cx.notify(); Some(()) @@ -757,7 +818,7 @@ impl LspLogView { move |log_view, _, e, window, cx| match e { Event::NewServerLogEntry { id, kind, text } => { if log_view.current_server_id == Some(*id) - && *kind == log_view.active_entry_kind + && LogKind::from_server_log_type(kind) == log_view.active_entry_kind { log_view.editor.update(cx, |editor, cx| { editor.set_read_only(false); @@ -1075,6 +1136,21 @@ impl LspLogView { } else { log_store.disable_rpc_trace_for_language_server(server_id); } + + if let Some(server_state) = log_store.language_servers.get(server_id) { + server_state + .project + .update(cx, |project, cx| { + if let Some((client, project)) = + project.lsp_store().read(cx).upstream_client() + { + // todo! client.send a new proto message to propagate the enabled + // !!!! we have to have a handler on both headless and normal projects + // that handler has to touch the Global and amend the sending bit + } + }) + .ok(); + }; }); if !enabled && Some(server_id) == self.current_server_id { self.show_logs_for_server(server_id, window, cx); @@ -1113,6 +1189,8 @@ impl LspLogView { window: &mut Window, cx: &mut Context, ) { + // todo! there's no language server for the remote case, hence no server info! + // BUT we do have the capabilities info within the LspStore.lsp_server_capabilities let lsp_store = self.project.read(cx).lsp_store(); let Some(server) = lsp_store.read(cx).language_server_for_id(server_id) else { return; @@ -1737,7 +1815,7 @@ impl LspLogToolbarItemView { pub enum Event { NewServerLogEntry { id: LanguageServerId, - kind: LogKind, + kind: LanguageServerLogType, text: String, }, } diff --git a/crates/language_tools/src/lsp_tool.rs b/crates/language_tools/src/lsp_tool.rs index dd3e80212f..99128e72d9 100644 --- a/crates/language_tools/src/lsp_tool.rs +++ b/crates/language_tools/src/lsp_tool.rs @@ -122,8 +122,7 @@ impl LanguageServerState { let lsp_logs = cx .try_global::() .and_then(|lsp_logs| lsp_logs.0.upgrade()); - let lsp_store = self.lsp_store.upgrade(); - let Some((lsp_logs, lsp_store)) = lsp_logs.zip(lsp_store) else { + let Some(lsp_logs) = lsp_logs else { return menu; }; @@ -210,10 +209,7 @@ impl LanguageServerState { }; let server_selector = server_info.server_selector(); - // TODO currently, Zed remote does not work well with the LSP logs - // https://github.com/zed-industries/zed/issues/28557 - let has_logs = lsp_store.read(cx).as_local().is_some() - && lsp_logs.read(cx).has_server_logs(&server_selector); + let has_logs = lsp_logs.read(cx).has_server_logs(&server_selector); let status_color = server_info .binary_status diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index deebaedd74..5c2782b3b9 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -977,7 +977,13 @@ impl LocalLspStore { this.update(&mut cx, |_, cx| { cx.emit(LspStoreEvent::LanguageServerLog( server_id, - LanguageServerLogType::Trace(params.verbose), + // todo! store verbose info on Verbose + LanguageServerLogType::Trace( + params + .verbose + .map(|_verbose_info| TraceLevel::Verbose) + .unwrap_or(TraceLevel::Messages), + ), params.message, )); }) @@ -12168,6 +12174,10 @@ 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() + } } // Registration with registerOptions as null, should fallback to true. @@ -12674,48 +12684,92 @@ impl PartialEq for LanguageServerPromptRequest { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TraceLevel { + Off, + Messages, + Verbose, +} + #[derive(Clone, Debug, PartialEq)] pub enum LanguageServerLogType { Log(MessageType), - Trace(Option), + Trace(TraceLevel), + 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(trace_level) => { + use proto::trace_message; + let level = match trace_level { + TraceLevel::Off => trace_message::TraceLevel::Off, + TraceLevel::Messages => trace_message::TraceLevel::Messages, + TraceLevel::Verbose => trace_message::TraceLevel::Verbose, + }; + proto::language_server_log::LogType::Trace(proto::TraceMessage { + level: level as i32, + }) + } + 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; + use proto::trace_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) => Self::Trace( + match trace_message::TraceLevel::from_i32(trace.level) + .unwrap_or(trace_message::TraceLevel::Messages) + { + trace_message::TraceLevel::Off => TraceLevel::Off, + trace_message::TraceLevel::Messages => TraceLevel::Messages, + trace_message::TraceLevel::Verbose => TraceLevel::Verbose, + }, + ), + 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, + }, + }, } } } diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index 473ef5c38c..f935f2263b 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -610,11 +610,42 @@ message ServerMetadataUpdated { message LanguageServerLog { uint64 project_id = 1; uint64 language_server_id = 2; + string message = 3; oneof log_type { - uint32 log_message_type = 3; - LspLogTrace log_trace = 4; + LogMessage log = 4; + TraceMessage trace = 5; + RpcMessage rpc = 6; + } +} + +message LogMessage { + LogLevel level = 1; + + enum LogLevel { + LOG = 0; + INFO = 1; + WARNING = 2; + ERROR = 3; + } +} + +message TraceMessage { + TraceLevel level = 1; + + enum TraceLevel { + OFF = 0; + MESSAGES = 1; + VERBOSE = 2; + } +} + +message RpcMessage { + Kind kind = 1; + + enum Kind { + RECEIVED = 0; + SENT = 1; } - string message = 5; } message LspLogTrace { diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index 5dbb9a2771..249968b246 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -43,6 +43,7 @@ gpui_tokio.workspace = true http_client.workspace = true language.workspace = true language_extension.workspace = true +language_tools.workspace = true languages.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 6216ff7728..14e2fbce21 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -65,6 +65,13 @@ impl HeadlessProject { settings::init(cx); language::init(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 and toggle the spam send + language_tools::lsp_log::init(cx); } pub fn new( From ab5da3af83b43b98b7fd423e522fdc9ea7e6c761 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Aug 2025 20:37:31 +0300 Subject: [PATCH 02/13] Add messages and handlers for RPC log toggling Co-authored-by: Ben Kunkle Co-authored-by: Lukas Wirth --- crates/collab/src/rpc.rs | 3 +- crates/language_tools/src/language_tools.rs | 5 +- crates/language_tools/src/lsp_log.rs | 91 ++++++++++++++------ crates/proto/proto/lsp.proto | 13 +++ crates/proto/proto/zed.proto | 3 +- crates/proto/src/proto.rs | 7 +- crates/remote_server/src/headless_project.rs | 19 +--- crates/zed/src/main.rs | 4 +- 8 files changed, 94 insertions(+), 51 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 73f327166a..7b5a8106d7 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -476,7 +476,8 @@ impl Server { .add_request_handler(forward_mutating_project_request::) .add_request_handler(forward_mutating_project_request::) .add_message_handler(broadcast_project_message_from_host::) - .add_message_handler(update_context); + .add_message_handler(update_context) + .add_request_handler(forward_mutating_project_request::); Arc::new(server) } diff --git a/crates/language_tools/src/language_tools.rs b/crates/language_tools/src/language_tools.rs index 34c3d4b9eb..f5af3d01f6 100644 --- a/crates/language_tools/src/language_tools.rs +++ b/crates/language_tools/src/language_tools.rs @@ -6,6 +6,7 @@ mod syntax_tree_view; #[cfg(test)] mod lsp_log_tests; +use client::AnyProtoClient; use gpui::{App, AppContext, Entity}; pub use lsp_log::{LogStore, LspLogToolbarItemView, LspLogView}; @@ -13,8 +14,8 @@ pub use syntax_tree_view::{SyntaxTreeToolbarItemView, SyntaxTreeView}; use ui::{Context, Window}; use workspace::{Item, ItemHandle, SplitDirection, Workspace}; -pub fn init(cx: &mut App) { - lsp_log::init(cx); +pub fn init(client: AnyProtoClient, cx: &mut App) { + lsp_log::init(client, cx); syntax_tree_view::init(cx); key_context_view::init(cx); } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 2b1c82cd1f..7b4d21af6c 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1,9 +1,11 @@ +use anyhow::Result; +use client::AnyProtoClient; use collections::{HashMap, VecDeque}; use copilot::Copilot; use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll}; use futures::{StreamExt, channel::mpsc}; 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, }; use itertools::Itertools; @@ -13,6 +15,7 @@ use lsp::{ SetTraceParams, TraceValue, notification::SetTrace, }; use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery}; +use proto::TypedEnvelope; use std::{any::TypeId, borrow::Cow, sync::Arc}; use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*}; use util::ResultExt as _; @@ -103,7 +106,7 @@ impl Message for RpcMessage { } pub(super) struct LanguageServerState { - project: WeakEntity, + project: Option>, name: Option, worktree_id: Option, kind: LanguageServerKind, @@ -226,7 +229,9 @@ pub struct GlobalLogStore(pub WeakEntity); 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 -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); cx.set_global(GlobalLogStore(log_store.downgrade())); @@ -291,6 +296,7 @@ impl LogStore { Some(name), None, Some(server.clone()), + None, cx, ); } @@ -331,7 +337,8 @@ impl LogStore { this.language_servers .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() { LanguageServerKind::Remote { project: project.downgrade(), @@ -344,7 +351,7 @@ impl LogStore { match event { project::Event::LanguageServerAdded(id, name, worktree_id) => { - this.add_language_server( + log_store.add_language_server( server_kind, *id, Some(name.clone()), @@ -354,21 +361,30 @@ impl LogStore { .lsp_store() .read(cx) .language_server_for_id(*id), + Some(subscription_weak_project), cx, ); } project::Event::LanguageServerRemoved(id) => { - this.remove_language_server(*id, cx); + log_store.remove_language_server(*id, cx); } 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 { 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(_) => { // 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 } => { let kind = if *received { @@ -376,7 +392,7 @@ impl LogStore { } else { 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, worktree_id: Option, server: Option>, - project: WeakEntity, + project: Option>, cx: &mut Context, ) -> Option<&mut LanguageServerState> { 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, server_id: LanguageServerId, ) -> Option<&mut LanguageServerRpcState> { @@ -771,6 +787,23 @@ impl LogStore { } } +async fn handle_toggle_lsp_logs( + lsp_log: Entity, + envelope: TypedEnvelope, + 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 { pub fn new( project: Entity, @@ -1130,26 +1163,32 @@ impl LspLogView { window: &mut Window, cx: &mut Context, ) { - self.log_store.update(cx, |log_store, _| { + self.log_store.update(cx, |log_store, cx| { if enabled { log_store.enable_rpc_trace_for_language_server(server_id); } else { log_store.disable_rpc_trace_for_language_server(server_id); } - if let Some(server_state) = log_store.language_servers.get(server_id) { - server_state - .project - .update(cx, |project, cx| { - if let Some((client, project)) = - project.lsp_store().read(cx).upstream_client() - { - // todo! client.send a new proto message to propagate the enabled - // !!!! we have to have a handler on both headless and normal projects - // that handler has to touch the Global and amend the sending bit - } - }) - .ok(); + if let Some(server_state) = log_store.language_servers.get(&server_id) { + if let Some(project) = &server_state.project { + project + .update(cx, |project, cx| { + if let Some((client, project_id)) = + project.lsp_store().read(cx).upstream_client() + { + client + .send(proto::ToggleLspLogs { + project_id, + log_type: proto::toggle_lsp_logs::LogType::Rpc as i32, + server_id: server_id.to_proto(), + enabled, + }) + .log_err(); + } + }) + .ok(); + } }; }); if !enabled && Some(server_id) == self.current_server_id { diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index f935f2263b..942a64a696 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -963,3 +963,16 @@ message MultiLspQuery { message MultiLspQueryResponse { 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; + } +} diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 70689bcd63..2222bdec08 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -396,7 +396,8 @@ message Envelope { GitCloneResponse git_clone_response = 364; 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; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index d38e54685f..5ae3e7a28b 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -312,7 +312,8 @@ messages!( (GetDefaultBranch, Background), (GetDefaultBranchResponse, Background), (GitClone, Background), - (GitCloneResponse, Background) + (GitCloneResponse, Background), + (ToggleLspLogs, Background), ); request_messages!( @@ -481,7 +482,8 @@ request_messages!( (GetDocumentDiagnostics, GetDocumentDiagnosticsResponse), (PullWorkspaceDiagnostics, Ack), (GetDefaultBranch, GetDefaultBranchResponse), - (GitClone, GitCloneResponse) + (GitClone, GitCloneResponse), + (ToggleLspLogs, Ack), ); lsp_messages!( @@ -612,6 +614,7 @@ entity_messages!( GitReset, GitCheckoutFiles, SetIndexText, + ToggleLspLogs, Push, Fetch, diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 14e2fbce21..ee400add7a 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -65,13 +65,6 @@ impl HeadlessProject { settings::init(cx); language::init(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 and toggle the spam send - language_tools::lsp_log::init(cx); } pub fn new( @@ -87,6 +80,8 @@ impl HeadlessProject { ) -> Self { debug_adapter_extension::init(proxy.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 mut store = WorktreeStore::local(true, fs.clone()); @@ -333,16 +328,6 @@ impl HeadlessProject { }) .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) => { let request = self.session.request(proto::LanguageServerPromptRequest { project_id: SSH_PROJECT_ID, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e99c8b564b..6cacf0fcbd 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -5,7 +5,7 @@ use agent_ui::AgentPanel; use anyhow::{Context as _, Result}; use clap::{Parser, command}; 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 collections::HashMap; use crashes::InitCrashHandler; @@ -621,7 +621,7 @@ pub fn main() { toolchain_selector::init(cx); theme_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); notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx); collab_ui::init(&app_state, cx); From 1cc491a919f02606a54fee752d0374599cf10780 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Aug 2025 20:52:33 +0300 Subject: [PATCH 03/13] Disable log storing in the remote LspLog storage Co-authored-by: Lukas Wirth Co-authored-by: Ben Kunkle --- crates/language_tools/src/language_tools.rs | 2 +- crates/language_tools/src/lsp_log.rs | 71 +++++++++++++------- crates/language_tools/src/lsp_log_tests.rs | 2 +- crates/remote_server/src/headless_project.rs | 3 +- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/crates/language_tools/src/language_tools.rs b/crates/language_tools/src/language_tools.rs index f5af3d01f6..45132b6ace 100644 --- a/crates/language_tools/src/language_tools.rs +++ b/crates/language_tools/src/language_tools.rs @@ -15,7 +15,7 @@ use ui::{Context, Window}; use workspace::{Item, ItemHandle, SplitDirection, Workspace}; pub fn init(client: AnyProtoClient, cx: &mut App) { - lsp_log::init(client, cx); + lsp_log::init(client, true, cx); syntax_tree_view::init(cx); key_context_view::init(cx); } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 7b4d21af6c..adef4ce2e5 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -32,6 +32,7 @@ const RECEIVE_LINE: &str = "\n// Receive:"; const MAX_STORED_LOG_ENTRIES: usize = 2000; pub struct LogStore { + store_logs: bool, projects: HashMap, ProjectState>, language_servers: HashMap, copilot_log_subscription: Option, @@ -228,11 +229,10 @@ pub struct GlobalLogStore(pub WeakEntity); 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 -pub fn init(client: AnyProtoClient, cx: &mut App) { +pub fn init(client: AnyProtoClient, store_logs: bool, cx: &mut App) { client.add_entity_message_handler(handle_toggle_lsp_logs); - let log_store = cx.new(LogStore::new); + let log_store = cx.new(|cx| LogStore::new(store_logs, cx)); cx.set_global(GlobalLogStore(log_store.downgrade())); cx.observe_new(move |workspace: &mut Workspace, _, cx| { @@ -263,7 +263,7 @@ pub fn init(client: AnyProtoClient, cx: &mut App) { } impl LogStore { - pub fn new(cx: &mut Context) -> Self { + pub fn new(store_logs: bool, cx: &mut Context) -> Self { let (io_tx, mut io_rx) = mpsc::unbounded(); let copilot_subscription = Copilot::global(cx).map(|copilot| { @@ -308,6 +308,7 @@ impl LogStore { _copilot_subscription: copilot_subscription, projects: HashMap::default(), language_servers: HashMap::default(), + store_logs, io_tx, }; @@ -484,15 +485,21 @@ impl LogStore { message: &str, cx: &mut Context, ) -> Option<()> { + let store_logs = self.store_logs; let language_server_state = self.get_language_server_state(id)?; let log_lines = &mut language_server_state.log_messages; - if let Some(new_message) = Self::push_new_message( + let message = message.trim_end().to_string(); + if !store_logs { + // Send all messages regardless of the visiblity in case of not storing, to notify the receiver anyway + cx.emit(Event::NewServerLogEntry { + id, + kind: LanguageServerLogType::Log(typ), + text: message, + }); + } else if let Some(new_message) = Self::push_new_message( log_lines, - LogMessage { - message: message.trim_end().to_string(), - typ, - }, + LogMessage { message, typ }, language_server_state.log_level, ) { cx.emit(Event::NewServerLogEntry { @@ -501,7 +508,6 @@ impl LogStore { text: new_message, }); } - Some(()) } @@ -511,10 +517,19 @@ impl LogStore { message: &str, cx: &mut Context, ) -> Option<()> { + let store_logs = self.store_logs; let language_server_state = self.get_language_server_state(id)?; let log_lines = &mut language_server_state.trace_messages; - if let Some(new_message) = Self::push_new_message( + if !store_logs { + // Send all messages regardless of the visiblity in case of not storing, to notify the receiver anyway + cx.emit(Event::NewServerLogEntry { + id, + // todo! Ben, fix this here too! + kind: LanguageServerLogType::Trace(project::lsp_store::TraceLevel::Verbose), + text: message.trim().to_string(), + }); + } else if let Some(new_message) = Self::push_new_message( log_lines, TraceMessage { message: message.trim().to_string(), @@ -528,7 +543,6 @@ impl LogStore { text: new_message, }); } - Some(()) } @@ -542,9 +556,9 @@ impl LogStore { } let visible = message.should_include(current_severity); - let re = visible.then(|| message.as_ref().to_string()); + let visible_message = visible.then(|| message.as_ref().to_string()); log_lines.push_back(message); - re + visible_message } fn add_language_server_rpc( @@ -554,6 +568,7 @@ impl LogStore { message: &str, cx: &mut Context<'_, LogStore>, ) { + let store_logs = self.store_logs; let Some(state) = self .get_language_server_state(language_server_id) .and_then(|state| state.rpc_state.as_mut()) @@ -570,9 +585,11 @@ impl LogStore { MessageKind::Send => SEND_LINE, MessageKind::Receive => RECEIVE_LINE, }; - rpc_log_lines.push_back(RpcMessage { - message: line_before_message.to_string(), - }); + if store_logs { + rpc_log_lines.push_back(RpcMessage { + message: line_before_message.to_string(), + }); + } cx.emit(Event::NewServerLogEntry { id: language_server_id, kind: LanguageServerLogType::Rpc { @@ -586,8 +603,17 @@ impl LogStore { rpc_log_lines.pop_front(); } - rpc_log_lines.push_back(RpcMessage { - message: message.trim().to_owned(), + if store_logs { + rpc_log_lines.push_back(RpcMessage { + message: message.trim().to_owned(), + }); + } + cx.emit(Event::NewServerLogEntry { + id: language_server_id, + kind: LanguageServerLogType::Rpc { + received: kind == MessageKind::Receive, + }, + text: message.to_owned(), }); } @@ -775,13 +801,6 @@ impl LogStore { }; self.add_language_server_rpc(language_server_id, kind, message, cx); - cx.emit(Event::NewServerLogEntry { - id: language_server_id, - kind: LanguageServerLogType::Rpc { - received: is_received, - }, - text: message.to_owned(), - }); cx.notify(); Some(()) } diff --git a/crates/language_tools/src/lsp_log_tests.rs b/crates/language_tools/src/lsp_log_tests.rs index ad2b653fdc..a7dbaa2a60 100644 --- a/crates/language_tools/src/lsp_log_tests.rs +++ b/crates/language_tools/src/lsp_log_tests.rs @@ -51,7 +51,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) { }, ); - let log_store = cx.new(LogStore::new); + let log_store = cx.new(|cx| LogStore::new(true, cx)); log_store.update(cx, |store, cx| store.add_project(&project, cx)); let _rust_buffer = project diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index ee400add7a..f0df2a5eac 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -80,8 +80,7 @@ impl HeadlessProject { ) -> Self { debug_adapter_extension::init(proxy.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); + language_tools::lsp_log::init(session.clone(), false, cx); let worktree_store = cx.new(|cx| { let mut store = WorktreeStore::local(true, fs.clone()); From 13c8d4e05284ebf01f660ed27751e67d12f1fd10 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 22 Aug 2025 21:20:24 +0300 Subject: [PATCH 04/13] Properly show the binary stats Co-authored-by: Ben Kunkle Co-authored-by: Lukas Wirth --- crates/language_tools/src/lsp_log.rs | 98 +++++++++++++++++++++------- crates/project/src/lsp_store.rs | 4 +- 2 files changed, 77 insertions(+), 25 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index adef4ce2e5..5cd8583b2c 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -11,8 +11,8 @@ use gpui::{ use itertools::Itertools; use language::{LanguageServerId, language_settings::SoftWrap}; use lsp::{ - IoKind, LanguageServer, LanguageServerName, LanguageServerSelector, MessageType, - SetTraceParams, TraceValue, notification::SetTrace, + IoKind, LanguageServer, LanguageServerBinary, LanguageServerName, LanguageServerSelector, + MessageType, SetTraceParams, TraceValue, notification::SetTrace, }; use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery}; use proto::TypedEnvelope; @@ -951,7 +951,7 @@ impl LspLogView { } fn editor_for_server_info( - server: &LanguageServer, + info: ServerInfo, window: &mut Window, cx: &mut Context, ) -> (Entity, Vec) { @@ -966,22 +966,21 @@ impl LspLogView { * Capabilities: {CAPABILITIES} * Configuration: {CONFIGURATION}", - NAME = server.name(), - ID = server.server_id(), - BINARY = server.binary(), - WORKSPACE_FOLDERS = server - .workspace_folders() - .into_iter() - .filter_map(|path| path - .to_file_path() - .ok() - .map(|path| path.to_string_lossy().into_owned())) - .collect::>() - .join(", "), - CAPABILITIES = serde_json::to_string_pretty(&server.capabilities()) + NAME = info.name, + ID = info.id, + BINARY = info.binary.as_ref().map_or_else( + || "Unknown".to_string(), + |bin| bin.path.as_path().to_string_lossy().to_string() + ), + WORKSPACE_FOLDERS = info.workspace_folders.join(", "), + CAPABILITIES = serde_json::to_string_pretty(&info.capabilities) .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")), - CONFIGURATION = serde_json::to_string_pretty(server.configuration()) - .unwrap_or_else(|e| format!("Failed to serialize configuration: {e}")), + CONFIGURATION = info + .configuration + .map(|configuration| serde_json::to_string_pretty(&configuration)) + .transpose() + .unwrap_or_else(|e| Some(format!("Failed to serialize configuration: {e}"))) + .unwrap_or_else(|| "Unknown".to_string()), ); let editor = initialize_new_editor(server_info, false, window, cx); let editor_subscription = cx.subscribe( @@ -1247,15 +1246,38 @@ impl LspLogView { window: &mut Window, cx: &mut Context, ) { - // todo! there's no language server for the remote case, hence no server info! - // BUT we do have the capabilities info within the LspStore.lsp_server_capabilities - let lsp_store = self.project.read(cx).lsp_store(); - let Some(server) = lsp_store.read(cx).language_server_for_id(server_id) else { + let Some(server_info) = self + .project + .read(cx) + .lsp_store() + .update(cx, |lsp_store, _| { + lsp_store + .language_server_for_id(server_id) + .as_ref() + .map(|language_server| ServerInfo::new(language_server)) + .or_else(move || { + let capabilities = + lsp_store.lsp_server_capabilities.get(&server_id)?.clone(); + let name = lsp_store + .language_server_statuses + .get(&server_id) + .map(|status| status.name.clone())?; + Some(ServerInfo { + id: server_id, + capabilities, + binary: None, + name, + workspace_folders: Vec::new(), + configuration: None, + }) + }) + }) + 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, window, cx); + let (editor, editor_subscriptions) = Self::editor_for_server_info(server_info, window, cx); self.editor = editor; self.editor_subscriptions = editor_subscriptions; cx.notify(); @@ -1870,6 +1892,36 @@ impl LspLogToolbarItemView { } } +struct ServerInfo { + id: LanguageServerId, + capabilities: lsp::ServerCapabilities, + binary: Option, + name: LanguageServerName, + workspace_folders: Vec, + configuration: Option, +} + +impl ServerInfo { + fn new(server: &LanguageServer) -> Self { + Self { + id: server.server_id(), + capabilities: server.capabilities(), + binary: Some(server.binary().clone()), + name: server.name(), + workspace_folders: server + .workspace_folders() + .into_iter() + .filter_map(|path| { + path.to_file_path() + .ok() + .map(|path| path.to_string_lossy().into_owned()) + }) + .collect::>(), + configuration: Some(server.configuration().clone()), + } + } +} + pub enum Event { NewServerLogEntry { id: LanguageServerId, diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 5c2782b3b9..3659d6ab92 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -3488,13 +3488,13 @@ pub struct LspStore { buffer_store: Entity, worktree_store: Entity, pub languages: Arc, - language_server_statuses: BTreeMap, + pub language_server_statuses: BTreeMap, active_entry: Option, _maintain_workspace_config: (Task>, watch::Sender<()>), _maintain_buffer_languages: Task<()>, diagnostic_summaries: HashMap, HashMap>>, - pub(super) lsp_server_capabilities: HashMap, + pub lsp_server_capabilities: HashMap, lsp_document_colors: HashMap, lsp_code_lens: HashMap, running_lsp_requests: HashMap>)>, From dc6377b8bc7e917b3bfd154ba63061dfe409870b Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Fri, 22 Aug 2025 14:27:55 -0500 Subject: [PATCH 05/13] fix handling of verbose --- crates/language_tools/src/lsp_log.rs | 55 +++++++++++++++++++++------- crates/project/src/lsp_store.rs | 42 +++++---------------- crates/proto/proto/lsp.proto | 8 +--- 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 5cd8583b2c..8ba119b418 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -80,6 +80,7 @@ impl Message for LogMessage { pub(super) struct TraceMessage { message: String, + is_verbose: bool, } impl AsRef for TraceMessage { @@ -89,7 +90,15 @@ impl AsRef for TraceMessage { } impl Message for TraceMessage { - type Level = (); + type Level = TraceValue; + + fn should_include(&self, level: Self::Level) -> bool { + match level { + TraceValue::Off => false, + TraceValue::Messages => !self.is_verbose, + TraceValue::Verbose => true, + } + } } struct RpcMessage { @@ -192,7 +201,7 @@ impl LogKind { fn from_server_log_type(log_type: &LanguageServerLogType) -> Self { match log_type { LanguageServerLogType::Log(_) => Self::Logs, - LanguageServerLogType::Trace(_) => Self::Trace, + LanguageServerLogType::Trace { .. } => Self::Trace, LanguageServerLogType::Rpc { .. } => Self::Rpc, } } @@ -383,9 +392,13 @@ impl LogStore { project::LanguageServerLogType::Log(typ) => { log_store.add_language_server_log(*id, *typ, message, cx); } - project::LanguageServerLogType::Trace(_) => { - // todo! do something with trace level - log_store.add_language_server_trace(*id, message, cx); + project::LanguageServerLogType::Trace { verbose_info } => { + log_store.add_language_server_trace( + *id, + message, + verbose_info.clone(), + cx, + ); } project::LanguageServerLogType::Rpc { received } => { let kind = if *received { @@ -491,7 +504,7 @@ impl LogStore { let log_lines = &mut language_server_state.log_messages; let message = message.trim_end().to_string(); if !store_logs { - // Send all messages regardless of the visiblity in case of not storing, to notify the receiver anyway + // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway cx.emit(Event::NewServerLogEntry { id, kind: LanguageServerLogType::Log(typ), @@ -515,6 +528,7 @@ impl LogStore { &mut self, id: LanguageServerId, message: &str, + verbose_info: Option, cx: &mut Context, ) -> Option<()> { let store_logs = self.store_logs; @@ -522,24 +536,33 @@ impl LogStore { let log_lines = &mut language_server_state.trace_messages; if !store_logs { - // Send all messages regardless of the visiblity in case of not storing, to notify the receiver anyway + // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway cx.emit(Event::NewServerLogEntry { id, - // todo! Ben, fix this here too! - kind: LanguageServerLogType::Trace(project::lsp_store::TraceLevel::Verbose), + kind: LanguageServerLogType::Trace { verbose_info }, text: message.trim().to_string(), }); } else if let Some(new_message) = Self::push_new_message( log_lines, TraceMessage { message: message.trim().to_string(), + is_verbose: false, }, - (), + TraceValue::Messages, ) { + if let Some(verbose_message) = verbose_info.as_ref() { + Self::push_new_message( + log_lines, + TraceMessage { + message: verbose_message.clone(), + is_verbose: true, + }, + TraceValue::Verbose, + ); + } cx.emit(Event::NewServerLogEntry { id, - // todo! Ben, fix this here too! - kind: LanguageServerLogType::Trace(project::lsp_store::TraceLevel::Verbose), + kind: LanguageServerLogType::Trace { verbose_info }, text: new_message, }); } @@ -1115,11 +1138,17 @@ impl LspLogView { window: &mut Window, cx: &mut Context, ) { + let trace_level = self + .log_store + .update(cx, |this, _| { + Some(this.get_language_server_state(server_id)?.trace_level) + }) + .unwrap_or(TraceValue::Messages); let log_contents = self .log_store .read(cx) .server_trace(server_id) - .map(|v| log_contents(v, ())); + .map(|v| log_contents(v, trace_level)); if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.active_entry_kind = LogKind::Trace; diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 3659d6ab92..4e0201d722 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -977,13 +977,9 @@ impl LocalLspStore { this.update(&mut cx, |_, cx| { cx.emit(LspStoreEvent::LanguageServerLog( server_id, - // todo! store verbose info on Verbose - LanguageServerLogType::Trace( - params - .verbose - .map(|_verbose_info| TraceLevel::Verbose) - .unwrap_or(TraceLevel::Messages), - ), + LanguageServerLogType::Trace { + verbose_info: params.verbose, + }, params.message, )); }) @@ -12684,17 +12680,10 @@ impl PartialEq for LanguageServerPromptRequest { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TraceLevel { - Off, - Messages, - Verbose, -} - #[derive(Clone, Debug, PartialEq)] pub enum LanguageServerLogType { Log(MessageType), - Trace(TraceLevel), + Trace { verbose_info: Option }, Rpc { received: bool }, } @@ -12717,15 +12706,9 @@ impl LanguageServerLogType { level: level as i32, }) } - Self::Trace(trace_level) => { - use proto::trace_message; - let level = match trace_level { - TraceLevel::Off => trace_message::TraceLevel::Off, - TraceLevel::Messages => trace_message::TraceLevel::Messages, - TraceLevel::Verbose => trace_message::TraceLevel::Verbose, - }; + Self::Trace { verbose_info } => { proto::language_server_log::LogType::Trace(proto::TraceMessage { - level: level as i32, + verbose_info: verbose_info.to_owned(), }) } Self::Rpc { received } => { @@ -12743,7 +12726,6 @@ impl LanguageServerLogType { pub fn from_proto(log_type: proto::language_server_log::LogType) -> Self { use proto::log_message::LogLevel; use proto::rpc_message; - use proto::trace_message; match log_type { proto::language_server_log::LogType::Log(message_type) => Self::Log( match LogLevel::from_i32(message_type.level).unwrap_or(LogLevel::Log) { @@ -12753,15 +12735,9 @@ impl LanguageServerLogType { LogLevel::Log => MessageType::LOG, }, ), - proto::language_server_log::LogType::Trace(trace) => Self::Trace( - match trace_message::TraceLevel::from_i32(trace.level) - .unwrap_or(trace_message::TraceLevel::Messages) - { - trace_message::TraceLevel::Off => TraceLevel::Off, - trace_message::TraceLevel::Messages => TraceLevel::Messages, - trace_message::TraceLevel::Verbose => TraceLevel::Verbose, - }, - ), + 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) diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index 942a64a696..16f6217b29 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -630,13 +630,7 @@ message LogMessage { } message TraceMessage { - TraceLevel level = 1; - - enum TraceLevel { - OFF = 0; - MESSAGES = 1; - VERBOSE = 2; - } + optional string verbose_info = 1; } message RpcMessage { From 9d1e2f5278ba90366ff0d8f6e9e02ba206d24176 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Aug 2025 15:43:25 +0300 Subject: [PATCH 06/13] Register log-related actions for all kinds of projects --- crates/language_tools/src/lsp_log.rs | 30 +++++++++++----------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 8ba119b418..1a5d33cb0a 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -245,27 +245,21 @@ pub fn init(client: AnyProtoClient, store_logs: bool, cx: &mut App) { cx.set_global(GlobalLogStore(log_store.downgrade())); cx.observe_new(move |workspace: &mut Workspace, _, cx| { - let project = workspace.project(); - if project.read(cx).is_local() || project.read(cx).is_via_ssh() { - log_store.update(cx, |store, cx| { - store.add_project(project, cx); - }); - } + log_store.update(cx, |store, cx| { + store.add_project(workspace.project(), cx); + }); let log_store = log_store.clone(); workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| { - let project = workspace.project().read(cx); - if project.is_local() || project.is_via_ssh() { - let project = workspace.project().clone(); - let log_store = log_store.clone(); - get_or_create_tool( - workspace, - SplitDirection::Right, - window, - cx, - move |window, cx| LspLogView::new(project, log_store, window, cx), - ); - } + let log_store = log_store.clone(); + let project = workspace.project().clone(); + get_or_create_tool( + workspace, + SplitDirection::Right, + window, + cx, + move |window, cx| LspLogView::new(project, log_store, window, cx), + ); }); }) .detach(); From 25ed82c994ede22a4ee8eb7a60f003e8b8ac9068 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Aug 2025 16:03:03 +0300 Subject: [PATCH 07/13] Move `call` into a separate feature in the `workspace` --- crates/assistant_slash_command/Cargo.toml | 2 +- crates/assistant_tool/Cargo.toml | 2 +- crates/breadcrumbs/Cargo.toml | 2 +- crates/copilot/Cargo.toml | 2 +- crates/editor/Cargo.toml | 2 +- crates/language_tools/Cargo.toml | 2 +- crates/workspace/Cargo.toml | 3 ++- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/assistant_slash_command/Cargo.toml b/crates/assistant_slash_command/Cargo.toml index f7b7af9b87..e33c2cda1a 100644 --- a/crates/assistant_slash_command/Cargo.toml +++ b/crates/assistant_slash_command/Cargo.toml @@ -25,7 +25,7 @@ parking_lot.workspace = true serde.workspace = true serde_json.workspace = true ui.workspace = true -workspace.workspace = true +workspace = { path = "../workspace", default-features = false } workspace-hack.workspace = true [dev-dependencies] diff --git a/crates/assistant_tool/Cargo.toml b/crates/assistant_tool/Cargo.toml index c95695052a..951226adfd 100644 --- a/crates/assistant_tool/Cargo.toml +++ b/crates/assistant_tool/Cargo.toml @@ -28,7 +28,7 @@ serde.workspace = true serde_json.workspace = true text.workspace = true util.workspace = true -workspace.workspace = true +workspace = { path = "../workspace", default-features = false } workspace-hack.workspace = true [dev-dependencies] diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml index c25cfc3c86..46f43d1630 100644 --- a/crates/breadcrumbs/Cargo.toml +++ b/crates/breadcrumbs/Cargo.toml @@ -19,7 +19,7 @@ itertools.workspace = true settings.workspace = true theme.workspace = true ui.workspace = true -workspace.workspace = true +workspace = { path = "../workspace", default-features = false } zed_actions.workspace = true workspace-hack.workspace = true diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 0fc119f311..470d198958 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -50,7 +50,7 @@ sum_tree.workspace = true task.workspace = true ui.workspace = true util.workspace = true -workspace.workspace = true +workspace = { path = "../workspace", default-features = false } workspace-hack.workspace = true itertools.workspace = true diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 339f98ae8b..b7051c9b19 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -89,7 +89,7 @@ ui.workspace = true url.workspace = true util.workspace = true uuid.workspace = true -workspace.workspace = true +workspace = { path = "../workspace", default-features = false } zed_actions.workspace = true workspace-hack.workspace = true diff --git a/crates/language_tools/Cargo.toml b/crates/language_tools/Cargo.toml index b8f85d8d90..a10b7dc50b 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -31,7 +31,7 @@ theme.workspace = true tree-sitter.workspace = true ui.workspace = true util.workspace = true -workspace.workspace = true +workspace = { path = "../workspace", default-features = false } zed_actions.workspace = true workspace-hack.workspace = true diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 869aa5322e..ce9be0c7ed 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -13,6 +13,7 @@ path = "src/workspace.rs" doctest = false [features] +default = ["call"] test-support = [ "call/test-support", "client/test-support", @@ -29,7 +30,7 @@ test-support = [ any_vec.workspace = true anyhow.workspace = true async-recursion.workspace = true -call.workspace = true +call = { workspace = true, optional = true } client.workspace = true clock.workspace = true collections.workspace = true From cc680d280de55715df26f1bd12e7602d2417a906 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 24 Aug 2025 02:11:37 +0300 Subject: [PATCH 08/13] zzz --- crates/workspace/src/workspace.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 044601df97..2a2e674af5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -5006,6 +5006,7 @@ impl Workspace { self.active_call.as_ref().map(|(call, _)| call) } + #[cfg(feature = "call")] fn on_active_call_event( &mut self, _: &Entity, @@ -5022,6 +5023,16 @@ impl Workspace { } } + #[cfg(not(feature = "call"))] + fn on_active_call_event( + &mut self, + _: &Entity, + _: &call::room::Event, + _: &mut Window, + _: &mut Context, + ) { + } + pub fn database_id(&self) -> Option { self.database_id } From 775160a1991efe885ff44663c1abbd02364a6a1f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Aug 2025 15:58:41 +0300 Subject: [PATCH 09/13] cfg-out call-related workspace functionality --- crates/call/src/call_impl/mod.rs | 10 ++ crates/workspace/src/pane_group.rs | 15 +- crates/workspace/src/workspace.rs | 248 ++++++++++++++++++++++------- 3 files changed, 209 insertions(+), 64 deletions(-) diff --git a/crates/call/src/call_impl/mod.rs b/crates/call/src/call_impl/mod.rs index 156a80faba..adab36bbd6 100644 --- a/crates/call/src/call_impl/mod.rs +++ b/crates/call/src/call_impl/mod.rs @@ -394,6 +394,16 @@ impl ActiveCall { } } + #[cfg(not(feature = "call"))] + pub fn unshare_project( + &mut self, + _project: Entity, + _cx: &mut Context, + ) -> Result<()> { + Ok(()) + } + + #[cfg(feature = "call")] pub fn unshare_project( &mut self, project: Entity, diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 9c2d09fd26..5c08148031 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -4,11 +4,14 @@ use crate::{ workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical}, }; use anyhow::Result; + +#[cfg(feature = "call")] use call::{ActiveCall, ParticipantLocation}; + use collections::HashMap; use gpui::{ - Along, AnyView, AnyWeakView, Axis, Bounds, Entity, Hsla, IntoElement, MouseButton, Pixels, - Point, StyleRefinement, WeakEntity, Window, point, size, + Along, AnyView, AnyWeakView, Axis, Bounds, Entity, Hsla, IntoElement, Pixels, Point, + StyleRefinement, WeakEntity, Window, point, size, }; use parking_lot::Mutex; use project::Project; @@ -197,6 +200,7 @@ pub enum Member { pub struct PaneRenderContext<'a> { pub project: &'a Entity, pub follower_states: &'a HashMap, + #[cfg(feature = "call")] pub active_call: Option<&'a Entity>, pub active_pane: &'a Entity, pub app_state: &'a Arc, @@ -258,6 +262,11 @@ impl PaneLeaderDecorator for PaneRenderContext<'_> { let mut leader_color; let status_box; match leader_id { + #[cfg(not(feature = "call"))] + CollaboratorId::PeerId(_) => { + return LeaderDecoration::default(); + } + #[cfg(feature = "call")] CollaboratorId::PeerId(peer_id) => { let Some(leader) = self.active_call.as_ref().and_then(|call| { let room = call.read(cx).room()?.read(cx); @@ -315,7 +324,7 @@ impl PaneLeaderDecorator for PaneRenderContext<'_> { |this, (leader_project_id, leader_user_id)| { let app_state = self.app_state.clone(); this.cursor_pointer().on_mouse_down( - MouseButton::Left, + gpui::MouseButton::Left, move |_, _, cx| { crate::join_in_room_project( leader_project_id, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2a2e674af5..9d45c8bef1 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -9,6 +9,7 @@ pub mod pane_group; mod path_list; mod persistence; pub mod searchable; +#[cfg(feature = "call")] pub mod shared_screen; mod status_bar; pub mod tasks; @@ -22,11 +23,17 @@ pub use dock::Panel; pub use path_list::PathList; pub use toast_layer::{ToastAction, ToastLayer, ToastView}; -use anyhow::{Context as _, Result, anyhow}; +#[cfg(feature = "call")] use call::{ActiveCall, call_settings::CallSettings}; +#[cfg(feature = "call")] +use client::{Status, proto::ErrorCode}; +#[cfg(feature = "call")] +use shared_screen::SharedScreen; + +use anyhow::{Context as _, Result, anyhow}; use client::{ - ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore, - proto::{self, ErrorCode, PanelId, PeerId}, + ChannelId, Client, ErrorExt, TypedEnvelope, UserStore, + proto::{self, PanelId, PeerId}, }; use collections::{HashMap, HashSet, hash_map}; use dock::{Dock, DockPosition, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE}; @@ -79,7 +86,6 @@ use schemars::JsonSchema; use serde::Deserialize; use session::AppSession; use settings::{Settings, update_settings_file}; -use shared_screen::SharedScreen; use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, @@ -886,6 +892,7 @@ impl Global for GlobalAppState {} pub struct WorkspaceStore { workspaces: HashSet>, + #[cfg(feature = "call")] client: Arc, _subscriptions: Vec, } @@ -1117,6 +1124,7 @@ pub struct Workspace { window_edited: bool, last_window_title: Option, dirty_items: HashMap, + #[cfg(feature = "call")] active_call: Option<(Entity, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: Option, @@ -1158,6 +1166,7 @@ pub struct FollowerState { struct FollowerView { view: Box, + #[cfg(feature = "call")] location: Option, } @@ -1357,10 +1366,15 @@ impl Workspace { let session_id = app_state.session.read(cx).id().to_owned(); + #[cfg(feature = "call")] let mut active_call = None; - if let Some(call) = ActiveCall::try_global(cx) { - let subscriptions = vec![cx.subscribe_in(&call, window, Self::on_active_call_event)]; - active_call = Some((call, subscriptions)); + #[cfg(feature = "call")] + { + if let Some(call) = ActiveCall::try_global(cx) { + let subscriptions = + vec![cx.subscribe_in(&call, window, Self::on_active_call_event)]; + active_call = Some((call, subscriptions)); + } } let (serializable_items_tx, serializable_items_rx) = @@ -1446,6 +1460,7 @@ impl Workspace { window_edited: false, last_window_title: None, dirty_items: Default::default(), + #[cfg(feature = "call")] active_call, database_id: workspace_id, app_state, @@ -2250,6 +2265,7 @@ impl Workspace { window: &mut Window, cx: &mut Context, ) -> Task> { + #[cfg(feature = "call")] let active_call = self.active_call().cloned(); // On Linux and Windows, closing the last window should restore the last workspace. @@ -2258,51 +2274,58 @@ impl Workspace { && cx.windows().len() == 1; cx.spawn_in(window, async move |this, cx| { - let workspace_count = cx.update(|_window, cx| { - cx.windows() - .iter() - .filter(|window| window.downcast::().is_some()) - .count() - })?; - - if let Some(active_call) = active_call - && workspace_count == 1 - && active_call.read_with(cx, |call, _| call.room().is_some())? + #[cfg(feature = "call")] { - if close_intent == CloseIntent::CloseWindow { - let answer = cx.update(|window, cx| { - window.prompt( - PromptLevel::Warning, - "Do you want to leave the current call?", - None, - &["Close window and hang up", "Cancel"], - cx, - ) - })?; + let workspace_count = cx.update(|_window, cx| { + cx.windows() + .iter() + .filter(|window| window.downcast::().is_some()) + .count() + })?; + if let Some(active_call) = active_call + && workspace_count == 1 + && active_call.read_with(cx, |call, _| call.room().is_some())? + { + if close_intent == CloseIntent::CloseWindow { + let answer = cx.update(|window, cx| { + window.prompt( + PromptLevel::Warning, + "Do you want to leave the current call?", + None, + &["Close window and hang up", "Cancel"], + cx, + ) + })?; - if answer.await.log_err() == Some(1) { - return anyhow::Ok(false); - } else { - active_call - .update(cx, |call, cx| call.hang_up(cx))? - .await - .log_err(); - } - } - if close_intent == CloseIntent::ReplaceWindow { - _ = active_call.update(cx, |this, cx| { - let workspace = cx - .windows() - .iter() - .filter_map(|window| window.downcast::()) - .next() - .unwrap(); - let project = workspace.read(cx)?.project.clone(); - if project.read(cx).is_shared() { - this.unshare_project(project, cx)?; + if answer.await.log_err() == Some(1) { + return anyhow::Ok(false); + } else { + { + active_call + .update(cx, |call, cx| call.hang_up(cx))? + .await + .log_err(); + } } - Ok::<_, anyhow::Error>(()) - })?; + } + if close_intent == CloseIntent::ReplaceWindow { + #[cfg(feature = "call")] + { + _ = active_call.update(cx, |active_call, cx| { + let workspace = cx + .windows() + .iter() + .filter_map(|window| window.downcast::()) + .next() + .unwrap(); + let project = workspace.read(cx)?.project.clone(); + if project.read(cx).is_shared() { + active_call.unshare_project(project, cx)?; + } + Ok::<_, anyhow::Error>(()) + })?; + } + } } } @@ -3486,6 +3509,7 @@ impl Workspace { item } + #[cfg(feature = "call")] pub fn open_shared_screen( &mut self, peer_id: PeerId, @@ -3907,8 +3931,11 @@ impl Workspace { pane.update(cx, |pane, _| { pane.track_alternate_file_items(); }); - if *local { - self.unfollow_in_pane(pane, window, cx); + #[cfg(feature = "call")] + { + if *local { + self.unfollow_in_pane(pane, window, cx); + } } serialize_workspace = *focus_changed || pane != self.active_pane(); if pane == self.active_pane() { @@ -3973,6 +4000,17 @@ impl Workspace { } } + #[cfg(not(feature = "call"))] + pub fn unfollow_in_pane( + &mut self, + _pane: &Entity, + _window: &mut Window, + _cx: &mut Context, + ) -> Option { + None + } + + #[cfg(feature = "call")] pub fn unfollow_in_pane( &mut self, pane: &Entity, @@ -4122,6 +4160,7 @@ impl Workspace { cx.notify(); } + #[cfg(feature = "call")] pub fn start_following( &mut self, leader_id: impl Into, @@ -4185,6 +4224,16 @@ impl Workspace { } } + #[cfg(not(feature = "call"))] + pub fn follow_next_collaborator( + &mut self, + _: &FollowNextCollaborator, + _window: &mut Window, + _cx: &mut Context, + ) { + } + + #[cfg(feature = "call")] pub fn follow_next_collaborator( &mut self, _: &FollowNextCollaborator, @@ -4233,6 +4282,16 @@ impl Workspace { } } + #[cfg(not(feature = "call"))] + pub fn follow( + &mut self, + _leader_id: impl Into, + _window: &mut Window, + _cx: &mut Context, + ) { + } + + #[cfg(feature = "call")] pub fn follow( &mut self, leader_id: impl Into, @@ -4285,6 +4344,17 @@ impl Workspace { } } + #[cfg(not(feature = "call"))] + pub fn unfollow( + &mut self, + _leader_id: impl Into, + _window: &mut Window, + _cx: &mut Context, + ) -> Option<()> { + None + } + + #[cfg(feature = "call")] pub fn unfollow( &mut self, leader_id: impl Into, @@ -4595,6 +4665,7 @@ impl Workspace { anyhow::bail!("no id for view"); }; let id = ViewId::from_proto(id)?; + #[cfg(feature = "call")] let panel_id = view.panel_id.and_then(proto::PanelId::from_i32); let pane = this.update(cx, |this, _cx| { @@ -4667,6 +4738,7 @@ impl Workspace { id, FollowerView { view: item, + #[cfg(feature = "call")] location: panel_id, }, ); @@ -4721,6 +4793,7 @@ impl Workspace { view.map(|view| { entry.insert(FollowerView { view, + #[cfg(feature = "call")] location: None, }) }) @@ -4911,6 +4984,17 @@ impl Workspace { ) } + #[cfg(not(feature = "call"))] + fn active_item_for_peer( + &self, + _peer_id: PeerId, + _window: &mut Window, + _cx: &mut Context, + ) -> Option<(Option, Box)> { + None + } + + #[cfg(feature = "call")] fn active_item_for_peer( &self, peer_id: PeerId, @@ -4952,6 +5036,7 @@ impl Workspace { item_to_activate } + #[cfg(feature = "call")] fn shared_screen_for_peer( &self, peer_id: PeerId, @@ -5002,6 +5087,7 @@ impl Workspace { } } + #[cfg(feature = "call")] pub fn active_call(&self) -> Option<&Entity> { self.active_call.as_ref().map(|(call, _)| call) } @@ -5023,16 +5109,6 @@ impl Workspace { } } - #[cfg(not(feature = "call"))] - fn on_active_call_event( - &mut self, - _: &Entity, - _: &call::room::Event, - _: &mut Window, - _: &mut Context, - ) { - } - pub fn database_id(&self) -> Option { self.database_id } @@ -5929,6 +6005,17 @@ impl Workspace { } } +#[cfg(not(feature = "call"))] +fn leader_border_for_pane( + _follower_states: &HashMap, + _pane: &Entity, + _: &Window, + _cx: &App, +) -> Option
{ + None +} + +#[cfg(feature = "call")] fn leader_border_for_pane( follower_states: &HashMap, pane: &Entity, @@ -6395,6 +6482,7 @@ impl Render for Workspace { &PaneRenderContext { follower_states: &self.follower_states, + #[cfg(feature = "call")] active_call: self.active_call(), active_pane: &self.active_pane, app_state: &self.app_state, @@ -6459,6 +6547,7 @@ impl Render for Workspace { &PaneRenderContext { follower_states: &self.follower_states, + #[cfg(feature = "call")] active_call: self.active_call(), active_pane: &self.active_pane, app_state: &self.app_state, @@ -6521,6 +6610,7 @@ impl Render for Workspace { &PaneRenderContext { follower_states: &self.follower_states, + #[cfg(feature = "call")] active_call: self.active_call(), active_pane: &self.active_pane, app_state: &self.app_state, @@ -6569,6 +6659,7 @@ impl Render for Workspace { &PaneRenderContext { follower_states: &self.follower_states, + #[cfg(feature = "call")] active_call: self.active_call(), active_pane: &self.active_pane, app_state: &self.app_state, @@ -6642,10 +6733,22 @@ impl WorkspaceStore { client.add_request_handler(cx.weak_entity(), Self::handle_follow), client.add_message_handler(cx.weak_entity(), Self::handle_update_followers), ], + #[cfg(feature = "call")] client, } } + #[cfg(not(feature = "call"))] + pub fn update_followers( + &self, + _project_id: Option, + _update: proto::update_followers::Variant, + _cx: &App, + ) -> Option<()> { + None + } + + #[cfg(feature = "call")] pub fn update_followers( &self, project_id: Option, @@ -6811,6 +6914,7 @@ actions!( ] ); +#[cfg(feature = "call")] async fn join_channel_internal( channel_id: ChannelId, app_state: &Arc, @@ -6958,6 +7062,17 @@ async fn join_channel_internal( anyhow::Ok(false) } +#[cfg(not(feature = "call"))] +pub fn join_channel( + _channel_id: ChannelId, + _app_state: Arc, + _requesting_window: Option>, + _cx: &mut App, +) -> Task> { + Task::ready(Ok(())) +} + +#[cfg(feature = "call")] pub fn join_channel( channel_id: ChannelId, app_state: Arc, @@ -7465,6 +7580,17 @@ fn serialize_ssh_project( }) } +#[cfg(not(feature = "call"))] +pub fn join_in_room_project( + _project_id: u64, + _follow_user_id: u64, + _app_state: Arc, + _cx: &mut App, +) -> Task> { + Task::ready(Ok(())) +} + +#[cfg(feature = "call")] pub fn join_in_room_project( project_id: u64, follow_user_id: u64, From d433406103ed0eb1b3231296a4e3a18005aa3e1c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Aug 2025 16:13:35 +0300 Subject: [PATCH 10/13] One less remote usage --- crates/language_tools/src/lsp_log.rs | 102 +++++++++---------- crates/remote_server/src/headless_project.rs | 10 +- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 1a5d33cb0a..ac33a02bcb 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -135,12 +135,6 @@ pub enum LanguageServerKind { Global, } -impl LanguageServerKind { - fn is_remote(&self) -> bool { - matches!(self, LanguageServerKind::Remote { .. }) - } -} - impl std::fmt::Debug for LanguageServerKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -1160,6 +1154,7 @@ impl LspLogView { window: &mut Window, cx: &mut Context, ) { + self.toggle_rpc_trace_for_server(server_id, true, window, cx); let rpc_log = self.log_store.update(cx, |log_store, _| { log_store .enable_rpc_trace_for_language_server(server_id) @@ -1597,7 +1592,6 @@ impl Render for LspLogToolbarItemView { let view_selector = current_server.map(|server| { let server_id = server.server_id; - let is_remote = server.server_kind.is_remote(); let rpc_trace_enabled = server.rpc_trace_enabled; let log_view = log_view.clone(); PopoverMenu::new("LspViewSelector") @@ -1619,55 +1613,53 @@ impl Render for LspLogToolbarItemView { view.show_logs_for_server(server_id, window, cx); }), ) - .when(!is_remote, |this| { - this.entry( - SERVER_TRACE, - None, - window.handler_for(&log_view, move |view, window, cx| { - view.show_trace_for_server(server_id, window, cx); - }), - ) - .custom_entry( - { - let log_toolbar_view = log_toolbar_view.clone(); - move |window, _| { - h_flex() - .w_full() - .justify_between() - .child(Label::new(RPC_MESSAGES)) - .child( - div().child( - Checkbox::new( - "LspLogEnableRpcTrace", - if rpc_trace_enabled { + .entry( + SERVER_TRACE, + None, + window.handler_for(&log_view, move |view, window, cx| { + view.show_trace_for_server(server_id, window, cx); + }), + ) + .custom_entry( + { + let log_toolbar_view = log_toolbar_view.clone(); + move |window, _| { + h_flex() + .w_full() + .justify_between() + .child(Label::new(RPC_MESSAGES)) + .child( + div().child( + Checkbox::new( + "LspLogEnableRpcTrace", + if rpc_trace_enabled { + ToggleState::Selected + } else { + ToggleState::Unselected + }, + ) + .on_click(window.listener_for( + &log_toolbar_view, + move |view, selection, window, cx| { + let enabled = matches!( + selection, ToggleState::Selected - } else { - ToggleState::Unselected - }, - ) - .on_click(window.listener_for( - &log_toolbar_view, - move |view, selection, window, cx| { - let enabled = matches!( - selection, - ToggleState::Selected - ); - view.toggle_rpc_logging_for_server( - server_id, enabled, window, cx, - ); - cx.stop_propagation(); - }, - )), - ), - ) - .into_any_element() - } - }, - window.handler_for(&log_view, move |view, window, cx| { - view.show_rpc_trace_for_server(server_id, window, cx); - }), - ) - }) + ); + view.toggle_rpc_logging_for_server( + server_id, enabled, window, cx, + ); + cx.stop_propagation(); + }, + )), + ), + ) + .into_any_element() + } + }, + window.handler_for(&log_view, move |view, window, cx| { + view.show_rpc_trace_for_server(server_id, window, cx); + }), + ) .entry( SERVER_INFO, None, diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index f0df2a5eac..30e7cbc9d9 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -500,7 +500,7 @@ impl HeadlessProject { }) } - pub async fn handle_open_server_settings( + async fn handle_open_server_settings( this: Entity, _: TypedEnvelope, mut cx: AsyncApp, @@ -553,7 +553,7 @@ impl HeadlessProject { }) } - pub async fn handle_find_search_candidates( + async fn handle_find_search_candidates( this: Entity, envelope: TypedEnvelope, mut cx: AsyncApp, @@ -585,7 +585,7 @@ impl HeadlessProject { Ok(response) } - pub async fn handle_list_remote_directory( + async fn handle_list_remote_directory( this: Entity, envelope: TypedEnvelope, cx: AsyncApp, @@ -617,7 +617,7 @@ impl HeadlessProject { }) } - pub async fn handle_get_path_metadata( + async fn handle_get_path_metadata( this: Entity, envelope: TypedEnvelope, cx: AsyncApp, @@ -635,7 +635,7 @@ impl HeadlessProject { }) } - pub async fn handle_shutdown_remote_server( + async fn handle_shutdown_remote_server( _this: Entity, _envelope: TypedEnvelope, cx: AsyncApp, From e75e30b25fe99a7c0373a257a5429b577a15f204 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Aug 2025 19:14:06 +0300 Subject: [PATCH 11/13] Pass LSP RPC logs up to ssh log store --- Cargo.lock | 1 + crates/language_tools/Cargo.toml | 1 + crates/language_tools/src/language_tools.rs | 5 +- crates/language_tools/src/lsp_log.rs | 183 +++++++++---------- crates/project/src/project.rs | 11 ++ crates/remote_server/src/headless_project.rs | 58 +++++- crates/workspace/src/workspace.rs | 1 + crates/zed/src/main.rs | 4 +- 8 files changed, 156 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c68c2eae3a..7d07e8315d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9211,6 +9211,7 @@ dependencies = [ "gpui", "itertools 0.14.0", "language", + "log", "lsp", "project", "proto", diff --git a/crates/language_tools/Cargo.toml b/crates/language_tools/Cargo.toml index a10b7dc50b..d0ae293a4b 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -22,6 +22,7 @@ futures.workspace = true gpui.workspace = true itertools.workspace = true language.workspace = true +log.workspace = true lsp.workspace = true project.workspace = true proto.workspace = true diff --git a/crates/language_tools/src/language_tools.rs b/crates/language_tools/src/language_tools.rs index 45132b6ace..d6a006f47b 100644 --- a/crates/language_tools/src/language_tools.rs +++ b/crates/language_tools/src/language_tools.rs @@ -6,7 +6,6 @@ mod syntax_tree_view; #[cfg(test)] mod lsp_log_tests; -use client::AnyProtoClient; use gpui::{App, AppContext, Entity}; pub use lsp_log::{LogStore, LspLogToolbarItemView, LspLogView}; @@ -14,8 +13,8 @@ pub use syntax_tree_view::{SyntaxTreeToolbarItemView, SyntaxTreeView}; use ui::{Context, Window}; use workspace::{Item, ItemHandle, SplitDirection, Workspace}; -pub fn init(client: AnyProtoClient, cx: &mut App) { - lsp_log::init(client, true, cx); +pub fn init(cx: &mut App) { + lsp_log::init(true, cx); syntax_tree_view::init(cx); key_context_view::init(cx); } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index ac33a02bcb..67281c1bdb 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1,11 +1,9 @@ -use anyhow::Result; -use client::AnyProtoClient; use collections::{HashMap, VecDeque}; use copilot::Copilot; use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll}; use futures::{StreamExt, channel::mpsc}; use gpui::{ - AnyView, App, AsyncApp, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, Global, + AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, Global, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div, }; use itertools::Itertools; @@ -15,7 +13,6 @@ use lsp::{ MessageType, SetTraceParams, TraceValue, notification::SetTrace, }; use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery}; -use proto::TypedEnvelope; use std::{any::TypeId, borrow::Cow, sync::Arc}; use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*}; use util::ResultExt as _; @@ -115,8 +112,7 @@ impl Message for RpcMessage { type Level = (); } -pub(super) struct LanguageServerState { - project: Option>, +pub struct LanguageServerState { name: Option, worktree_id: Option, kind: LanguageServerKind, @@ -155,7 +151,7 @@ impl LanguageServerKind { } } -struct LanguageServerRpcState { +pub struct LanguageServerRpcState { rpc_messages: VecDeque, last_message_kind: Option, } @@ -232,9 +228,7 @@ pub struct GlobalLogStore(pub WeakEntity); impl Global for GlobalLogStore {} -pub fn init(client: AnyProtoClient, store_logs: bool, cx: &mut App) { - client.add_entity_message_handler(handle_toggle_lsp_logs); - +pub fn init(store_logs: bool, cx: &mut App) { let log_store = cx.new(|cx| LogStore::new(store_logs, cx)); cx.set_global(GlobalLogStore(log_store.downgrade())); @@ -293,7 +287,6 @@ impl LogStore { Some(name), None, Some(server.clone()), - None, cx, ); } @@ -311,9 +304,9 @@ impl LogStore { cx.spawn(async move |this, cx| { while let Some((server_id, io_kind, message)) = io_rx.next().await { - if let Some(this) = this.upgrade() { - this.update(cx, |this, cx| { - this.on_io(server_id, io_kind, &message, cx); + if let Some(log_store) = this.upgrade() { + log_store.update(cx, |log_store, cx| { + log_store.on_io(server_id, io_kind, &message, cx); })?; } } @@ -324,6 +317,11 @@ impl LogStore { } pub fn add_project(&mut self, project: &Entity, cx: &mut Context) { + log::error!( + "?????????????? ssh: {} local: {}", + project.read(cx).is_via_ssh(), + project.read(cx).is_local() + ); let weak_project = project.downgrade(); let subscription_weak_project = weak_project.clone(); self.projects.insert( @@ -336,17 +334,15 @@ impl LogStore { .retain(|_, state| state.kind.project() != Some(&weak_project)); }), 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() { - LanguageServerKind::Remote { - project: project.downgrade(), - } - } else { + let server_kind = if project.read(cx).is_local() { LanguageServerKind::Local { project: project.downgrade(), } + } else { + LanguageServerKind::Remote { + project: project.downgrade(), + } }; - match event { project::Event::LanguageServerAdded(id, name, worktree_id) => { log_store.add_language_server( @@ -359,7 +355,6 @@ impl LogStore { .lsp_store() .read(cx) .language_server_for_id(*id), - Some(subscription_weak_project), cx, ); } @@ -373,7 +368,6 @@ impl LogStore { None, None, None, - Some(subscription_weak_project), cx, ); match typ { @@ -401,24 +395,27 @@ impl LogStore { _ => {} } }), - cx.subscribe_self(move |_, e, cx| match e { - Event::NewServerLogEntry { id, kind, text } => { - subscription_weak_project - .update(cx, |project, cx| { - if let Some((client, project_id)) = - project.lsp_store().read(cx).downstream_client() - { - client - .send(proto::LanguageServerLog { - project_id, - language_server_id: id.to_proto(), - message: text.clone(), - log_type: Some(kind.to_proto()), - }) - .log_err(); - }; - }) - .ok(); + cx.subscribe(&cx.entity(), move |_, _, e, cx| { + log::error!("||??????????????????????????????????????||||||@@@@|| {e:?}"); + match e { + Event::NewServerLogEntry { id, kind, text } => { + subscription_weak_project + .update(cx, |project, cx| { + if let Some((client, project_id)) = + project.lsp_store().read(cx).downstream_client() + { + client + .send(proto::LanguageServerLog { + project_id, + language_server_id: id.to_proto(), + message: text.clone(), + log_type: Some(kind.to_proto()), + }) + .log_err(); + }; + }) + .ok(); + } } }), ], @@ -433,14 +430,13 @@ impl LogStore { self.language_servers.get_mut(&id) } - fn add_language_server( + pub fn add_language_server( &mut self, kind: LanguageServerKind, server_id: LanguageServerId, name: Option, worktree_id: Option, server: Option>, - project: Option>, cx: &mut Context, ) -> Option<&mut LanguageServerState> { let server_state = self.language_servers.entry(server_id).or_insert_with(|| { @@ -449,7 +445,6 @@ impl LogStore { name: None, worktree_id: None, kind, - project, rpc_state: None, log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), @@ -619,6 +614,8 @@ impl LogStore { message: message.trim().to_owned(), }); } + + log::error!("|||||||||| {message}"); cx.emit(Event::NewServerLogEntry { id: language_server_id, kind: LanguageServerLogType::Rpc { @@ -628,7 +625,7 @@ impl LogStore { }); } - fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context) { + pub fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context) { self.language_servers.remove(&id); cx.notify(); } @@ -662,7 +659,7 @@ impl LogStore { }) } - fn enable_rpc_trace_for_language_server( + pub fn enable_rpc_trace_for_language_server( &mut self, server_id: LanguageServerId, ) -> Option<&mut LanguageServerRpcState> { @@ -817,23 +814,6 @@ impl LogStore { } } -async fn handle_toggle_lsp_logs( - lsp_log: Entity, - envelope: TypedEnvelope, - 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 { pub fn new( project: Entity, @@ -875,48 +855,48 @@ impl LspLogView { cx.notify(); }); - let events_subscriptions = cx.subscribe_in( - &log_store, - window, - move |log_view, _, e, window, cx| match e { - Event::NewServerLogEntry { id, kind, text } => { - if log_view.current_server_id == Some(*id) - && LogKind::from_server_log_type(kind) == log_view.active_entry_kind - { - log_view.editor.update(cx, |editor, cx| { - editor.set_read_only(false); - let last_offset = editor.buffer().read(cx).len(cx); - let newest_cursor_is_at_end = - editor.selections.newest::(cx).start >= last_offset; - editor.edit( - vec![ - (last_offset..last_offset, text.as_str()), - (last_offset..last_offset, "\n"), - ], - cx, - ); - if text.len() > 1024 - && let Some((fold_offset, _)) = - text.char_indices().dropping(1024).next() - && fold_offset < text.len() - { - editor.fold_ranges( - vec![last_offset + fold_offset..last_offset + text.len()], - false, - window, + let events_subscriptions = + cx.subscribe_in(&log_store, window, move |log_view, _, e, window, cx| { + log::error!("||||||||@@@@|| {e:?}"); + match e { + Event::NewServerLogEntry { id, kind, text } => { + if log_view.current_server_id == Some(*id) + && LogKind::from_server_log_type(kind) == log_view.active_entry_kind + { + log_view.editor.update(cx, |editor, cx| { + editor.set_read_only(false); + let last_offset = editor.buffer().read(cx).len(cx); + let newest_cursor_is_at_end = + editor.selections.newest::(cx).start >= last_offset; + editor.edit( + vec![ + (last_offset..last_offset, text.as_str()), + (last_offset..last_offset, "\n"), + ], cx, ); - } + if text.len() > 1024 + && let Some((fold_offset, _)) = + text.char_indices().dropping(1024).next() + && fold_offset < text.len() + { + editor.fold_ranges( + vec![last_offset + fold_offset..last_offset + text.len()], + false, + window, + cx, + ); + } - if newest_cursor_is_at_end { - editor.request_autoscroll(Autoscroll::bottom(), cx); - } - editor.set_read_only(true); - }); + if newest_cursor_is_at_end { + editor.request_autoscroll(Autoscroll::bottom(), cx); + } + editor.set_read_only(true); + }); + } } } - }, - ); + }); let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx); let focus_handle = cx.focus_handle(); @@ -1207,7 +1187,7 @@ impl LspLogView { } if let Some(server_state) = log_store.language_servers.get(&server_id) { - if let Some(project) = &server_state.project { + if let LanguageServerKind::Remote { project } = &server_state.kind { project .update(cx, |project, cx| { if let Some((client, project_id)) = @@ -1937,6 +1917,7 @@ impl ServerInfo { } } +#[derive(Debug)] pub enum Event { NewServerLogEntry { id: LanguageServerId, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9fd4eed641..b245e7f8a5 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1355,6 +1355,7 @@ impl Project { ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer); ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.git_store); + ssh_proto.add_entity_message_handler(Self::handle_toggle_lsp_logs); ssh_proto.add_entity_message_handler(Self::handle_create_buffer_for_peer); ssh_proto.add_entity_message_handler(Self::handle_update_worktree); ssh_proto.add_entity_message_handler(Self::handle_update_project); @@ -4629,6 +4630,16 @@ impl Project { })? } + // TODO kb + async fn handle_toggle_lsp_logs( + _this: Entity, + _envelope: TypedEnvelope, + _cx: AsyncApp, + ) -> Result<()> { + log::error!("##########PPPPPPPPPPPPPPPproject##########################"); + Ok(()) + } + async fn handle_update_buffer_from_ssh( this: Entity, envelope: TypedEnvelope, diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 30e7cbc9d9..5156dda246 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -1,5 +1,7 @@ use ::proto::{FromProto, ToProto}; use anyhow::{Context as _, Result, anyhow}; +use language_tools::lsp_log::{GlobalLogStore, LanguageServerKind}; +use lsp::LanguageServerId; use extension::ExtensionHostProxy; use extension_host::headless_host::HeadlessExtensionStore; @@ -65,6 +67,7 @@ impl HeadlessProject { settings::init(cx); language::init(cx); project::Project::init_settings(cx); + language_tools::lsp_log::init(false, cx); } pub fn new( @@ -80,7 +83,6 @@ impl HeadlessProject { ) -> Self { debug_adapter_extension::init(proxy.clone(), cx); languages::init(languages.clone(), node_runtime.clone(), cx); - language_tools::lsp_log::init(session.clone(), false, cx); let worktree_store = cx.new(|cx| { let mut store = WorktreeStore::local(true, fs.clone()); @@ -236,6 +238,7 @@ impl HeadlessProject { session.add_entity_request_handler(Self::handle_open_new_buffer); session.add_entity_request_handler(Self::handle_find_search_candidates); session.add_entity_request_handler(Self::handle_open_server_settings); + session.add_entity_message_handler(Self::handle_toggle_lsp_logs); session.add_entity_request_handler(BufferStore::handle_update_buffer); session.add_entity_message_handler(BufferStore::handle_close_buffer); @@ -299,11 +302,38 @@ impl HeadlessProject { fn on_lsp_store_event( &mut self, - _lsp_store: Entity, + lsp_store: Entity, event: &LspStoreEvent, cx: &mut Context, ) { match event { + LspStoreEvent::LanguageServerAdded(id, name, worktree_id) => { + let log_store = cx + .try_global::() + .and_then(|lsp_logs| lsp_logs.0.upgrade()); + if let Some(log_store) = log_store { + log_store.update(cx, |log_store, cx| { + log_store.add_language_server( + LanguageServerKind::Global, + *id, + Some(name.clone()), + *worktree_id, + lsp_store.read(cx).language_server_for_id(*id), + cx, + ); + }); + } + } + LspStoreEvent::LanguageServerRemoved(id) => { + let log_store = cx + .try_global::() + .and_then(|lsp_logs| lsp_logs.0.upgrade()); + if let Some(log_store) = log_store { + log_store.update(cx, |log_store, cx| { + log_store.remove_language_server(*id, cx); + }); + } + } LspStoreEvent::LanguageServerUpdate { language_server_id, name, @@ -500,6 +530,30 @@ impl HeadlessProject { }) } + async fn handle_toggle_lsp_logs( + _: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result<()> { + let server_id = LanguageServerId::from_proto(envelope.payload.server_id); + let lsp_logs = cx + .update(|cx| { + cx.try_global::() + .and_then(|lsp_logs| lsp_logs.0.upgrade()) + })? + .context("lsp logs store is missing")?; + + lsp_logs.update(&mut cx, |lsp_logs, _| { + // we do not support any other log toggling yet + if envelope.payload.enabled { + lsp_logs.enable_rpc_trace_for_language_server(server_id); + } else { + lsp_logs.disable_rpc_trace_for_language_server(server_id); + } + })?; + Ok(()) + } + async fn handle_open_server_settings( this: Entity, _: TypedEnvelope, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9d45c8bef1..943984cf07 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -7425,6 +7425,7 @@ pub fn open_ssh_project_with_new_connection( cx, ) })?; + // TODO kb register here instead? open_ssh_project_inner( project, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6cacf0fcbd..e99c8b564b 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -5,7 +5,7 @@ use agent_ui::AgentPanel; use anyhow::{Context as _, Result}; use clap::{Parser, command}; use cli::FORCE_CLI_MODE_ENV_VAR_NAME; -use client::{AnyProtoClient, Client, ProxySettings, UserStore, parse_zed_link}; +use client::{Client, ProxySettings, UserStore, parse_zed_link}; use collab_ui::channel_view::ChannelView; use collections::HashMap; use crashes::InitCrashHandler; @@ -621,7 +621,7 @@ pub fn main() { toolchain_selector::init(cx); theme_selector::init(cx); settings_profile_selector::init(cx); - language_tools::init(AnyProtoClient::new(app_state.client.clone()), cx); + language_tools::init(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); collab_ui::init(&app_state, cx); From 679c24282d399ecf7483f60dfd65ace4dfffec34 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Aug 2025 00:06:11 +0300 Subject: [PATCH 12/13] Simplify --- crates/language_tools/src/lsp_log.rs | 75 +++++++++++----------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 67281c1bdb..054ae44317 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -38,7 +38,7 @@ pub struct LogStore { } struct ProjectState { - _subscriptions: [gpui::Subscription; 3], + _subscriptions: [gpui::Subscription; 2], } trait Message: AsRef { @@ -259,13 +259,13 @@ impl LogStore { let copilot_subscription = Copilot::global(cx).map(|copilot| { let copilot = &copilot; - cx.subscribe(copilot, |this, copilot, edit_prediction_event, cx| { + cx.subscribe(copilot, |log_store, copilot, edit_prediction_event, cx| { if let copilot::Event::CopilotLanguageServerStarted = edit_prediction_event && let Some(server) = copilot.read(cx).language_server() { let server_id = server.server_id(); let weak_this = cx.weak_entity(); - this.copilot_log_subscription = + log_store.copilot_log_subscription = Some(server.on_notification::( move |params, cx| { weak_this @@ -280,8 +280,9 @@ impl LogStore { .ok(); }, )); + let name = LanguageServerName::new_static("copilot"); - this.add_language_server( + log_store.add_language_server( LanguageServerKind::Global, server.server_id(), Some(name), @@ -293,7 +294,7 @@ impl LogStore { }) }); - let this = Self { + let log_store = Self { copilot_log_subscription: None, _copilot_subscription: copilot_subscription, projects: HashMap::default(), @@ -302,9 +303,9 @@ impl LogStore { io_tx, }; - cx.spawn(async move |this, cx| { + cx.spawn(async move |log_store, cx| { while let Some((server_id, io_kind, message)) = io_rx.next().await { - if let Some(log_store) = this.upgrade() { + if let Some(log_store) = log_store.upgrade() { log_store.update(cx, |log_store, cx| { log_store.on_io(server_id, io_kind, &message, cx); })?; @@ -313,17 +314,11 @@ impl LogStore { anyhow::Ok(()) }) .detach_and_log_err(cx); - this + log_store } pub fn add_project(&mut self, project: &Entity, cx: &mut Context) { - log::error!( - "?????????????? ssh: {} local: {}", - project.read(cx).is_via_ssh(), - project.read(cx).is_local() - ); let weak_project = project.downgrade(); - let subscription_weak_project = weak_project.clone(); self.projects.insert( project.downgrade(), ProjectState { @@ -395,29 +390,6 @@ impl LogStore { _ => {} } }), - cx.subscribe(&cx.entity(), move |_, _, e, cx| { - log::error!("||??????????????????????????????????????||||||@@@@|| {e:?}"); - match e { - Event::NewServerLogEntry { id, kind, text } => { - subscription_weak_project - .update(cx, |project, cx| { - if let Some((client, project_id)) = - project.lsp_store().read(cx).downstream_client() - { - client - .send(proto::LanguageServerLog { - project_id, - language_server_id: id.to_proto(), - message: text.clone(), - log_type: Some(kind.to_proto()), - }) - .log_err(); - }; - }) - .ok(); - } - } - }), ], }, ); @@ -855,11 +827,28 @@ impl LspLogView { cx.notify(); }); + + let weak_lsp_store = project.read(cx).lsp_store().downgrade(); let events_subscriptions = cx.subscribe_in(&log_store, window, move |log_view, _, e, window, cx| { log::error!("||||||||@@@@|| {e:?}"); match e { Event::NewServerLogEntry { id, kind, text } => { + weak_lsp_store + .update(cx, |lsp_store, _| { + if let Some((client, project_id)) = lsp_store.downstream_client() { + client + .send(proto::LanguageServerLog { + project_id, + language_server_id: id.to_proto(), + message: text.clone(), + log_type: Some(kind.to_proto()), + }) + .log_err(); + }; + }) + .ok(); + if log_view.current_server_id == Some(*id) && LogKind::from_server_log_type(kind) == log_view.active_entry_kind { @@ -904,7 +893,7 @@ impl LspLogView { window.focus(&log_view.editor.focus_handle(cx)); }); - let mut this = Self { + let mut lsp_log_view = Self { focus_handle, editor, editor_subscriptions, @@ -919,9 +908,9 @@ impl LspLogView { ], }; if let Some(server_id) = server_id { - this.show_logs_for_server(server_id, window, cx); + lsp_log_view.show_logs_for_server(server_id, window, cx); } - this + lsp_log_view } fn editor_for_logs( @@ -1849,12 +1838,6 @@ const SERVER_LOGS: &str = "Server Logs"; const SERVER_TRACE: &str = "Server Trace"; const SERVER_INFO: &str = "Server Info"; -impl Default for LspLogToolbarItemView { - fn default() -> Self { - Self::new() - } -} - impl LspLogToolbarItemView { pub fn new() -> Self { Self { From 03ba256d965ceee031d3e2221286b77ce4b91c27 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 26 Aug 2025 10:39:57 +0300 Subject: [PATCH 13/13] Fix ssh message sending --- crates/language_tools/src/lsp_log.rs | 158 ++++++++++++------- crates/remote_server/src/headless_project.rs | 4 +- 2 files changed, 102 insertions(+), 60 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 054ae44317..5a52a103a6 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -12,7 +12,9 @@ use lsp::{ IoKind, LanguageServer, LanguageServerBinary, LanguageServerName, LanguageServerSelector, MessageType, SetTraceParams, TraceValue, notification::SetTrace, }; -use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery}; +use project::{ + LspStore, Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery, +}; use std::{any::TypeId, borrow::Cow, sync::Arc}; use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*}; use util::ResultExt as _; @@ -128,6 +130,7 @@ pub struct LanguageServerState { pub enum LanguageServerKind { Local { project: WeakEntity }, Remote { project: WeakEntity }, + LocalSsh { lsp_store: WeakEntity }, Global, } @@ -136,6 +139,7 @@ impl std::fmt::Debug for LanguageServerKind { match self { LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"), LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"), + LanguageServerKind::LocalSsh { .. } => write!(f, "LanguageServerKind::LocalSsh"), LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"), } } @@ -146,6 +150,7 @@ impl LanguageServerKind { match self { Self::Local { project } => Some(project), Self::Remote { project } => Some(project), + Self::LocalSsh { .. } => None, Self::Global { .. } => None, } } @@ -264,13 +269,13 @@ impl LogStore { && let Some(server) = copilot.read(cx).language_server() { let server_id = server.server_id(); - let weak_this = cx.weak_entity(); + let weak_lsp_store = cx.weak_entity(); log_store.copilot_log_subscription = Some(server.on_notification::( move |params, cx| { - weak_this - .update(cx, |this, cx| { - this.add_language_server_log( + weak_lsp_store + .update(cx, |lsp_store, cx| { + lsp_store.add_language_server_log( server_id, MessageType::LOG, ¶ms.message, @@ -460,21 +465,27 @@ impl LogStore { let message = message.trim_end().to_string(); if !store_logs { // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway - cx.emit(Event::NewServerLogEntry { - id, - kind: LanguageServerLogType::Log(typ), - text: message, - }); + self.emit_event( + Event::NewServerLogEntry { + id, + kind: LanguageServerLogType::Log(typ), + text: message, + }, + cx, + ); } else if let Some(new_message) = Self::push_new_message( log_lines, LogMessage { message, typ }, language_server_state.log_level, ) { - cx.emit(Event::NewServerLogEntry { - id, - kind: LanguageServerLogType::Log(typ), - text: new_message, - }); + self.emit_event( + Event::NewServerLogEntry { + id, + kind: LanguageServerLogType::Log(typ), + text: new_message, + }, + cx, + ); } Some(()) } @@ -492,11 +503,14 @@ impl LogStore { let log_lines = &mut language_server_state.trace_messages; if !store_logs { // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway - cx.emit(Event::NewServerLogEntry { - id, - kind: LanguageServerLogType::Trace { verbose_info }, - text: message.trim().to_string(), - }); + self.emit_event( + Event::NewServerLogEntry { + id, + kind: LanguageServerLogType::Trace { verbose_info }, + text: message.trim().to_string(), + }, + cx, + ); } else if let Some(new_message) = Self::push_new_message( log_lines, TraceMessage { @@ -515,11 +529,14 @@ impl LogStore { TraceValue::Verbose, ); } - cx.emit(Event::NewServerLogEntry { - id, - kind: LanguageServerLogType::Trace { verbose_info }, - text: new_message, - }); + self.emit_event( + Event::NewServerLogEntry { + id, + kind: LanguageServerLogType::Trace { verbose_info }, + text: new_message, + }, + cx, + ); } Some(()) } @@ -544,7 +561,7 @@ impl LogStore { language_server_id: LanguageServerId, kind: MessageKind, message: &str, - cx: &mut Context<'_, LogStore>, + cx: &mut Context<'_, Self>, ) { let store_logs = self.store_logs; let Some(state) = self @@ -554,6 +571,7 @@ impl LogStore { return; }; + let mut line_before_message_to_send = None; let rpc_log_lines = &mut state.rpc_messages; if state.last_message_kind != Some(kind) { while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { @@ -568,13 +586,7 @@ impl LogStore { message: line_before_message.to_string(), }); } - cx.emit(Event::NewServerLogEntry { - id: language_server_id, - kind: LanguageServerLogType::Rpc { - received: kind == MessageKind::Receive, - }, - text: line_before_message.to_string(), - }); + line_before_message_to_send = Some(line_before_message); } while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { @@ -587,14 +599,25 @@ impl LogStore { }); } - log::error!("|||||||||| {message}"); - cx.emit(Event::NewServerLogEntry { - id: language_server_id, - kind: LanguageServerLogType::Rpc { - received: kind == MessageKind::Receive, + let received = kind == MessageKind::Receive; + if let Some(line_before_message) = line_before_message_to_send { + self.emit_event( + Event::NewServerLogEntry { + id: language_server_id, + kind: LanguageServerLogType::Rpc { received }, + text: line_before_message.to_string(), + }, + cx, + ); + } + self.emit_event( + Event::NewServerLogEntry { + id: language_server_id, + kind: LanguageServerLogType::Rpc { received }, + text: message.to_owned(), }, - text: message.to_owned(), - }); + cx, + ); } pub fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context) { @@ -627,7 +650,7 @@ impl LogStore { None } } - LanguageServerKind::Global => Some(*id), + LanguageServerKind::Global | LanguageServerKind::LocalSsh { .. } => Some(*id), }) } @@ -784,6 +807,37 @@ impl LogStore { cx.notify(); Some(()) } + + fn emit_event(&mut self, e: Event, cx: &mut Context) { + match &e { + Event::NewServerLogEntry { id, kind, text } => { + if let Some(state) = self.get_language_server_state(*id) { + let downstream_client = match &state.kind { + LanguageServerKind::Remote { project } + | LanguageServerKind::Local { project } => project + .upgrade() + .map(|project| project.read(cx).lsp_store()), + LanguageServerKind::LocalSsh { lsp_store } => lsp_store.upgrade(), + LanguageServerKind::Global => None, + } + .and_then(|lsp_store| lsp_store.read(cx).downstream_client()); + if let Some((client, project_id)) = downstream_client { + log::error!("|||||||||| {text}"); + client + .send(proto::LanguageServerLog { + project_id, + language_server_id: id.to_proto(), + message: text.clone(), + log_type: Some(kind.to_proto()), + }) + .ok(); + } + } + } + } + + cx.emit(e); + } } impl LspLogView { @@ -828,27 +882,11 @@ impl LspLogView { cx.notify(); }); - let weak_lsp_store = project.read(cx).lsp_store().downgrade(); let events_subscriptions = cx.subscribe_in(&log_store, window, move |log_view, _, e, window, cx| { - log::error!("||||||||@@@@|| {e:?}"); + log::error!("@@@@@@ {e:?}"); match e { Event::NewServerLogEntry { id, kind, text } => { - weak_lsp_store - .update(cx, |lsp_store, _| { - if let Some((client, project_id)) = lsp_store.downstream_client() { - client - .send(proto::LanguageServerLog { - project_id, - language_server_id: id.to_proto(), - message: text.clone(), - log_type: Some(kind.to_proto()), - }) - .log_err(); - }; - }) - .ok(); - if log_view.current_server_id == Some(*id) && LogKind::from_server_log_type(kind) == log_view.active_entry_kind { @@ -983,7 +1021,9 @@ impl LspLogView { .language_servers .iter() .map(|(server_id, state)| match &state.kind { - LanguageServerKind::Local { .. } | LanguageServerKind::Remote { .. } => { + LanguageServerKind::Local { .. } + | LanguageServerKind::Remote { .. } + | LanguageServerKind::LocalSsh { .. } => { let worktree_root_name = state .worktree_id .and_then(|id| self.project.read(cx).worktree_for_id(id, cx)) diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 5156dda246..71eeaede15 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -314,7 +314,9 @@ impl HeadlessProject { if let Some(log_store) = log_store { log_store.update(cx, |log_store, cx| { log_store.add_language_server( - LanguageServerKind::Global, + LanguageServerKind::LocalSsh { + lsp_store: self.lsp_store.downgrade(), + }, *id, Some(name.clone()), *worktree_id,