diff --git a/Cargo.lock b/Cargo.lock index 42649b137f..7d07e8315d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9211,8 +9211,10 @@ dependencies = [ "gpui", "itertools 0.14.0", "language", + "log", "lsp", "project", + "proto", "release_channel", "serde_json", "settings", @@ -13500,6 +13502,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 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/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/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/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 5aa914311a..d0ae293a4b 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -22,15 +22,17 @@ futures.workspace = true gpui.workspace = true itertools.workspace = true language.workspace = true +log.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 cbf5756875..d6a006f47b 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 d5206c1f26..5a52a103a6 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -9,12 +9,15 @@ 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, 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 +31,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, @@ -75,6 +79,7 @@ impl Message for LogMessage { pub(super) struct TraceMessage { message: String, + is_verbose: bool, } impl AsRef for TraceMessage { @@ -84,7 +89,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 { @@ -101,7 +114,7 @@ impl Message for RpcMessage { type Level = (); } -pub(super) struct LanguageServerState { +pub struct LanguageServerState { name: Option, worktree_id: Option, kind: LanguageServerKind, @@ -117,20 +130,16 @@ pub(super) struct LanguageServerState { 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 +150,13 @@ impl LanguageServerKind { match self { Self::Local { project } => Some(project), Self::Remote { project } => Some(project), + Self::LocalSsh { .. } => None, Self::Global { .. } => None, } } } -struct LanguageServerRpcState { +pub struct LanguageServerRpcState { rpc_messages: VecDeque, last_message_kind: Option, } @@ -183,6 +193,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 +229,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_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(); } 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 +285,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 +299,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 +333,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_ssh() { - LanguageServerKind::Remote { - project: project.downgrade(), - } - } else { + 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::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()), @@ -347,16 +359,36 @@ impl LogStore { ); } 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); } } } @@ -375,7 +407,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 +458,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 +494,133 @@ 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); - log_lines.push_back(message); - if visible { - cx.emit(Event::NewServerLogEntry { id, kind, text }); - cx.notify(); - } + let visible_message = visible.then(|| message.as_ref().to_string()); + log_lines.push_back(message); + visible_message } - fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context) { + 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 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 { + 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(), + }); + } + line_before_message_to_send = Some(line_before_message); + } + + 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(), + }); + } + + 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(), + }, + cx, + ); + } + + pub fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context) { self.language_servers.remove(&id); cx.notify(); } @@ -516,11 +650,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,51 +797,47 @@ 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(), - }); - cx.emit(Event::NewServerLogEntry { - id: language_server_id, - kind: LogKind::Rpc, - text: message.to_string(), - }); + self.add_language_server_rpc(language_server_id, kind, message, cx); 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 { @@ -751,48 +881,49 @@ 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 - { - 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(); @@ -800,7 +931,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 +946,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 +969,7 @@ impl LspLogView { } fn editor_for_server_info( - server: &LanguageServer, + info: ServerInfo, window: &mut Window, cx: &mut Context, ) -> (Entity, Vec) { @@ -853,22 +984,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 +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)) @@ -1003,11 +1135,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 +1163,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 +1208,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 +1273,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 +1601,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 +1622,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 +1878,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 +1910,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 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/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..4e0201d722 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>)>, @@ -12168,6 +12170,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. @@ -12677,45 +12683,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) - } - Self::Trace(message) => { - proto::language_server_log::LogType::LogTrace(proto::LspLogTrace { - message: message.clone(), + proto::language_server_log::LogType::Log(proto::LogMessage { + level: level as i32, }) } + Self::Trace { verbose_info } => { + proto::language_server_log::LogType::Trace(proto::TraceMessage { + verbose_info: verbose_info.to_owned(), + }) + } + Self::Rpc { received } => { + let kind = if *received { + proto::rpc_message::Kind::Received + } else { + proto::rpc_message::Kind::Sent + }; + let kind = kind as i32; + proto::language_server_log::LogType::Rpc(proto::RpcMessage { kind }) + } } } pub fn from_proto(log_type: proto::language_server_log::LogType) -> Self { + use proto::log_message::LogLevel; + use proto::rpc_message; match log_type { - proto::language_server_log::LogType::LogMessageType(message_type) => { - Self::Log(match message_type { - 1 => MessageType::ERROR, - 2 => MessageType::WARNING, - 3 => MessageType::INFO, - 4 => MessageType::LOG, - _ => MessageType::LOG, - }) - } - proto::language_server_log::LogType::LogTrace(trace) => Self::Trace(trace.message), + proto::language_server_log::LogType::Log(message_type) => Self::Log( + match LogLevel::from_i32(message_type.level).unwrap_or(LogLevel::Log) { + LogLevel::Error => MessageType::ERROR, + LogLevel::Warning => MessageType::WARNING, + LogLevel::Info => MessageType::INFO, + LogLevel::Log => MessageType::LOG, + }, + ), + proto::language_server_log::LogType::Trace(trace_message) => Self::Trace { + verbose_info: trace_message.verbose_info, + }, + proto::language_server_log::LogType::Rpc(message) => Self::Rpc { + received: match rpc_message::Kind::from_i32(message.kind) + .unwrap_or(rpc_message::Kind::Received) + { + rpc_message::Kind::Received => true, + rpc_message::Kind::Sent => false, + }, + }, } } } 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/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index 473ef5c38c..16f6217b29 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 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/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..71eeaede15 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: 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, @@ -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/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 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 044601df97..943984cf07 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,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, @@ -7299,6 +7425,7 @@ pub fn open_ssh_project_with_new_connection( cx, ) })?; + // TODO kb register here instead? open_ssh_project_inner( project, @@ -7454,6 +7581,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,