diff --git a/Cargo.lock b/Cargo.lock index 4325addc392214614a6654563d88041331f2ded9..8088efd6ea5517ac9cc2cf9bc3224176a09b9e60 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/assistant_slash_command/Cargo.toml b/crates/assistant_slash_command/Cargo.toml index f7b7af9b879492cbb48f4e88d8379b45cbc2d053..e33c2cda1abb3b37f5a3f10e9a76d4c3fcdd6808 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 c95695052a4778209010b2f9e7a4a57be4cb6cf7..951226adfd685b5ae19201ed9531f5a3c8d74e0b 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 c25cfc3c86f26a72b3af37246ab30a175a68969a..46f43d163068ae4b8018080c7873c5ec4db30131 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/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 43581fd9421e5a8d10460a9ed15c565bd66a6e5e..b2e25458ef98b295b4d056a7f59521f4fa896f1a 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -175,6 +175,7 @@ CREATE TABLE "language_servers" ( "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE, "name" VARCHAR NOT NULL, "capabilities" TEXT NOT NULL, + "worktree_id" BIGINT, PRIMARY KEY (project_id, id) ); diff --git a/crates/collab/migrations/20250827084812_worktree_in_servers.sql b/crates/collab/migrations/20250827084812_worktree_in_servers.sql new file mode 100644 index 0000000000000000000000000000000000000000..d4c6ffbbcccb2d2f23654cfc287b45bb8ea20508 --- /dev/null +++ b/crates/collab/migrations/20250827084812_worktree_in_servers.sql @@ -0,0 +1,2 @@ +ALTER TABLE language_servers + ADD COLUMN worktree_id BIGINT; diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 393f2c80f8e733aa2d2b3b5f4b811c9868e0a620..a3f0ea6cbc6e762e365f82e74b886234e62da109 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -694,6 +694,7 @@ impl Database { project_id: ActiveValue::set(project_id), id: ActiveValue::set(server.id as i64), name: ActiveValue::set(server.name.clone()), + worktree_id: ActiveValue::set(server.worktree_id.map(|id| id as i64)), capabilities: ActiveValue::set(update.capabilities.clone()), }) .on_conflict( @@ -704,6 +705,7 @@ impl Database { .update_columns([ language_server::Column::Name, language_server::Column::Capabilities, + language_server::Column::WorktreeId, ]) .to_owned(), ) @@ -1065,7 +1067,7 @@ impl Database { server: proto::LanguageServer { id: language_server.id as u64, name: language_server.name, - worktree_id: None, + worktree_id: language_server.worktree_id.map(|id| id as u64), }, capabilities: language_server.capabilities, }) diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 9e7cabf9b29c91d7e486f42d5e6b12020b0f514e..0713ac2cb2810797b319b53583bc8c0e1756fe68 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -809,7 +809,7 @@ impl Database { server: proto::LanguageServer { id: language_server.id as u64, name: language_server.name, - worktree_id: None, + worktree_id: language_server.worktree_id.map(|id| id as u64), }, capabilities: language_server.capabilities, }) diff --git a/crates/collab/src/db/tables/language_server.rs b/crates/collab/src/db/tables/language_server.rs index 34c7514d917b313990521acf8542c31394d009fc..705aae292ba456622e9808f033a348f60c3835a4 100644 --- a/crates/collab/src/db/tables/language_server.rs +++ b/crates/collab/src/db/tables/language_server.rs @@ -10,6 +10,7 @@ pub struct Model { pub id: i64, pub name: String, pub capabilities: String, + pub worktree_id: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 73f327166a3f1fb40a1f232ea2fabcdedd3fb129..9e4dfd4854b4de67de522bfbbd1160fe880a05cb 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -476,7 +476,9 @@ 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::) + .add_message_handler(broadcast_project_message_from_host::); Arc::new(server) } diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 0fc119f31125f4ef3925799fd98fd47cac7ca9da..470d1989583d9eb1ed06159a28686ddf2620156e 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 339f98ae8bd88263f1fea12c535569864faae294..b7051c9b19ab748390c109b5c6cd449cb0f4886e 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 5aa914311a6eccc1cb68efa37e878ad12249d6fd..a10b7dc50b9838d6da1afb9838758b4dd4582563 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -24,13 +24,14 @@ 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 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/language_tools/src/language_tools.rs b/crates/language_tools/src/language_tools.rs index cbf5756875f723b52fabbfe877c32265dd6f0aef..d6a006f47bc921f00c15edc8b45d3efe39364acc 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; @@ -14,7 +14,7 @@ use ui::{Context, Window}; use workspace::{Item, ItemHandle, SplitDirection, Workspace}; pub fn init(cx: &mut App) { - lsp_log::init(cx); + 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 a71e434e5274392add0463830519834202b7ba58..d55b54a6d253214cd81bf0226278863c4d59c834 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -9,12 +9,16 @@ 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::{ + LspStore, Project, ProjectItem, WorktreeId, lsp_store::LanguageServerLogType, + search::SearchQuery, }; -use project::{Project, WorktreeId, 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}, @@ -28,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, @@ -46,6 +51,7 @@ trait Message: AsRef { } } +#[derive(Debug)] pub(super) struct LogMessage { message: String, typ: MessageType, @@ -73,8 +79,10 @@ impl Message for LogMessage { } } +#[derive(Debug)] pub(super) struct TraceMessage { message: String, + is_verbose: bool, } impl AsRef for TraceMessage { @@ -84,9 +92,18 @@ 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, + } + } } +#[derive(Debug)] struct RpcMessage { message: String, } @@ -101,7 +118,7 @@ impl Message for RpcMessage { type Level = (); } -pub(super) struct LanguageServerState { +pub struct LanguageServerState { name: Option, worktree_id: Option, kind: LanguageServerKind, @@ -113,24 +130,35 @@ pub(super) struct LanguageServerState { io_logs_subscription: Option, } +impl std::fmt::Debug for LanguageServerState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LanguageServerState") + .field("name", &self.name) + .field("worktree_id", &self.worktree_id) + .field("kind", &self.kind) + .field("log_messages", &self.log_messages) + .field("trace_messages", &self.trace_messages) + .field("rpc_state", &self.rpc_state) + .field("trace_level", &self.trace_level) + .field("log_level", &self.log_level) + .finish_non_exhaustive() + } +} + #[derive(PartialEq, Clone)] pub enum LanguageServerKind { Local { project: WeakEntity }, Remote { project: WeakEntity }, + LocalSsh { lsp_store: WeakEntity }, 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 { LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"), LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"), + LanguageServerKind::LocalSsh { .. } => write!(f, "LanguageServerKind::LocalSsh"), LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"), } } @@ -141,12 +169,14 @@ impl LanguageServerKind { match self { Self::Local { project } => Some(project), Self::Remote { project } => Some(project), + Self::LocalSsh { .. } => None, Self::Global { .. } => None, } } } -struct LanguageServerRpcState { +#[derive(Debug)] +pub struct LanguageServerRpcState { rpc_messages: VecDeque, last_message_kind: Option, } @@ -167,7 +197,7 @@ pub struct LspLogToolbarItemView { _log_view_subscription: Option, } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] enum MessageKind { Send, Receive, @@ -183,6 +213,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,59 +249,53 @@ actions!( ] ); -pub(super) struct GlobalLogStore(pub WeakEntity); +pub struct GlobalLogStore(pub WeakEntity); impl Global for GlobalLogStore {} -pub fn init(cx: &mut App) { - let log_store = cx.new(LogStore::new); +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())); cx.observe_new(move |workspace: &mut Workspace, _, cx| { - let project = workspace.project(); - if project.read(cx).is_local() || project.read(cx).is_via_remote_server() { - 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_remote_server() { - 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(); } 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| { 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 = + 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, @@ -274,8 +305,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), @@ -287,26 +319,27 @@ impl LogStore { }) }); - let this = Self { + let log_store = Self { copilot_log_subscription: None, _copilot_subscription: copilot_subscription, projects: HashMap::default(), language_servers: HashMap::default(), + store_logs, 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(this) = this.upgrade() { - this.update(cx, |this, cx| { - this.on_io(server_id, io_kind, &message, cx); + 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); })?; } } anyhow::Ok(()) }) .detach_and_log_err(cx); - this + log_store } pub fn add_project(&mut self, project: &Entity, cx: &mut Context) { @@ -320,20 +353,19 @@ impl LogStore { this.language_servers .retain(|_, state| state.kind.project() != Some(&weak_project)); }), - cx.subscribe(project, |this, project, event, cx| { - let server_kind = if project.read(cx).is_via_remote_server() { - LanguageServerKind::Remote { + cx.subscribe(project, move |log_store, project, event, cx| { + let server_kind = if project.read(cx).is_local() { + LanguageServerKind::Local { project: project.downgrade(), } } else { - LanguageServerKind::Local { + LanguageServerKind::Remote { project: project.downgrade(), } }; - match event { project::Event::LanguageServerAdded(id, name, worktree_id) => { - this.add_language_server( + log_store.add_language_server( server_kind, *id, Some(name.clone()), @@ -346,20 +378,78 @@ impl LogStore { cx, ); } + project::Event::LanguageServerBufferRegistered { + server_id, + buffer_id, + name, + .. + } if project.read(cx).is_via_collab() => { + let worktree_id = project + .read(cx) + .buffer_for_id(*buffer_id, cx) + .and_then(|buffer| { + Some(buffer.read(cx).project_path(cx)?.worktree_id) + }); + let name = name.clone().or_else(|| { + project + .read(cx) + .lsp_store() + .read(cx) + .language_server_statuses + .get(server_id) + .map(|status| status.name.clone()) + }); + log_store.add_language_server( + server_kind, + *server_id, + name, + worktree_id, + None, + 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, + 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(_) => { - this.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 { + MessageKind::Receive + } else { + MessageKind::Send + }; + log_store.add_language_server_rpc(*id, kind, message, cx); } } } + project::Event::ToggleLspLogs { server_id, enabled } => { + // we do not support any other log toggling yet + if *enabled { + log_store.enable_rpc_trace_for_language_server(*server_id); + } else { + log_store.disable_rpc_trace_for_language_server(*server_id); + } + } _ => {} } }), @@ -375,7 +465,7 @@ impl LogStore { self.language_servers.get_mut(&id) } - fn add_language_server( + pub fn add_language_server( &mut self, kind: LanguageServerKind, server_id: LanguageServerId, @@ -426,20 +516,35 @@ 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; - Self::add_language_server_message( + 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 + 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, - id, - LogMessage { - message: message.trim_end().to_string(), - typ, - }, + LogMessage { message, typ }, language_server_state.log_level, - LogKind::Logs, - cx, - ); + ) { + self.emit_event( + Event::NewServerLogEntry { + id, + kind: LanguageServerLogType::Log(typ), + text: new_message, + }, + cx, + ); + } Some(()) } @@ -447,46 +552,127 @@ impl LogStore { &mut self, id: LanguageServerId, message: &str, + verbose_info: Option, 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; - Self::add_language_server_message( + if !store_logs { + // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway + 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, - id, TraceMessage { message: message.trim().to_string(), + is_verbose: false, }, - (), - LogKind::Trace, - cx, - ); + 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, + ); + } + self.emit_event( + Event::NewServerLogEntry { + id, + kind: LanguageServerLogType::Trace { verbose_info }, + text: new_message, + }, + cx, + ); + } 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); + + let visible_message = visible.then(|| message.as_ref().to_string()); log_lines.push_back(message); + visible_message + } - if visible { - cx.emit(Event::NewServerLogEntry { id, kind, text }); - cx.notify(); + fn add_language_server_rpc( + &mut self, + language_server_id: LanguageServerId, + kind: MessageKind, + message: &str, + cx: &mut Context<'_, Self>, + ) { + 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()) + else { + return; + }; + + let received = kind == MessageKind::Receive; + 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, + }; + if store_logs { + rpc_log_lines.push_back(RpcMessage { + message: line_before_message.to_string(), + }); + } + // Do not send a synthetic message over the wire, it will be derived from the actual RPC message + cx.emit(Event::NewServerLogEntry { + id: language_server_id, + kind: LanguageServerLogType::Rpc { received }, + text: line_before_message.to_string(), + }); + } + + while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { + rpc_log_lines.pop_front(); + } + + if store_logs { + rpc_log_lines.push_back(RpcMessage { + message: message.trim().to_owned(), + }); } + + self.emit_event( + Event::NewServerLogEntry { + id: language_server_id, + kind: LanguageServerLogType::Rpc { received }, + text: message.to_owned(), + }, + cx, + ); } - 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(); } @@ -516,11 +702,11 @@ impl LogStore { None } } - LanguageServerKind::Global => Some(*id), + LanguageServerKind::Global | LanguageServerKind::LocalSsh { .. } => Some(*id), }) } - fn enable_rpc_trace_for_language_server( + pub fn enable_rpc_trace_for_language_server( &mut self, server_id: LanguageServerId, ) -> Option<&mut LanguageServerRpcState> { @@ -663,50 +849,45 @@ 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(), - }); - } + self.add_language_server_rpc(language_server_id, kind, message, cx); + cx.notify(); + Some(()) + } - while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { - rpc_log_lines.pop_front(); + 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 { + client + .send(proto::LanguageServerLog { + project_id, + language_server_id: id.to_proto(), + message: text.clone(), + log_type: Some(kind.to_proto()), + }) + .ok(); + } + } + } } - let message = message.trim(); - rpc_log_lines.push_back(RpcMessage { - message: message.to_string(), - }); - cx.emit(Event::NewServerLogEntry { - id: language_server_id, - kind: LogKind::Rpc, - text: message.to_string(), - }); - cx.notify(); - Some(()) + cx.emit(e); } } @@ -751,13 +932,14 @@ 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) - && *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); @@ -800,7 +982,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, @@ -815,9 +997,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( @@ -838,7 +1020,7 @@ impl LspLogView { } fn editor_for_server_info( - server: &LanguageServer, + info: ServerInfo, window: &mut Window, cx: &mut Context, ) -> (Entity, Vec) { @@ -853,22 +1035,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( @@ -891,7 +1072,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)) @@ -1003,11 +1186,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; @@ -1025,6 +1214,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) @@ -1069,12 +1259,33 @@ 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) { + if let LanguageServerKind::Remote { project } = &server_state.kind { + 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 { self.show_logs_for_server(server_id, window, cx); @@ -1113,13 +1324,38 @@ impl LspLogView { window: &mut Window, cx: &mut Context, ) { - 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(); @@ -1416,7 +1652,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") @@ -1438,55 +1673,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, @@ -1696,12 +1929,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 { @@ -1734,10 +1961,41 @@ 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()), + } + } +} + +#[derive(Debug)] pub enum Event { NewServerLogEntry { id: LanguageServerId, - kind: LogKind, + kind: LanguageServerLogType, text: String, }, } diff --git a/crates/language_tools/src/lsp_log_tests.rs b/crates/language_tools/src/lsp_log_tests.rs index ad2b653fdcfd4dc228cac58da7ed15f844b4bb26..a7dbaa2a601bc6ebd60685635cd0802750452053 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/language_tools/src/lsp_tool.rs b/crates/language_tools/src/lsp_tool.rs index dd3e80212fda08f43718a664d2cfd6d377182273..2d6a99a0bc2f0d2ed498aaada204574740293343 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,11 @@ 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 is_remote = self + .lsp_store + .update(cx, |lsp_store, _| lsp_store.as_remote().is_some()) + .unwrap_or(false); + let has_logs = is_remote || lsp_logs.read(cx).has_server_logs(&server_selector); let status_color = server_info .binary_status @@ -241,10 +241,10 @@ impl LanguageServerState { .as_ref() .or_else(|| server_info.binary_status.as_ref()?.message.as_ref()) .cloned(); - let hover_label = if has_logs { - Some("View Logs") - } else if message.is_some() { + let hover_label = if message.is_some() { Some("View Message") + } else if has_logs { + Some("View Logs") } else { None }; @@ -288,16 +288,7 @@ impl LanguageServerState { let server_name = server_info.name.clone(); let workspace = self.workspace.clone(); move |window, cx| { - if has_logs { - lsp_logs.update(cx, |lsp_logs, cx| { - lsp_logs.open_server_trace( - workspace.clone(), - server_selector.clone(), - window, - cx, - ); - }); - } else if let Some(message) = &message { + if let Some(message) = &message { let Some(create_buffer) = workspace .update(cx, |workspace, cx| { workspace @@ -347,6 +338,15 @@ impl LanguageServerState { anyhow::Ok(()) }) .detach(); + } else if has_logs { + lsp_logs.update(cx, |lsp_logs, cx| { + lsp_logs.open_server_trace( + workspace.clone(), + server_selector.clone(), + window, + cx, + ); + }); } else { cx.propagate(); } @@ -529,26 +529,48 @@ impl LspTool { }); let lsp_store = workspace.project().read(cx).lsp_store(); + let mut language_servers = LanguageServers::default(); + for (_, status) in lsp_store.read(cx).language_server_statuses() { + language_servers.binary_statuses.insert( + status.name.clone(), + LanguageServerBinaryStatus { + status: BinaryStatus::None, + message: None, + }, + ); + } + let lsp_store_subscription = cx.subscribe_in(&lsp_store, window, |lsp_tool, _, e, window, cx| { lsp_tool.on_lsp_store_event(e, window, cx) }); - let state = cx.new(|_| LanguageServerState { + let server_state = cx.new(|_| LanguageServerState { workspace: workspace.weak_handle(), items: Vec::new(), lsp_store: lsp_store.downgrade(), active_editor: None, - language_servers: LanguageServers::default(), + language_servers, }); - Self { - server_state: state, + let mut lsp_tool = Self { + server_state, popover_menu_handle, lsp_menu: None, lsp_menu_refresh: Task::ready(()), _subscriptions: vec![settings_subscription, lsp_store_subscription], + }; + if !lsp_tool + .server_state + .read(cx) + .language_servers + .binary_statuses + .is_empty() + { + lsp_tool.refresh_lsp_menu(true, window, cx); } + + lsp_tool } fn on_lsp_store_event( @@ -708,6 +730,25 @@ impl LspTool { } } } + state + .lsp_store + .update(cx, |lsp_store, cx| { + for (server_id, status) in lsp_store.language_server_statuses() { + if let Some(worktree) = status.worktree.and_then(|worktree_id| { + lsp_store + .worktree_store() + .read(cx) + .worktree_for_id(worktree_id, cx) + }) { + server_ids_to_worktrees.insert(server_id, worktree.clone()); + server_names_to_worktrees + .entry(status.name.clone()) + .or_default() + .insert((worktree, server_id)); + } + } + }) + .ok(); let mut servers_per_worktree = BTreeMap::>::new(); let mut servers_without_worktree = Vec::::new(); diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index ad9d0abf405b18f9048030621e960251057588de..2bc95bf81d85ca6e09a246408a4ef8bfa28b91e3 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -977,7 +977,9 @@ impl LocalLspStore { this.update(&mut cx, |_, cx| { cx.emit(LspStoreEvent::LanguageServerLog( server_id, - LanguageServerLogType::Trace(params.verbose), + LanguageServerLogType::Trace { + verbose_info: params.verbose, + }, params.message, )); }) @@ -3482,13 +3484,13 @@ pub struct LspStore { buffer_store: Entity, 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>)>, @@ -3565,6 +3567,7 @@ pub struct LanguageServerStatus { pub pending_work: BTreeMap, pub has_pending_diagnostic_updates: bool, progress_tokens: HashSet, + pub worktree: Option, } #[derive(Clone, Debug)] @@ -7483,7 +7486,7 @@ impl LspStore { server: Some(proto::LanguageServer { id: server_id.to_proto(), name: status.name.to_string(), - worktree_id: None, + worktree_id: status.worktree.map(|id| id.to_proto()), }), capabilities: serde_json::to_string(&server.capabilities()) .expect("serializing server LSP capabilities"), @@ -7527,6 +7530,7 @@ impl LspStore { pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), + worktree: server.worktree_id.map(WorktreeId::from_proto), }, ) }) @@ -8892,6 +8896,7 @@ impl LspStore { pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), + worktree: server.worktree_id.map(WorktreeId::from_proto), }, ); cx.emit(LspStoreEvent::LanguageServerAdded( @@ -10905,6 +10910,7 @@ impl LspStore { pending_work: Default::default(), has_pending_diagnostic_updates: false, progress_tokens: Default::default(), + worktree: Some(key.worktree_id), }, ); @@ -12190,6 +12196,14 @@ impl LspStore { let data = self.lsp_code_lens.get_mut(&buffer_id)?; Some(data.update.take()?.1) } + + pub fn downstream_client(&self) -> Option<(AnyProtoClient, u64)> { + self.downstream_client.clone() + } + + pub fn worktree_store(&self) -> Entity { + self.worktree_store.clone() + } } // Registration with registerOptions as null, should fallback to true. @@ -12699,45 +12713,69 @@ impl PartialEq for LanguageServerPromptRequest { #[derive(Clone, Debug, PartialEq)] pub enum LanguageServerLogType { Log(MessageType), - Trace(Option), + Trace { verbose_info: Option }, + 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) + proto::language_server_log::LogType::Log(proto::LogMessage { + level: level as i32, + }) } - Self::Trace(message) => { - proto::language_server_log::LogType::LogTrace(proto::LspLogTrace { - message: message.clone(), + Self::Trace { verbose_info } => { + proto::language_server_log::LogType::Trace(proto::TraceMessage { + verbose_info: verbose_info.to_owned(), }) } + Self::Rpc { received } => { + let kind = if *received { + proto::rpc_message::Kind::Received + } else { + proto::rpc_message::Kind::Sent + }; + let kind = kind as i32; + proto::language_server_log::LogType::Rpc(proto::RpcMessage { kind }) + } } } pub fn from_proto(log_type: proto::language_server_log::LogType) -> Self { + use proto::log_message::LogLevel; + use proto::rpc_message; match log_type { - proto::language_server_log::LogType::LogMessageType(message_type) => { - Self::Log(match message_type { - 1 => MessageType::ERROR, - 2 => MessageType::WARNING, - 3 => MessageType::INFO, - 4 => MessageType::LOG, - _ => MessageType::LOG, - }) - } - proto::language_server_log::LogType::LogTrace(trace) => Self::Trace(trace.message), + proto::language_server_log::LogType::Log(message_type) => Self::Log( + match LogLevel::from_i32(message_type.level).unwrap_or(LogLevel::Log) { + LogLevel::Error => MessageType::ERROR, + LogLevel::Warning => MessageType::WARNING, + LogLevel::Info => MessageType::INFO, + LogLevel::Log => MessageType::LOG, + }, + ), + proto::language_server_log::LogType::Trace(trace_message) => Self::Trace { + verbose_info: trace_message.verbose_info, + }, + proto::language_server_log::LogType::Rpc(message) => Self::Rpc { + received: match rpc_message::Kind::from_i32(message.kind) + .unwrap_or(rpc_message::Kind::Received) + { + rpc_message::Kind::Received => true, + rpc_message::Kind::Sent => false, + }, + }, } } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9e3900198cbc9aa4845428235196763511c0751c..63ce309e7a1cf9f8c6c3d7868be7ed343d1c010a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -280,6 +280,11 @@ pub enum Event { server_id: LanguageServerId, buffer_id: BufferId, buffer_abs_path: PathBuf, + name: Option, + }, + ToggleLspLogs { + server_id: LanguageServerId, + enabled: bool, }, Toast { notification_id: SharedString, @@ -1001,6 +1006,7 @@ impl Project { client.add_entity_request_handler(Self::handle_open_buffer_by_path); client.add_entity_request_handler(Self::handle_open_new_buffer); client.add_entity_message_handler(Self::handle_create_buffer_for_peer); + client.add_entity_message_handler(Self::handle_toggle_lsp_logs); WorktreeStore::init(&client); BufferStore::init(&client); @@ -2971,6 +2977,7 @@ impl Project { buffer_id, server_id: *language_server_id, buffer_abs_path: PathBuf::from(&update.buffer_abs_path), + name: name.clone(), }); } } @@ -4697,6 +4704,20 @@ impl Project { })? } + async fn handle_toggle_lsp_logs( + project: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result<()> { + project.update(&mut cx, |_, cx| { + cx.emit(Event::ToggleLspLogs { + server_id: LanguageServerId::from_proto(envelope.payload.server_id), + enabled: envelope.payload.enabled, + }) + })?; + Ok(()) + } + async fn handle_synchronize_buffers( this: Entity, envelope: TypedEnvelope, diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 6dcd07482e6e3b6ef858f61a04ce312304925dd7..ed15ba845ad815006d5f8de4368a0e61f77c7940 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1951,6 +1951,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC server_id: LanguageServerId(1), buffer_id, buffer_abs_path: PathBuf::from(path!("/dir/a.rs")), + name: Some(fake_server.server.name()) } ); assert_eq!( diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index 473ef5c38cc6f401a05556c1f02271e83bd8fa97..16f6217b29d50a4a2eb9198565f688335c218802 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -610,11 +610,36 @@ 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 { + optional string verbose_info = 1; +} + +message RpcMessage { + Kind kind = 1; + + enum Kind { + RECEIVED = 0; + SENT = 1; } - string message = 5; } message LspLogTrace { @@ -932,3 +957,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 70689bcd6306195fce0d5c6449bf3dd9f5d43539..2222bdec082759cb75ffcdb2c7a95435f36eba11 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 e17ec5203bd5b7bcab03c6461c343156116cc563..04495fb898b1d9bdbf229bb69e1e44b8afa6d1fb 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/Cargo.toml b/crates/remote_server/Cargo.toml index 5dbb9a2771c4e3fda04ed014f993f843b44dd976..249968b246c0615d1a8d60c4446eeac6bcac7451 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 04028ebcac82f814652f32ad7439e32d650f5ad0..1e197fdd338432a7731933cb8051bb2bb4265c18 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( @@ -235,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); @@ -298,11 +302,40 @@ 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::LocalSsh { + lsp_store: self.lsp_store.downgrade(), + }, + *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, @@ -326,16 +359,6 @@ impl HeadlessProject { }) .log_err(); } - LspStoreEvent::LanguageServerLog(language_server_id, log_type, message) => { - self.session - .send(proto::LanguageServerLog { - project_id: REMOTE_SERVER_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: REMOTE_SERVER_PROJECT_ID, @@ -509,7 +532,31 @@ impl HeadlessProject { }) } - pub async fn handle_open_server_settings( + 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, mut cx: AsyncApp, @@ -562,7 +609,7 @@ impl HeadlessProject { }) } - pub async fn handle_find_search_candidates( + async fn handle_find_search_candidates( this: Entity, envelope: TypedEnvelope, mut cx: AsyncApp, @@ -594,7 +641,7 @@ impl HeadlessProject { Ok(response) } - pub async fn handle_list_remote_directory( + async fn handle_list_remote_directory( this: Entity, envelope: TypedEnvelope, cx: AsyncApp, @@ -626,7 +673,7 @@ impl HeadlessProject { }) } - pub async fn handle_get_path_metadata( + async fn handle_get_path_metadata( this: Entity, envelope: TypedEnvelope, cx: AsyncApp, @@ -644,7 +691,7 @@ impl HeadlessProject { }) } - pub async fn handle_shutdown_remote_server( + async fn handle_shutdown_remote_server( _this: Entity, _envelope: TypedEnvelope, cx: AsyncApp, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 1966755d626af6e155440379982af180e9ccbc95..a0717333159e508ea42a1b95bd9f2226e6392871 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -30,7 +30,7 @@ pub struct ActiveSettingsProfileName(pub String); impl Global for ActiveSettingsProfileName {} -#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize)] pub struct WorktreeId(usize); impl From for usize { diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 869aa5322eba7fdaf417606dd62ae73a0c3702b3..ce9be0c7edb47106fced94485d5e5d30abe0dedb 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 diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 9c2d09fd26308b95ab145b557a516b5e6603a0e4..5c0814803173dc33fba4b50fc663d70c15cc0694 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 25e2cb1cfe934a88ec4cc3811bf3216e0765c0af..b6577ff325c1f5a4ff0d9c861f357fb8f4007696 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 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 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)?; + } + anyhow::Ok(()) + })?; } - 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,10 +5087,12 @@ impl Workspace { } } + #[cfg(feature = "call")] pub fn active_call(&self) -> Option<&Entity> { self.active_call.as_ref().map(|(call, _)| call) } + #[cfg(feature = "call")] fn on_active_call_event( &mut self, _: &Entity, @@ -5918,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, @@ -6384,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, @@ -6448,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, @@ -6510,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, @@ -6558,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, @@ -6631,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, @@ -6800,6 +6914,7 @@ actions!( ] ); +#[cfg(feature = "call")] async fn join_channel_internal( channel_id: ChannelId, app_state: &Arc, @@ -6947,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, @@ -7454,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,