Pass LSP RPC logs up to ssh log store

This commit is contained in:
Kirill Bulatov 2025-08-25 19:14:06 +03:00
parent d433406103
commit e75e30b25f
8 changed files with 156 additions and 108 deletions

1
Cargo.lock generated
View file

@ -9211,6 +9211,7 @@ dependencies = [
"gpui", "gpui",
"itertools 0.14.0", "itertools 0.14.0",
"language", "language",
"log",
"lsp", "lsp",
"project", "project",
"proto", "proto",

View file

@ -22,6 +22,7 @@ futures.workspace = true
gpui.workspace = true gpui.workspace = true
itertools.workspace = true itertools.workspace = true
language.workspace = true language.workspace = true
log.workspace = true
lsp.workspace = true lsp.workspace = true
project.workspace = true project.workspace = true
proto.workspace = true proto.workspace = true

View file

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

View file

@ -1,11 +1,9 @@
use anyhow::Result;
use client::AnyProtoClient;
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
use copilot::Copilot; use copilot::Copilot;
use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll}; use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};
use futures::{StreamExt, channel::mpsc}; use futures::{StreamExt, channel::mpsc};
use gpui::{ use gpui::{
AnyView, App, AsyncApp, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, Global, AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, Global,
IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -15,7 +13,6 @@ use lsp::{
MessageType, SetTraceParams, TraceValue, notification::SetTrace, MessageType, SetTraceParams, TraceValue, notification::SetTrace,
}; };
use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery}; use project::{Project, WorktreeId, lsp_store::LanguageServerLogType, search::SearchQuery};
use proto::TypedEnvelope;
use std::{any::TypeId, borrow::Cow, sync::Arc}; use std::{any::TypeId, borrow::Cow, sync::Arc};
use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*}; use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*};
use util::ResultExt as _; use util::ResultExt as _;
@ -115,8 +112,7 @@ impl Message for RpcMessage {
type Level = (); type Level = ();
} }
pub(super) struct LanguageServerState { pub struct LanguageServerState {
project: Option<WeakEntity<Project>>,
name: Option<LanguageServerName>, name: Option<LanguageServerName>,
worktree_id: Option<WorktreeId>, worktree_id: Option<WorktreeId>,
kind: LanguageServerKind, kind: LanguageServerKind,
@ -155,7 +151,7 @@ impl LanguageServerKind {
} }
} }
struct LanguageServerRpcState { pub struct LanguageServerRpcState {
rpc_messages: VecDeque<RpcMessage>, rpc_messages: VecDeque<RpcMessage>,
last_message_kind: Option<MessageKind>, last_message_kind: Option<MessageKind>,
} }
@ -232,9 +228,7 @@ pub struct GlobalLogStore(pub WeakEntity<LogStore>);
impl Global for GlobalLogStore {} impl Global for GlobalLogStore {}
pub fn init(client: AnyProtoClient, store_logs: bool, cx: &mut App) { pub fn init(store_logs: bool, cx: &mut App) {
client.add_entity_message_handler(handle_toggle_lsp_logs);
let log_store = cx.new(|cx| LogStore::new(store_logs, cx)); let log_store = cx.new(|cx| LogStore::new(store_logs, cx));
cx.set_global(GlobalLogStore(log_store.downgrade())); cx.set_global(GlobalLogStore(log_store.downgrade()));
@ -293,7 +287,6 @@ impl LogStore {
Some(name), Some(name),
None, None,
Some(server.clone()), Some(server.clone()),
None,
cx, cx,
); );
} }
@ -311,9 +304,9 @@ impl LogStore {
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
while let Some((server_id, io_kind, message)) = io_rx.next().await { while let Some((server_id, io_kind, message)) = io_rx.next().await {
if let Some(this) = this.upgrade() { if let Some(log_store) = this.upgrade() {
this.update(cx, |this, cx| { log_store.update(cx, |log_store, cx| {
this.on_io(server_id, io_kind, &message, cx); log_store.on_io(server_id, io_kind, &message, cx);
})?; })?;
} }
} }
@ -324,6 +317,11 @@ impl LogStore {
} }
pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) { pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
log::error!(
"?????????????? ssh: {} local: {}",
project.read(cx).is_via_ssh(),
project.read(cx).is_local()
);
let weak_project = project.downgrade(); let weak_project = project.downgrade();
let subscription_weak_project = weak_project.clone(); let subscription_weak_project = weak_project.clone();
self.projects.insert( self.projects.insert(
@ -336,17 +334,15 @@ impl LogStore {
.retain(|_, state| state.kind.project() != Some(&weak_project)); .retain(|_, state| state.kind.project() != Some(&weak_project));
}), }),
cx.subscribe(project, move |log_store, project, event, cx| { cx.subscribe(project, move |log_store, project, event, cx| {
let subscription_weak_project = project.downgrade(); let server_kind = if project.read(cx).is_local() {
let server_kind = if project.read(cx).is_via_ssh() {
LanguageServerKind::Remote {
project: project.downgrade(),
}
} else {
LanguageServerKind::Local { LanguageServerKind::Local {
project: project.downgrade(), project: project.downgrade(),
} }
} else {
LanguageServerKind::Remote {
project: project.downgrade(),
}
}; };
match event { match event {
project::Event::LanguageServerAdded(id, name, worktree_id) => { project::Event::LanguageServerAdded(id, name, worktree_id) => {
log_store.add_language_server( log_store.add_language_server(
@ -359,7 +355,6 @@ impl LogStore {
.lsp_store() .lsp_store()
.read(cx) .read(cx)
.language_server_for_id(*id), .language_server_for_id(*id),
Some(subscription_weak_project),
cx, cx,
); );
} }
@ -373,7 +368,6 @@ impl LogStore {
None, None,
None, None,
None, None,
Some(subscription_weak_project),
cx, cx,
); );
match typ { match typ {
@ -401,24 +395,27 @@ impl LogStore {
_ => {} _ => {}
} }
}), }),
cx.subscribe_self(move |_, e, cx| match e { cx.subscribe(&cx.entity(), move |_, _, e, cx| {
Event::NewServerLogEntry { id, kind, text } => { log::error!("||??????????????????????????????????????||||||@@@@|| {e:?}");
subscription_weak_project match e {
.update(cx, |project, cx| { Event::NewServerLogEntry { id, kind, text } => {
if let Some((client, project_id)) = subscription_weak_project
project.lsp_store().read(cx).downstream_client() .update(cx, |project, cx| {
{ if let Some((client, project_id)) =
client project.lsp_store().read(cx).downstream_client()
.send(proto::LanguageServerLog { {
project_id, client
language_server_id: id.to_proto(), .send(proto::LanguageServerLog {
message: text.clone(), project_id,
log_type: Some(kind.to_proto()), language_server_id: id.to_proto(),
}) message: text.clone(),
.log_err(); log_type: Some(kind.to_proto()),
}; })
}) .log_err();
.ok(); };
})
.ok();
}
} }
}), }),
], ],
@ -433,14 +430,13 @@ impl LogStore {
self.language_servers.get_mut(&id) self.language_servers.get_mut(&id)
} }
fn add_language_server( pub fn add_language_server(
&mut self, &mut self,
kind: LanguageServerKind, kind: LanguageServerKind,
server_id: LanguageServerId, server_id: LanguageServerId,
name: Option<LanguageServerName>, name: Option<LanguageServerName>,
worktree_id: Option<WorktreeId>, worktree_id: Option<WorktreeId>,
server: Option<Arc<LanguageServer>>, server: Option<Arc<LanguageServer>>,
project: Option<WeakEntity<Project>>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Option<&mut LanguageServerState> { ) -> Option<&mut LanguageServerState> {
let server_state = self.language_servers.entry(server_id).or_insert_with(|| { let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
@ -449,7 +445,6 @@ impl LogStore {
name: None, name: None,
worktree_id: None, worktree_id: None,
kind, kind,
project,
rpc_state: None, rpc_state: None,
log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
@ -619,6 +614,8 @@ impl LogStore {
message: message.trim().to_owned(), message: message.trim().to_owned(),
}); });
} }
log::error!("|||||||||| {message}");
cx.emit(Event::NewServerLogEntry { cx.emit(Event::NewServerLogEntry {
id: language_server_id, id: language_server_id,
kind: LanguageServerLogType::Rpc { kind: LanguageServerLogType::Rpc {
@ -628,7 +625,7 @@ impl LogStore {
}); });
} }
fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) { pub fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {
self.language_servers.remove(&id); self.language_servers.remove(&id);
cx.notify(); cx.notify();
} }
@ -662,7 +659,7 @@ impl LogStore {
}) })
} }
fn enable_rpc_trace_for_language_server( pub fn enable_rpc_trace_for_language_server(
&mut self, &mut self,
server_id: LanguageServerId, server_id: LanguageServerId,
) -> Option<&mut LanguageServerRpcState> { ) -> Option<&mut LanguageServerRpcState> {
@ -817,23 +814,6 @@ impl LogStore {
} }
} }
async fn handle_toggle_lsp_logs(
lsp_log: Entity<LogStore>,
envelope: TypedEnvelope<proto::ToggleLspLogs>,
mut cx: AsyncApp,
) -> Result<()> {
let server_id = LanguageServerId::from_proto(envelope.payload.server_id);
lsp_log.update(&mut cx, |lsp_log, _| {
// we do not support any other log toggling yet
if envelope.payload.enabled {
lsp_log.enable_rpc_trace_for_language_server(server_id);
} else {
lsp_log.disable_rpc_trace_for_language_server(server_id);
}
})?;
Ok(())
}
impl LspLogView { impl LspLogView {
pub fn new( pub fn new(
project: Entity<Project>, project: Entity<Project>,
@ -875,48 +855,48 @@ impl LspLogView {
cx.notify(); cx.notify();
}); });
let events_subscriptions = cx.subscribe_in( let events_subscriptions =
&log_store, cx.subscribe_in(&log_store, window, move |log_view, _, e, window, cx| {
window, log::error!("||||||||@@@@|| {e:?}");
move |log_view, _, e, window, cx| match e { match e {
Event::NewServerLogEntry { id, kind, text } => { Event::NewServerLogEntry { id, kind, text } => {
if log_view.current_server_id == Some(*id) if log_view.current_server_id == Some(*id)
&& LogKind::from_server_log_type(kind) == log_view.active_entry_kind && LogKind::from_server_log_type(kind) == log_view.active_entry_kind
{ {
log_view.editor.update(cx, |editor, cx| { log_view.editor.update(cx, |editor, cx| {
editor.set_read_only(false); editor.set_read_only(false);
let last_offset = editor.buffer().read(cx).len(cx); let last_offset = editor.buffer().read(cx).len(cx);
let newest_cursor_is_at_end = let newest_cursor_is_at_end =
editor.selections.newest::<usize>(cx).start >= last_offset; editor.selections.newest::<usize>(cx).start >= last_offset;
editor.edit( editor.edit(
vec![ vec![
(last_offset..last_offset, text.as_str()), (last_offset..last_offset, text.as_str()),
(last_offset..last_offset, "\n"), (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, 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 { if newest_cursor_is_at_end {
editor.request_autoscroll(Autoscroll::bottom(), cx); editor.request_autoscroll(Autoscroll::bottom(), cx);
} }
editor.set_read_only(true); editor.set_read_only(true);
}); });
}
} }
} }
}, });
);
let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx); let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx);
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
@ -1207,7 +1187,7 @@ impl LspLogView {
} }
if let Some(server_state) = log_store.language_servers.get(&server_id) { if let Some(server_state) = log_store.language_servers.get(&server_id) {
if let Some(project) = &server_state.project { if let LanguageServerKind::Remote { project } = &server_state.kind {
project project
.update(cx, |project, cx| { .update(cx, |project, cx| {
if let Some((client, project_id)) = if let Some((client, project_id)) =
@ -1937,6 +1917,7 @@ impl ServerInfo {
} }
} }
#[derive(Debug)]
pub enum Event { pub enum Event {
NewServerLogEntry { NewServerLogEntry {
id: LanguageServerId, id: LanguageServerId,

View file

@ -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.settings_observer);
ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.git_store); 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_create_buffer_for_peer);
ssh_proto.add_entity_message_handler(Self::handle_update_worktree); ssh_proto.add_entity_message_handler(Self::handle_update_worktree);
ssh_proto.add_entity_message_handler(Self::handle_update_project); 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<Self>,
_envelope: TypedEnvelope<proto::ToggleLspLogs>,
_cx: AsyncApp,
) -> Result<()> {
log::error!("##########PPPPPPPPPPPPPPPproject##########################");
Ok(())
}
async fn handle_update_buffer_from_ssh( async fn handle_update_buffer_from_ssh(
this: Entity<Self>, this: Entity<Self>,
envelope: TypedEnvelope<proto::UpdateBuffer>, envelope: TypedEnvelope<proto::UpdateBuffer>,

View file

@ -1,5 +1,7 @@
use ::proto::{FromProto, ToProto}; use ::proto::{FromProto, ToProto};
use anyhow::{Context as _, Result, anyhow}; use anyhow::{Context as _, Result, anyhow};
use language_tools::lsp_log::{GlobalLogStore, LanguageServerKind};
use lsp::LanguageServerId;
use extension::ExtensionHostProxy; use extension::ExtensionHostProxy;
use extension_host::headless_host::HeadlessExtensionStore; use extension_host::headless_host::HeadlessExtensionStore;
@ -65,6 +67,7 @@ impl HeadlessProject {
settings::init(cx); settings::init(cx);
language::init(cx); language::init(cx);
project::Project::init_settings(cx); project::Project::init_settings(cx);
language_tools::lsp_log::init(false, cx);
} }
pub fn new( pub fn new(
@ -80,7 +83,6 @@ impl HeadlessProject {
) -> Self { ) -> Self {
debug_adapter_extension::init(proxy.clone(), cx); debug_adapter_extension::init(proxy.clone(), cx);
languages::init(languages.clone(), node_runtime.clone(), cx); languages::init(languages.clone(), node_runtime.clone(), cx);
language_tools::lsp_log::init(session.clone(), false, cx);
let worktree_store = cx.new(|cx| { let worktree_store = cx.new(|cx| {
let mut store = WorktreeStore::local(true, fs.clone()); let mut store = WorktreeStore::local(true, fs.clone());
@ -236,6 +238,7 @@ impl HeadlessProject {
session.add_entity_request_handler(Self::handle_open_new_buffer); 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_find_search_candidates);
session.add_entity_request_handler(Self::handle_open_server_settings); 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_request_handler(BufferStore::handle_update_buffer);
session.add_entity_message_handler(BufferStore::handle_close_buffer); session.add_entity_message_handler(BufferStore::handle_close_buffer);
@ -299,11 +302,38 @@ impl HeadlessProject {
fn on_lsp_store_event( fn on_lsp_store_event(
&mut self, &mut self,
_lsp_store: Entity<LspStore>, lsp_store: Entity<LspStore>,
event: &LspStoreEvent, event: &LspStoreEvent,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
match event { match event {
LspStoreEvent::LanguageServerAdded(id, name, worktree_id) => {
let log_store = cx
.try_global::<GlobalLogStore>()
.and_then(|lsp_logs| lsp_logs.0.upgrade());
if let Some(log_store) = log_store {
log_store.update(cx, |log_store, cx| {
log_store.add_language_server(
LanguageServerKind::Global,
*id,
Some(name.clone()),
*worktree_id,
lsp_store.read(cx).language_server_for_id(*id),
cx,
);
});
}
}
LspStoreEvent::LanguageServerRemoved(id) => {
let log_store = cx
.try_global::<GlobalLogStore>()
.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 { LspStoreEvent::LanguageServerUpdate {
language_server_id, language_server_id,
name, name,
@ -500,6 +530,30 @@ impl HeadlessProject {
}) })
} }
async fn handle_toggle_lsp_logs(
_: Entity<Self>,
envelope: TypedEnvelope<proto::ToggleLspLogs>,
mut cx: AsyncApp,
) -> Result<()> {
let server_id = LanguageServerId::from_proto(envelope.payload.server_id);
let lsp_logs = cx
.update(|cx| {
cx.try_global::<GlobalLogStore>()
.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( async fn handle_open_server_settings(
this: Entity<Self>, this: Entity<Self>,
_: TypedEnvelope<proto::OpenServerSettings>, _: TypedEnvelope<proto::OpenServerSettings>,

View file

@ -7425,6 +7425,7 @@ pub fn open_ssh_project_with_new_connection(
cx, cx,
) )
})?; })?;
// TODO kb register here instead?
open_ssh_project_inner( open_ssh_project_inner(
project, project,

View file

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