From babf846ef9130eb19c4cdab858a9f54262b00641 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Fri, 13 Jun 2025 00:22:07 -0600 Subject: [PATCH] Fix newlines in language server logs when switching log types + misc (#32659) Mistake in #31863 where the stored log entries no longer had a format that could simply have `\n` added after each entry. Also fixes a potential crash in the long line folding logic if unicode was in the logs - introduced in #22996. Also updates the log line truncation logic to never exceed the pre-allocated capacity Release Notes: - N/A --- crates/language_tools/src/lsp_log.rs | 87 +++++++++++++++------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index f88b474ded..bddfbc5c71 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -6,6 +6,7 @@ use gpui::{ AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div, }; +use itertools::Itertools; use language::{LanguageServerId, language_settings::SoftWrap}; use lsp::{ IoKind, LanguageServer, LanguageServerName, MessageType, SetTraceParams, TraceValue, @@ -20,8 +21,8 @@ use workspace::{ searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, }; -const SEND_LINE: &str = "// Send:\n"; -const RECEIVE_LINE: &str = "// Receive:\n"; +const SEND_LINE: &str = "\n// Send:"; +const RECEIVE_LINE: &str = "\n// Receive:"; const MAX_STORED_LOG_ENTRIES: usize = 2000; pub struct LogStore { @@ -421,7 +422,7 @@ impl LogStore { log_lines, id, LogMessage { - message: message.trim_end().to_string(), + message: message.trim().to_string(), typ, }, language_server_state.log_level, @@ -444,7 +445,7 @@ impl LogStore { log_lines, id, TraceMessage { - message: message.trim_end().to_string(), + message: message.trim().to_string(), }, (), LogKind::Trace, @@ -461,15 +462,15 @@ impl LogStore { kind: LogKind, cx: &mut Context, ) { - while log_lines.len() >= MAX_STORED_LOG_ENTRIES { + while log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES { log_lines.pop_front(); } - let entry = format!("{}\n", message.as_ref().trim()); + 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, entry, kind }); + cx.emit(Event::NewServerLogEntry { id, kind, text }); cx.notify(); } } @@ -556,6 +557,9 @@ impl LogStore { 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, @@ -565,22 +569,23 @@ impl LogStore { }); cx.emit(Event::NewServerLogEntry { id: language_server_id, - entry: line_before_message.to_string(), kind: LogKind::Rpc, + text: line_before_message.to_string(), }); } - while rpc_log_lines.len() >= MAX_STORED_LOG_ENTRIES { + 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, - entry: format!("{}\n\n", message), kind: LogKind::Rpc, + text: message.to_string(), }); cx.notify(); Some(()) @@ -634,24 +639,37 @@ impl LspLogView { &log_store, window, move |log_view, _, e, window, cx| match e { - Event::NewServerLogEntry { id, entry, kind } => { + 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_point = editor.buffer().read(cx).len(cx); + let last_offset = editor.buffer().read(cx).len(cx); let newest_cursor_is_at_end = - editor.selections.newest::(cx).start >= last_point; - editor.edit(vec![(last_point..last_point, entry.as_str())], cx); - let entry_length = entry.len(); - if entry_length > 1024 { - editor.fold_ranges( - vec![last_point + 1024..last_point + entry_length], - false, - window, - cx, - ); + 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 { + if let Some((fold_offset, _)) = + text.char_indices().dropping(1024).next() + { + if 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 { @@ -997,23 +1015,12 @@ impl LspLogView { } } -fn log_filter(line: &T, cmp: ::Level) -> Option<&str> { - if line.should_include(cmp) { - Some(line.as_ref()) - } else { - None - } -} - -fn log_contents(lines: &VecDeque, cmp: ::Level) -> String { - let (a, b) = lines.as_slices(); - let a = a.iter().filter_map(move |v| log_filter(v, cmp)); - let b = b.iter().filter_map(move |v| log_filter(v, cmp)); - a.chain(b).fold(String::new(), |mut acc, el| { - acc.push_str(el); - acc.push('\n'); - acc - }) +fn log_contents(lines: &VecDeque, level: ::Level) -> String { + lines + .iter() + .filter(|message| message.should_include(level)) + .flat_map(|message| [message.as_ref(), "\n"]) + .collect() } impl Render for LspLogView { @@ -1597,8 +1604,8 @@ impl LspLogToolbarItemView { pub enum Event { NewServerLogEntry { id: LanguageServerId, - entry: String, kind: LogKind, + text: String, }, }