Improve diagnostic header UI (#9888)

Mikayla Maki created

This PR rearranges the diagnostics to put the headers to the left of the
diagnostic messages and adds an additional button to close the
diagnostics.

<img width="394" alt="Screenshot 2024-03-27 at 2 01 19 PM"
src="https://github.com/zed-industries/zed/assets/2280405/83be4051-6441-47c6-9b48-77c75ce9c8eb">

<img width="326" alt="Screenshot 2024-03-27 at 2 01 56 PM"
src="https://github.com/zed-industries/zed/assets/2280405/d849ca34-91e9-4de6-9d9c-503b75e97d60">

As a drive by, I also quieted a useless but loud log message.

Release Notes:

- Added a close button to the `f8` diagnostics.

Change summary

crates/copilot/src/copilot.rs                  |  2 
crates/editor/src/editor.rs                    | 62 +++++++++++++------
crates/gpui/src/geometry.rs                    |  9 ++
crates/ui/src/components/button/button_like.rs |  4 
4 files changed, 54 insertions(+), 23 deletions(-)

Detailed changes

crates/copilot/src/copilot.rs 🔗

@@ -797,7 +797,7 @@ impl Copilot {
     ) -> Task<Result<()>> {
         let server = match self.server.as_authenticated() {
             Ok(server) => server,
-            Err(error) => return Task::ready(Err(error)),
+            Err(_) => return Task::ready(Ok(())),
         };
         let request =
             server

crates/editor/src/editor.rs 🔗

@@ -59,12 +59,12 @@ use fuzzy::{StringMatch, StringMatchCandidate};
 use git::diff_hunk_to_display;
 use gpui::{
     div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
-    AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
-    DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
-    FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton,
-    ParentElement, Pixels, Render, SharedString, StrikethroughStyle, Styled, StyledText,
-    Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, ViewContext,
-    ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
+    AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds,
+    ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView,
+    FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model,
+    MouseButton, ParentElement, Pixels, Render, SharedString, StrikethroughStyle, Styled,
+    StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View,
+    ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -10499,6 +10499,41 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
         let mut text_style = cx.text_style().clone();
         text_style.color = diagnostic_style(diagnostic.severity, true, cx.theme().status());
 
+        let multi_line_diagnostic = diagnostic.message.contains('\n');
+
+        let buttons = |diagnostic: &Diagnostic, block_id: usize| {
+            if multi_line_diagnostic {
+                v_flex()
+            } else {
+                h_flex()
+            }
+            .children(diagnostic.is_primary.then(|| {
+                IconButton::new(("close-block", block_id), IconName::XCircle)
+                    .icon_color(Color::Muted)
+                    .size(ButtonSize::Compact)
+                    .style(ButtonStyle::Transparent)
+                    .visible_on_hover(group_id.clone())
+                    .on_click(move |_click, cx| cx.dispatch_action(Box::new(Cancel)))
+                    .tooltip(|cx| Tooltip::for_action("Close Diagnostics", &Cancel, cx))
+            }))
+            .child(
+                IconButton::new(("copy-block", block_id), IconName::Copy)
+                    .icon_color(Color::Muted)
+                    .size(ButtonSize::Compact)
+                    .style(ButtonStyle::Transparent)
+                    .visible_on_hover(group_id.clone())
+                    .on_click({
+                        let message = diagnostic.message.clone();
+                        move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
+                    })
+                    .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
+            )
+        };
+
+        let icon_size = buttons(&diagnostic, cx.block_id)
+            .into_any_element()
+            .measure(AvailableSpace::min_size(), cx);
+
         h_flex()
             .id(cx.block_id)
             .group(group_id.clone())
@@ -10509,9 +10544,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
             .child(
                 div()
                     .flex()
-                    .w(cx.anchor_x - cx.gutter_dimensions.width)
+                    .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
                     .flex_shrink(),
             )
+            .child(buttons(&diagnostic, cx.block_id))
             .child(div().flex().flex_shrink_0().child(
                 StyledText::new(text_without_backticks.clone()).with_highlights(
                     &text_style,
@@ -10526,18 +10562,6 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
                     }),
                 ),
             ))
-            .child(
-                IconButton::new(("copy-block", cx.block_id), IconName::Copy)
-                    .icon_color(Color::Muted)
-                    .size(ButtonSize::Compact)
-                    .style(ButtonStyle::Transparent)
-                    .visible_on_hover(group_id)
-                    .on_click({
-                        let message = diagnostic.message.clone();
-                        move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
-                    })
-                    .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
-            )
             .into_any_element()
     })
 }

crates/gpui/src/geometry.rs 🔗

@@ -2480,6 +2480,13 @@ impl From<ScaledPixels> for f64 {
 #[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg, PartialEq)]
 pub struct Rems(pub f32);
 
+impl Rems {
+    /// Convert this Rem value to pixels.
+    pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
+        *self * rem_size
+    }
+}
+
 impl Mul<Pixels> for Rems {
     type Output = Pixels;
 
@@ -2555,7 +2562,7 @@ impl AbsoluteLength {
     pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
         match self {
             AbsoluteLength::Pixels(pixels) => *pixels,
-            AbsoluteLength::Rems(rems) => *rems * rem_size,
+            AbsoluteLength::Rems(rems) => rems.to_pixels(rem_size),
         }
     }
 }

crates/ui/src/components/button/button_like.rs 🔗

@@ -276,7 +276,7 @@ pub enum ButtonSize {
 }
 
 impl ButtonSize {
-    fn height(self) -> Rems {
+    pub fn rems(self) -> Rems {
         match self {
             ButtonSize::Large => rems_from_px(32.),
             ButtonSize::Default => rems_from_px(22.),
@@ -424,7 +424,7 @@ impl RenderOnce for ButtonLike {
             .id(self.id.clone())
             .group("")
             .flex_none()
-            .h(self.height.unwrap_or(self.size.height().into()))
+            .h(self.height.unwrap_or(self.size.rems().into()))
             .when_some(self.width, |this, width| this.w(width).justify_center())
             .when_some(self.rounding, |this, rounding| match rounding {
                 ButtonLikeRounding::All => this.rounded_md(),