Fix layout of diagnostic blocks (#3729)

Previously, all of the lines in a multi-line error message were painted
on top of each other. I also simplified the logic for highlighting
backtick-enclosed ranges in a diagnostic message.
This commit is contained in:
Max Brunsfeld 2023-12-19 15:49:56 -08:00 committed by GitHub
commit 71d17e268d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 76 deletions

View file

@ -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))
}),

View file

@ -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();
let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic);
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}"))
}
_ => 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();
let mut prev_offset = 0;
let mut inside_block = false;
let mut highlights = initial_highlights;
for (match_ix, (offset, _)) in message
.match_indices('`')
.chain([(message.len(), "")])
.enumerate()
{
message_without_backticks.push_str(&message[prev_offset..offset]);
if inside_block {
highlights.extend(prev_offset - match_ix..offset - match_ix);
}
pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, Vec<Range<usize>>) {
let mut text_without_backticks = String::new();
let mut code_ranges = Vec::new();
inside_block = !inside_block;
prev_offset = offset + 1;
if let Some(source) = &diagnostic.source {
text_without_backticks.push_str(&source);
code_ranges.push(0..source.len());
text_without_backticks.push_str(": ");
}
(message_without_backticks, highlights)
let mut prev_offset = 0;
let mut in_code_block = false;
for (ix, _) in diagnostic
.message
.match_indices('`')
.chain([(diagnostic.message.len(), "")])
{
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;
}
}
(text_without_backticks.into(), code_ranges)
}
pub fn diagnostic_style(