Fix newlines in language server logs when switching log types + misc (#32659)

Michael Sloan created

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

Change summary

crates/language_tools/src/lsp_log.rs | 87 ++++++++++++++++-------------
1 file changed, 47 insertions(+), 40 deletions(-)

Detailed changes

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<Self>,
     ) {
-        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::<usize>(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::<usize>(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<T: Message>(line: &T, cmp: <T as Message>::Level) -> Option<&str> {
-    if line.should_include(cmp) {
-        Some(line.as_ref())
-    } else {
-        None
-    }
-}
-
-fn log_contents<T: Message>(lines: &VecDeque<T>, cmp: <T as Message>::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<T: Message>(lines: &VecDeque<T>, level: <T as Message>::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,
     },
 }