Fix layout of diagnostic blocks

Max Brunsfeld created

Change summary

crates/diagnostics2/src/diagnostics.rs |  23 +++-
crates/editor2/src/editor.rs           | 122 ++++++++++++---------------
2 files changed, 72 insertions(+), 73 deletions(-)

Detailed changes

crates/diagnostics2/src/diagnostics.rs 🔗

@@ -14,9 +14,9 @@ use editor::{
 use futures::future::try_join_all;
 use gpui::{
     actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusHandle,
-    Focusable, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render,
-    SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
-    WindowContext,
+    Focusable, FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model,
+    ParentElement, Render, SharedString, Styled, StyledText, Subscription, Task, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
 };
 use language::{
     Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -36,7 +36,7 @@ use std::{
 };
 use theme::ActiveTheme;
 pub use toolbar_controls::ToolbarControls;
-use ui::{h_stack, prelude::*, HighlightedLabel, Icon, IconElement, Label};
+use ui::{h_stack, prelude::*, Icon, IconElement, Label};
 use util::TryFutureExt;
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -785,8 +785,10 @@ impl Item for ProjectDiagnosticsEditor {
 }
 
 fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
-    let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message);
-    Arc::new(move |_| {
+    let (message, code_ranges) = highlight_diagnostic_message(&diagnostic);
+    let message: SharedString = message.into();
+    Arc::new(move |cx| {
+        let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
         h_stack()
             .id("diagnostic header")
             .py_2()
@@ -809,7 +811,14 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
                     .child(
                         h_stack()
                             .gap_1()
-                            .child(HighlightedLabel::new(message.clone(), highlights.clone()))
+                            .child(
+                                StyledText::new(message.clone()).with_highlights(
+                                    &cx.text_style(),
+                                    code_ranges
+                                        .iter()
+                                        .map(|range| (range.clone(), highlight_style)),
+                                ),
+                            )
                             .when_some(diagnostic.code.as_ref(), |stack, code| {
                                 stack.child(Label::new(format!("({code})")).color(Color::Muted))
                             }),

crates/editor2/src/editor.rs 🔗

@@ -9719,90 +9719,80 @@ impl InvalidationRegion for SnippetState {
     }
 }
 
-// impl Deref for EditorStyle {
-//     type Target = theme::Editor;
-
-//     fn deref(&self) -> &Self::Target {
-//         &self.theme
-//     }
-// }
-
 pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> RenderBlock {
-    let mut highlighted_lines = Vec::new();
-
-    for (index, line) in diagnostic.message.lines().enumerate() {
-        let line = match &diagnostic.source {
-            Some(source) if index == 0 => {
-                let source_highlight = Vec::from_iter(0..source.len());
-                highlight_diagnostic_message(source_highlight, &format!("{source}: {line}"))
-            }
+    let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic);
 
-            _ => highlight_diagnostic_message(Vec::new(), line),
-        };
-        highlighted_lines.push(line);
-    }
     Arc::new(move |cx: &mut BlockContext| {
-        let copy_id: SharedString = format!("copy-{}", cx.block_id.clone()).to_string().into();
+        let color = Some(cx.theme().colors().text_accent);
+        let group_id: SharedString = cx.block_id.to_string().into();
         // TODO: Nate: We should tint the background of the block with the severity color
         // We need to extend the theme before we can do this
-        v_stack()
+        h_stack()
             .id(cx.block_id)
+            .group(group_id.clone())
             .relative()
+            .pl(cx.anchor_x)
             .size_full()
-            .bg(gpui::red())
-            .children(highlighted_lines.iter().map(|(line, highlights)| {
-                let group_id = cx.block_id.to_string();
-                h_stack()
-                    .group(group_id.clone())
-                    .gap_2()
-                    .absolute()
-                    .left(cx.anchor_x)
-                    .px_1p5()
-                    .child(HighlightedLabel::new(line.clone(), highlights.clone()))
-                    .child(
-                        div().z_index(1).child(
-                            IconButton::new(copy_id.clone(), Icon::Copy)
-                                .icon_color(Color::Muted)
-                                .size(ButtonSize::Compact)
-                                .style(ButtonStyle::Transparent)
-                                .visible_on_hover(group_id)
-                                .on_click(cx.listener({
-                                    let message = diagnostic.message.clone();
-                                    move |_, _, cx| {
-                                        cx.write_to_clipboard(ClipboardItem::new(message.clone()))
-                                    }
-                                }))
-                                .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
-                        ),
-                    )
-            }))
+            .gap_2()
+            .child(
+                StyledText::new(text_without_backticks.clone()).with_highlights(
+                    &cx.text_style(),
+                    code_ranges.iter().map(|range| {
+                        (
+                            range.clone(),
+                            HighlightStyle {
+                                color,
+                                ..Default::default()
+                            },
+                        )
+                    }),
+                ),
+            )
+            .child(
+                IconButton::new(("copy-block", cx.block_id), Icon::Copy)
+                    .icon_color(Color::Muted)
+                    .size(ButtonSize::Compact)
+                    .style(ButtonStyle::Transparent)
+                    .visible_on_hover(group_id)
+                    .on_click(cx.listener({
+                        let message = diagnostic.message.clone();
+                        move |_, _, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
+                    }))
+                    .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
+            )
             .into_any_element()
     })
 }
 
-pub fn highlight_diagnostic_message(
-    initial_highlights: Vec<usize>,
-    message: &str,
-) -> (String, Vec<usize>) {
-    let mut message_without_backticks = String::new();
+pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, Vec<Range<usize>>) {
+    let mut text_without_backticks = String::new();
+    let mut code_ranges = Vec::new();
+
+    if let Some(source) = &diagnostic.source {
+        text_without_backticks.push_str(&source);
+        code_ranges.push(0..source.len());
+        text_without_backticks.push_str(": ");
+    }
+
     let mut prev_offset = 0;
-    let mut inside_block = false;
-    let mut highlights = initial_highlights;
-    for (match_ix, (offset, _)) in message
+    let mut in_code_block = false;
+    for (ix, _) in diagnostic
+        .message
         .match_indices('`')
-        .chain([(message.len(), "")])
-        .enumerate()
+        .chain([(diagnostic.message.len(), "")])
     {
-        message_without_backticks.push_str(&message[prev_offset..offset]);
-        if inside_block {
-            highlights.extend(prev_offset - match_ix..offset - match_ix);
+        let prev_len = text_without_backticks.len();
+        text_without_backticks.push_str(&diagnostic.message[prev_offset..ix]);
+        prev_offset = ix + 1;
+        if in_code_block {
+            code_ranges.push(prev_len..text_without_backticks.len());
+            in_code_block = false;
+        } else {
+            in_code_block = true;
         }
-
-        inside_block = !inside_block;
-        prev_offset = offset + 1;
     }
 
-    (message_without_backticks, highlights)
+    (text_without_backticks.into(), code_ranges)
 }
 
 pub fn diagnostic_style(