From 33296802fb0424863ad3a956b7b70b76afaa23f3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 12:11:39 +0300 Subject: [PATCH 1/6] Add a rough prototype --- crates/language_tools/src/lsp_log.rs | 60 ++++++++++++++++------------ 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 383ca94851409cfd28530c62e3d786e1b1c72061..a796bc46c85ba5270477cc4db2fb347cf8adc904 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -36,7 +36,7 @@ struct ProjectState { } struct LanguageServerState { - log_buffer: ModelHandle, + log_storage: Vec, rpc_state: Option, _io_logs_subscription: Option, _lsp_logs_subscription: Option, @@ -168,15 +168,14 @@ impl LogStore { project: &ModelHandle, id: LanguageServerId, cx: &mut ModelContext, - ) -> Option> { + ) -> Option<&mut Vec> { let project_state = self.projects.get_mut(&project.downgrade())?; let server_state = project_state.servers.entry(id).or_insert_with(|| { cx.notify(); LanguageServerState { rpc_state: None, - log_buffer: cx - .add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")) - .clone(), + // TODO kb move this to settings? + log_storage: Vec::with_capacity(10_000), _io_logs_subscription: None, _lsp_logs_subscription: None, } @@ -186,7 +185,7 @@ impl LogStore { if let Some(server) = server.as_deref() { if server.has_notification_handler::() { // Another event wants to re-add the server that was already added and subscribed to, avoid doing it again. - return Some(server_state.log_buffer.clone()); + return Some(&mut server_state.log_storage); } } @@ -215,7 +214,7 @@ impl LogStore { } }) }); - Some(server_state.log_buffer.clone()) + Some(&mut server_state.log_storage) } fn add_language_server_log( @@ -225,25 +224,23 @@ impl LogStore { message: &str, cx: &mut ModelContext, ) -> Option<()> { - let buffer = match self + let log_lines = match self .projects .get_mut(&project.downgrade())? .servers - .get(&id) - .map(|state| state.log_buffer.clone()) + .get_mut(&id) + .map(|state| &mut state.log_storage) { Some(existing_buffer) => existing_buffer, None => self.add_language_server(&project, id, cx)?, }; - buffer.update(cx, |buffer, cx| { - let len = buffer.len(); - let has_newline = message.ends_with("\n"); - buffer.edit([(len..len, message)], None, cx); - if !has_newline { - let len = buffer.len(); - buffer.edit([(len..len, "\n")], None, cx); - } - }); + + // TODO kb something better VecDequeue? + if log_lines.capacity() == log_lines.len() { + log_lines.drain(..log_lines.len() / 2); + } + log_lines.push(message.trim().to_string()); + cx.notify(); Some(()) } @@ -260,15 +257,15 @@ impl LogStore { Some(()) } - pub fn log_buffer_for_server( + fn server_logs( &self, project: &ModelHandle, server_id: LanguageServerId, - ) -> Option> { + ) -> Option<&[String]> { let weak_project = project.downgrade(); let project_state = self.projects.get(&weak_project)?; let server_state = project_state.servers.get(&server_id)?; - Some(server_state.log_buffer.clone()) + Some(&server_state.log_storage) } fn enable_rpc_trace_for_language_server( @@ -487,14 +484,24 @@ impl LspLogView { } fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext) { - let buffer = self + let log_contents = self .log_store .read(cx) - .log_buffer_for_server(&self.project, server_id); - if let Some(buffer) = buffer { + .server_logs(&self.project, server_id) + .map(|lines| lines.join("\n")); + if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = false; - self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx); + let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, log_contents)); + let editor = cx.add_view(|cx| { + let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx); + editor.set_read_only(true); + editor.move_to_end(&Default::default(), cx); + editor + }); + cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())) + .detach(); + self.editor = editor; cx.notify(); } } @@ -505,6 +512,7 @@ impl LspLogView { cx: &mut ViewContext, ) { let buffer = self.log_store.update(cx, |log_set, cx| { + // TODO kb save this buffer from overflows too log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx) }); if let Some(buffer) = buffer { From 5a4161d29385ca6fd454ca788cab3204c5f1e820 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 15:41:27 +0300 Subject: [PATCH 2/6] Do not detach subscriptions --- crates/language_tools/src/lsp_log.rs | 63 +++++++++++++++++----------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index a796bc46c85ba5270477cc4db2fb347cf8adc904..dcdaf1df6b4683175d0c6f1428de41c9811784e7 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1,4 +1,4 @@ -use collections::HashMap; +use collections::{HashMap, VecDeque}; use editor::Editor; use futures::{channel::mpsc, StreamExt}; use gpui::{ @@ -36,7 +36,7 @@ struct ProjectState { } struct LanguageServerState { - log_storage: Vec, + log_storage: VecDeque, rpc_state: Option, _io_logs_subscription: Option, _lsp_logs_subscription: Option, @@ -49,6 +49,7 @@ struct LanguageServerRpcState { pub struct LspLogView { pub(crate) editor: ViewHandle, + _editor_subscription: Subscription, log_store: ModelHandle, current_server_id: Option, is_showing_rpc_trace: bool, @@ -168,14 +169,14 @@ impl LogStore { project: &ModelHandle, id: LanguageServerId, cx: &mut ModelContext, - ) -> Option<&mut Vec> { + ) -> Option<&mut LanguageServerState> { let project_state = self.projects.get_mut(&project.downgrade())?; let server_state = project_state.servers.entry(id).or_insert_with(|| { cx.notify(); LanguageServerState { rpc_state: None, // TODO kb move this to settings? - log_storage: Vec::with_capacity(10_000), + log_storage: VecDeque::with_capacity(10_000), _io_logs_subscription: None, _lsp_logs_subscription: None, } @@ -185,7 +186,7 @@ impl LogStore { if let Some(server) = server.as_deref() { if server.has_notification_handler::() { // Another event wants to re-add the server that was already added and subscribed to, avoid doing it again. - return Some(&mut server_state.log_storage); + return Some(server_state); } } @@ -214,7 +215,7 @@ impl LogStore { } }) }); - Some(&mut server_state.log_storage) + Some(server_state) } fn add_language_server_log( @@ -224,22 +225,24 @@ impl LogStore { message: &str, cx: &mut ModelContext, ) -> Option<()> { - let log_lines = match self + let language_server_state = match self .projects .get_mut(&project.downgrade())? .servers .get_mut(&id) - .map(|state| &mut state.log_storage) { - Some(existing_buffer) => existing_buffer, + Some(existing_state) => existing_state, None => self.add_language_server(&project, id, cx)?, }; - // TODO kb something better VecDequeue? + let log_lines = &mut language_server_state.log_storage; if log_lines.capacity() == log_lines.len() { - log_lines.drain(..log_lines.len() / 2); + log_lines.pop_front(); } - log_lines.push(message.trim().to_string()); + log_lines.push_back(message.trim().to_string()); + + //// TODO kb refresh editor too + //need LspLogView. cx.notify(); Some(()) @@ -261,7 +264,7 @@ impl LogStore { &self, project: &ModelHandle, server_id: LanguageServerId, - ) -> Option<&[String]> { + ) -> Option<&VecDeque> { let weak_project = project.downgrade(); let project_state = self.projects.get(&weak_project)?; let server_state = project_state.servers.get(&server_id)?; @@ -408,8 +411,10 @@ impl LspLogView { cx.notify(); }); + let (editor, _editor_subscription) = Self::editor_for_buffer(project.clone(), buffer, cx); let mut this = Self { - editor: Self::editor_for_buffer(project.clone(), buffer, cx), + editor, + _editor_subscription, project, log_store, current_server_id: None, @@ -426,16 +431,15 @@ impl LspLogView { project: ModelHandle, buffer: ModelHandle, cx: &mut ViewContext, - ) -> ViewHandle { + ) -> (ViewHandle, Subscription) { let editor = cx.add_view(|cx| { let mut editor = Editor::for_buffer(buffer, Some(project), cx); editor.set_read_only(true); editor.move_to_end(&Default::default(), cx); editor }); - cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())) - .detach(); - editor + let subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); + (editor, subscription) } pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option> { @@ -488,19 +492,27 @@ impl LspLogView { .log_store .read(cx) .server_logs(&self.project, server_id) - .map(|lines| lines.join("\n")); + .map(|lines| { + let (a, b) = lines.as_slices(); + let log_contents = a.join("\n"); + if b.is_empty() { + log_contents + } else { + log_contents + "\n" + &b.join("\n") + } + }); if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = false; - let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, log_contents)); let editor = cx.add_view(|cx| { - let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx); + let mut editor = Editor::multi_line(None, cx); editor.set_read_only(true); editor.move_to_end(&Default::default(), cx); + editor.set_text(log_contents, cx); editor }); - cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())) - .detach(); + self._editor_subscription = + cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); self.editor = editor; cx.notify(); } @@ -518,7 +530,10 @@ impl LspLogView { if let Some(buffer) = buffer { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = true; - self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx); + let (editor, _editor_subscription) = + Self::editor_for_buffer(self.project.clone(), buffer, cx); + self.editor = editor; + self._editor_subscription = _editor_subscription; cx.notify(); } } From ba5c188630d86373b119213ffdab21449eccccc6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 16:53:44 +0300 Subject: [PATCH 3/6] Update editor with current buffer logs --- crates/language_tools/src/lsp_log.rs | 41 ++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index dcdaf1df6b4683175d0c6f1428de41c9811784e7..bf75d35bb7c6ac515ba476cd944620c0f7fbe595 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -24,6 +24,7 @@ use workspace::{ const SEND_LINE: &str = "// Send:\n"; const RECEIVE_LINE: &str = "// Receive:\n"; +const MAX_STORED_LOG_ENTRIES: usize = 5000; pub struct LogStore { projects: HashMap, ProjectState>, @@ -54,7 +55,7 @@ pub struct LspLogView { current_server_id: Option, is_showing_rpc_trace: bool, project: ModelHandle, - _log_store_subscription: Subscription, + _log_store_subscriptions: Vec, } pub struct LspLogToolbarItemView { @@ -175,8 +176,7 @@ impl LogStore { cx.notify(); LanguageServerState { rpc_state: None, - // TODO kb move this to settings? - log_storage: VecDeque::with_capacity(10_000), + log_storage: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), _io_logs_subscription: None, _lsp_logs_subscription: None, } @@ -236,14 +236,16 @@ impl LogStore { }; let log_lines = &mut language_server_state.log_storage; - if log_lines.capacity() == log_lines.len() { + if log_lines.len() == MAX_STORED_LOG_ENTRIES { log_lines.pop_front(); } - log_lines.push_back(message.trim().to_string()); - - //// TODO kb refresh editor too - //need LspLogView. + let message = message.trim(); + log_lines.push_back(message.to_string()); + cx.emit(Event::NewServerLogEntry { + id, + entry: message.to_string(), + }); cx.notify(); Some(()) } @@ -375,7 +377,7 @@ impl LspLogView { .get(&project.downgrade()) .and_then(|project| project.servers.keys().copied().next()); let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")); - let _log_store_subscription = cx.observe(&log_store, |this, store, cx| { + let model_changes_subscription = cx.observe(&log_store, |this, store, cx| { (|| -> Option<()> { let project_state = store.read(cx).projects.get(&this.project.downgrade())?; if let Some(current_lsp) = this.current_server_id { @@ -411,6 +413,18 @@ impl LspLogView { cx.notify(); }); + let events_subscriptions = cx.subscribe(&log_store, |log_view, _, e, cx| match e { + Event::NewServerLogEntry { id, entry } => { + if log_view.current_server_id == Some(*id) { + log_view.editor.update(cx, |editor, cx| { + editor.set_read_only(false); + editor.handle_input(entry, cx); + editor.handle_input("\n", cx); + editor.set_read_only(true); + }) + } + } + }); let (editor, _editor_subscription) = Self::editor_for_buffer(project.clone(), buffer, cx); let mut this = Self { editor, @@ -419,7 +433,7 @@ impl LspLogView { log_store, current_server_id: None, is_showing_rpc_trace: false, - _log_store_subscription, + _log_store_subscriptions: vec![model_changes_subscription, events_subscriptions], }; if let Some(server_id) = server_id { this.show_logs_for_server(server_id, cx); @@ -524,7 +538,6 @@ impl LspLogView { cx: &mut ViewContext, ) { let buffer = self.log_store.update(cx, |log_set, cx| { - // TODO kb save this buffer from overflows too log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx) }); if let Some(buffer) = buffer { @@ -972,8 +985,12 @@ impl LspLogToolbarItemView { } } +pub enum Event { + NewServerLogEntry { id: LanguageServerId, entry: String }, +} + impl Entity for LogStore { - type Event = (); + type Event = Event; } impl Entity for LspLogView { From c872c86c4a5df3200cc1b2f621aedc5a4c8651e3 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 20:53:39 +0300 Subject: [PATCH 4/6] Remove another needless log buffer --- crates/language_tools/src/lsp_log.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index bf75d35bb7c6ac515ba476cd944620c0f7fbe595..58f7d68235babf37f07b81864a966e109ae3975b 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -376,7 +376,6 @@ impl LspLogView { .projects .get(&project.downgrade()) .and_then(|project| project.servers.keys().copied().next()); - let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")); let model_changes_subscription = cx.observe(&log_store, |this, store, cx| { (|| -> Option<()> { let project_state = store.read(cx).projects.get(&this.project.downgrade())?; @@ -425,7 +424,14 @@ impl LspLogView { } } }); - let (editor, _editor_subscription) = Self::editor_for_buffer(project.clone(), buffer, cx); + // TODO kb deduplicate + let editor = cx.add_view(|cx| { + let mut editor = Editor::multi_line(None, cx); + editor.set_read_only(true); + editor.move_to_end(&Default::default(), cx); + editor + }); + let _editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); let mut this = Self { editor, _editor_subscription, From 08af830fd7b6b70ea29ba55b3088acec967829cd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 21:40:25 +0300 Subject: [PATCH 5/6] Do not create buffers for rpc logs --- crates/language_tools/src/lsp_log.rs | 218 +++++++++++++++------------ 1 file changed, 118 insertions(+), 100 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 58f7d68235babf37f07b81864a966e109ae3975b..ae63f84b64deff1910328823e6fcb285db8ed685 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1,5 +1,5 @@ use collections::{HashMap, VecDeque}; -use editor::Editor; +use editor::{Editor, MoveToEnd}; use futures::{channel::mpsc, StreamExt}; use gpui::{ actions, @@ -11,7 +11,7 @@ use gpui::{ AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription, View, ViewContext, ViewHandle, WeakModelHandle, }; -use language::{Buffer, LanguageServerId, LanguageServerName}; +use language::{LanguageServerId, LanguageServerName}; use lsp::IoKind; use project::{search::SearchQuery, Project}; use std::{borrow::Cow, sync::Arc}; @@ -22,8 +22,8 @@ use workspace::{ ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceCreated, }; -const SEND_LINE: &str = "// Send:\n"; -const RECEIVE_LINE: &str = "// Receive:\n"; +const SEND_LINE: &str = "// Send:"; +const RECEIVE_LINE: &str = "// Receive:"; const MAX_STORED_LOG_ENTRIES: usize = 5000; pub struct LogStore { @@ -37,20 +37,20 @@ struct ProjectState { } struct LanguageServerState { - log_storage: VecDeque, + log_messages: VecDeque, rpc_state: Option, _io_logs_subscription: Option, _lsp_logs_subscription: Option, } struct LanguageServerRpcState { - buffer: ModelHandle, + rpc_messages: VecDeque, last_message_kind: Option, } pub struct LspLogView { pub(crate) editor: ViewHandle, - _editor_subscription: Subscription, + editor_subscription: Subscription, log_store: ModelHandle, current_server_id: Option, is_showing_rpc_trace: bool, @@ -124,10 +124,9 @@ impl LogStore { io_tx, }; cx.spawn_weak(|this, mut cx| async move { - while let Some((project, server_id, io_kind, mut message)) = io_rx.next().await { + while let Some((project, server_id, io_kind, message)) = io_rx.next().await { if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { - message.push('\n'); this.on_io(project, server_id, io_kind, &message, cx); }); } @@ -176,7 +175,7 @@ impl LogStore { cx.notify(); LanguageServerState { rpc_state: None, - log_storage: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), + log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), _io_logs_subscription: None, _lsp_logs_subscription: None, } @@ -235,16 +234,16 @@ impl LogStore { None => self.add_language_server(&project, id, cx)?, }; - let log_lines = &mut language_server_state.log_storage; + let log_lines = &mut language_server_state.log_messages; if log_lines.len() == MAX_STORED_LOG_ENTRIES { log_lines.pop_front(); } - let message = message.trim(); log_lines.push_back(message.to_string()); cx.emit(Event::NewServerLogEntry { id, entry: message.to_string(), + is_rpc: false, }); cx.notify(); Some(()) @@ -270,38 +269,24 @@ impl LogStore { let weak_project = project.downgrade(); let project_state = self.projects.get(&weak_project)?; let server_state = project_state.servers.get(&server_id)?; - Some(&server_state.log_storage) + Some(&server_state.log_messages) } fn enable_rpc_trace_for_language_server( &mut self, project: &ModelHandle, server_id: LanguageServerId, - cx: &mut ModelContext, - ) -> Option> { + ) -> Option<&mut LanguageServerRpcState> { let weak_project = project.downgrade(); let project_state = self.projects.get_mut(&weak_project)?; let server_state = project_state.servers.get_mut(&server_id)?; - let rpc_state = server_state.rpc_state.get_or_insert_with(|| { - let language = project.read(cx).languages().language_for_name("JSON"); - let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "")); - cx.spawn_weak({ - let buffer = buffer.clone(); - |_, mut cx| async move { - let language = language.await.ok(); - buffer.update(&mut cx, |buffer, cx| { - buffer.set_language(language, cx); - }); - } - }) - .detach(); - - LanguageServerRpcState { - buffer, + let rpc_state = server_state + .rpc_state + .get_or_insert_with(|| LanguageServerRpcState { + rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES), last_message_kind: None, - } - }); - Some(rpc_state.buffer.clone()) + }); + Some(rpc_state) } pub fn disable_rpc_trace_for_language_server( @@ -330,7 +315,7 @@ impl LogStore { IoKind::StdIn => false, IoKind::StdErr => { let project = project.upgrade(cx)?; - let message = format!("stderr: {}\n", message.trim()); + let message = format!("stderr: {}", message.trim()); self.add_language_server_log(&project, language_server_id, &message, cx); return Some(()); } @@ -343,24 +328,40 @@ impl LogStore { .get_mut(&language_server_id)? .rpc_state .as_mut()?; - state.buffer.update(cx, |buffer, cx| { - let kind = if is_received { - MessageKind::Receive - } else { - MessageKind::Send + let kind = if is_received { + MessageKind::Receive + } else { + MessageKind::Send + }; + + let rpc_log_lines = &mut state.rpc_messages; + if rpc_log_lines.len() == MAX_STORED_LOG_ENTRIES { + rpc_log_lines.pop_front(); + } + if state.last_message_kind != Some(kind) { + let line_before_message = match kind { + MessageKind::Send => SEND_LINE, + MessageKind::Receive => RECEIVE_LINE, }; - if state.last_message_kind != Some(kind) { - let len = buffer.len(); - let line = match kind { - MessageKind::Send => SEND_LINE, - MessageKind::Receive => RECEIVE_LINE, - }; - buffer.edit([(len..len, line)], None, cx); - state.last_message_kind = Some(kind); - } - let len = buffer.len(); - buffer.edit([(len..len, message)], None, cx); + rpc_log_lines.push_back(line_before_message.to_string()); + cx.emit(Event::NewServerLogEntry { + id: language_server_id, + entry: line_before_message.to_string(), + is_rpc: true, + }); + } + + if rpc_log_lines.len() == MAX_STORED_LOG_ENTRIES { + rpc_log_lines.pop_front(); + } + let message = message.trim(); + rpc_log_lines.push_back(message.to_string()); + cx.emit(Event::NewServerLogEntry { + id: language_server_id, + entry: message.to_string(), + is_rpc: true, }); + cx.notify(); Some(()) } } @@ -413,28 +414,25 @@ impl LspLogView { cx.notify(); }); let events_subscriptions = cx.subscribe(&log_store, |log_view, _, e, cx| match e { - Event::NewServerLogEntry { id, entry } => { + Event::NewServerLogEntry { id, entry, is_rpc } => { if log_view.current_server_id == Some(*id) { - log_view.editor.update(cx, |editor, cx| { - editor.set_read_only(false); - editor.handle_input(entry, cx); - editor.handle_input("\n", cx); - editor.set_read_only(true); - }) + if (*is_rpc && log_view.is_showing_rpc_trace) + || (!*is_rpc && !log_view.is_showing_rpc_trace) + { + log_view.editor.update(cx, |editor, cx| { + editor.set_read_only(false); + editor.handle_input(entry.trim(), cx); + editor.handle_input("\n", cx); + editor.set_read_only(true); + }); + } } } }); - // TODO kb deduplicate - let editor = cx.add_view(|cx| { - let mut editor = Editor::multi_line(None, cx); - editor.set_read_only(true); - editor.move_to_end(&Default::default(), cx); - editor - }); - let _editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); + let (editor, editor_subscription) = Self::editor_for_logs(String::new(), cx); let mut this = Self { editor, - _editor_subscription, + editor_subscription, project, log_store, current_server_id: None, @@ -447,19 +445,19 @@ impl LspLogView { this } - fn editor_for_buffer( - project: ModelHandle, - buffer: ModelHandle, + fn editor_for_logs( + log_contents: String, cx: &mut ViewContext, ) -> (ViewHandle, Subscription) { let editor = cx.add_view(|cx| { - let mut editor = Editor::for_buffer(buffer, Some(project), cx); + let mut editor = Editor::multi_line(None, cx); + editor.set_text(log_contents, cx); + editor.move_to_end(&MoveToEnd, cx); editor.set_read_only(true); - editor.move_to_end(&Default::default(), cx); editor }); - let subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); - (editor, subscription) + let editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); + (editor, editor_subscription) } pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option> { @@ -512,28 +510,13 @@ impl LspLogView { .log_store .read(cx) .server_logs(&self.project, server_id) - .map(|lines| { - let (a, b) = lines.as_slices(); - let log_contents = a.join("\n"); - if b.is_empty() { - log_contents - } else { - log_contents + "\n" + &b.join("\n") - } - }); + .map(log_contents); if let Some(log_contents) = log_contents { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = false; - let editor = cx.add_view(|cx| { - let mut editor = Editor::multi_line(None, cx); - editor.set_read_only(true); - editor.move_to_end(&Default::default(), cx); - editor.set_text(log_contents, cx); - editor - }); - self._editor_subscription = - cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone())); + let (editor, editor_subscription) = Self::editor_for_logs(log_contents, cx); self.editor = editor; + self.editor_subscription = editor_subscription; cx.notify(); } } @@ -543,16 +526,37 @@ impl LspLogView { server_id: LanguageServerId, cx: &mut ViewContext, ) { - let buffer = self.log_store.update(cx, |log_set, cx| { - log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx) + let rpc_log = self.log_store.update(cx, |log_store, _| { + log_store + .enable_rpc_trace_for_language_server(&self.project, server_id) + .map(|state| log_contents(&state.rpc_messages)) }); - if let Some(buffer) = buffer { + if let Some(rpc_log) = rpc_log { self.current_server_id = Some(server_id); self.is_showing_rpc_trace = true; - let (editor, _editor_subscription) = - Self::editor_for_buffer(self.project.clone(), buffer, cx); + let (editor, editor_subscription) = Self::editor_for_logs(rpc_log, cx); + let language = self.project.read(cx).languages().language_for_name("JSON"); + editor + .read(cx) + .buffer() + .read(cx) + .as_singleton() + .expect("log buffer should be a singleton") + .update(cx, |_, cx| { + cx.spawn_weak({ + let buffer = cx.handle(); + |_, mut cx| async move { + let language = language.await.ok(); + buffer.update(&mut cx, |buffer, cx| { + buffer.set_language(language, cx); + }); + } + }) + .detach(); + }); + self.editor = editor; - self._editor_subscription = _editor_subscription; + self.editor_subscription = editor_subscription; cx.notify(); } } @@ -565,7 +569,7 @@ impl LspLogView { ) { self.log_store.update(cx, |log_store, cx| { if enabled { - log_store.enable_rpc_trace_for_language_server(&self.project, server_id, cx); + log_store.enable_rpc_trace_for_language_server(&self.project, server_id); } else { log_store.disable_rpc_trace_for_language_server(&self.project, server_id, cx); } @@ -577,6 +581,16 @@ impl LspLogView { } } +fn log_contents(lines: &VecDeque) -> String { + let (a, b) = lines.as_slices(); + let log_contents = a.join("\n"); + if b.is_empty() { + log_contents + } else { + log_contents + "\n" + &b.join("\n") + } +} + impl View for LspLogView { fn ui_name() -> &'static str { "LspLogView" @@ -992,7 +1006,11 @@ impl LspLogToolbarItemView { } pub enum Event { - NewServerLogEntry { id: LanguageServerId, entry: String }, + NewServerLogEntry { + id: LanguageServerId, + entry: String, + is_rpc: bool, + }, } impl Entity for LogStore { From a95cce9a60716e77ecbfaa85eaa9ffaa039cfc60 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 17 Oct 2023 21:47:21 +0300 Subject: [PATCH 6/6] Reduce max log lines, clean log buffers better --- crates/language_tools/src/lsp_log.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index ae63f84b64deff1910328823e6fcb285db8ed685..c75fea256db388166840adf9d55bf18ca3eaabdb 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -24,7 +24,7 @@ use workspace::{ const SEND_LINE: &str = "// Send:"; const RECEIVE_LINE: &str = "// Receive:"; -const MAX_STORED_LOG_ENTRIES: usize = 5000; +const MAX_STORED_LOG_ENTRIES: usize = 2000; pub struct LogStore { projects: HashMap, ProjectState>, @@ -235,7 +235,7 @@ impl LogStore { }; let log_lines = &mut language_server_state.log_messages; - if log_lines.len() == MAX_STORED_LOG_ENTRIES { + while log_lines.len() >= MAX_STORED_LOG_ENTRIES { log_lines.pop_front(); } let message = message.trim(); @@ -335,9 +335,6 @@ impl LogStore { }; let rpc_log_lines = &mut state.rpc_messages; - if rpc_log_lines.len() == MAX_STORED_LOG_ENTRIES { - rpc_log_lines.pop_front(); - } if state.last_message_kind != Some(kind) { let line_before_message = match kind { MessageKind::Send => SEND_LINE, @@ -351,7 +348,7 @@ impl LogStore { }); } - if rpc_log_lines.len() == MAX_STORED_LOG_ENTRIES { + while rpc_log_lines.len() >= MAX_STORED_LOG_ENTRIES { rpc_log_lines.pop_front(); } let message = message.trim();