Add an include/exclude warnings toggle in project diagnostics

Antonio Scandurra created

Change summary

crates/diagnostics/src/diagnostics.rs      |  27 +++++
crates/diagnostics/src/toolbar_controls.rs | 115 ++++++++++++++++++++++++
crates/zed/src/zed.rs                      |   4 
3 files changed, 143 insertions(+), 3 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -1,4 +1,5 @@
 pub mod items;
+mod toolbar_controls;
 
 use anyhow::Result;
 use collections::{BTreeSet, HashSet};
@@ -30,18 +31,20 @@ use std::{
     sync::Arc,
 };
 use theme::ThemeSettings;
+pub use toolbar_controls::ToolbarControls;
 use util::TryFutureExt;
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
     ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace,
 };
 
-actions!(diagnostics, [Deploy]);
+actions!(diagnostics, [Deploy, ToggleWarnings]);
 
 const CONTEXT_LINE_COUNT: u32 = 1;
 
 pub fn init(cx: &mut AppContext) {
     cx.add_action(ProjectDiagnosticsEditor::deploy);
+    cx.add_action(ProjectDiagnosticsEditor::toggle_warnings);
     items::init(cx);
 }
 
@@ -55,6 +58,7 @@ struct ProjectDiagnosticsEditor {
     excerpts: ModelHandle<MultiBuffer>,
     path_states: Vec<PathState>,
     paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
+    include_warnings: bool,
 }
 
 struct PathState {
@@ -187,6 +191,7 @@ impl ProjectDiagnosticsEditor {
             editor,
             path_states: Default::default(),
             paths_to_update,
+            include_warnings: true,
         };
         this.update_excerpts(None, cx);
         this
@@ -204,6 +209,18 @@ impl ProjectDiagnosticsEditor {
         }
     }
 
+    fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
+        self.include_warnings = !self.include_warnings;
+        self.paths_to_update = self
+            .project
+            .read(cx)
+            .diagnostic_summaries(cx)
+            .map(|(path, server_id, _)| (path, server_id))
+            .collect();
+        self.update_excerpts(None, cx);
+        cx.notify();
+    }
+
     fn update_excerpts(
         &mut self,
         language_server_id: Option<LanguageServerId>,
@@ -277,14 +294,18 @@ impl ProjectDiagnosticsEditor {
         let mut blocks_to_add = Vec::new();
         let mut blocks_to_remove = HashSet::default();
         let mut first_excerpt_id = None;
+        let max_severity = if self.include_warnings {
+            DiagnosticSeverity::WARNING
+        } else {
+            DiagnosticSeverity::ERROR
+        };
         let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
             let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
             let mut new_groups = snapshot
                 .diagnostic_groups(language_server_id)
                 .into_iter()
                 .filter(|(_, group)| {
-                    group.entries[group.primary_ix].diagnostic.severity
-                        <= DiagnosticSeverity::WARNING
+                    group.entries[group.primary_ix].diagnostic.severity <= max_severity
                 })
                 .peekable();
             loop {

crates/diagnostics/src/toolbar_controls.rs 🔗

@@ -0,0 +1,115 @@
+use crate::{ProjectDiagnosticsEditor, ToggleWarnings};
+use gpui::{
+    elements::*,
+    platform::{CursorStyle, MouseButton},
+    Action, Entity, EventContext, View, ViewContext, WeakViewHandle,
+};
+use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
+
+pub struct ToolbarControls {
+    editor: Option<WeakViewHandle<ProjectDiagnosticsEditor>>,
+}
+
+impl Entity for ToolbarControls {
+    type Event = ();
+}
+
+impl View for ToolbarControls {
+    fn ui_name() -> &'static str {
+        "ToolbarControls"
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+        let include_warnings = self
+            .editor
+            .as_ref()
+            .and_then(|editor| editor.upgrade(cx))
+            .map(|editor| editor.read(cx).include_warnings)
+            .unwrap_or(false);
+        let tooltip = if include_warnings {
+            "Exclude Warnings".into()
+        } else {
+            "Include Warnings".into()
+        };
+        Flex::row()
+            .with_child(render_toggle_button(
+                0,
+                "icons/warning.svg",
+                include_warnings,
+                (tooltip, Some(Box::new(ToggleWarnings))),
+                cx,
+                move |this, cx| {
+                    if let Some(editor) = this.editor.and_then(|editor| editor.upgrade(cx)) {
+                        editor.update(cx, |editor, cx| {
+                            editor.toggle_warnings(&Default::default(), cx)
+                        });
+                    }
+                },
+            ))
+            .into_any()
+    }
+}
+
+impl ToolbarItemView for ToolbarControls {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        _: &mut ViewContext<Self>,
+    ) -> ToolbarItemLocation {
+        if let Some(pane_item) = active_pane_item.as_ref() {
+            if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
+                self.editor = Some(editor.downgrade());
+                ToolbarItemLocation::PrimaryRight { flex: None }
+            } else {
+                ToolbarItemLocation::Hidden
+            }
+        } else {
+            ToolbarItemLocation::Hidden
+        }
+    }
+}
+
+impl ToolbarControls {
+    pub fn new() -> Self {
+        ToolbarControls { editor: None }
+    }
+}
+
+fn render_toggle_button<
+    F: 'static + Fn(&mut ToolbarControls, &mut EventContext<ToolbarControls>),
+>(
+    index: usize,
+    icon: &'static str,
+    toggled: bool,
+    tooltip: (String, Option<Box<dyn Action>>),
+    cx: &mut ViewContext<ToolbarControls>,
+    on_click: F,
+) -> AnyElement<ToolbarControls> {
+    enum Button {}
+
+    let theme = theme::current(cx);
+    let (tooltip_text, action) = tooltip;
+
+    MouseEventHandler::new::<Button, _>(index, cx, |mouse_state, _| {
+        let style = theme
+            .workspace
+            .toolbar
+            .toggleable_tool
+            .in_state(toggled)
+            .style_for(mouse_state);
+        Svg::new(icon)
+            .with_color(style.color)
+            .constrained()
+            .with_width(style.icon_width)
+            .aligned()
+            .constrained()
+            .with_width(style.button_width)
+            .with_height(style.button_width)
+            .contained()
+            .with_style(style.container)
+    })
+    .with_cursor_style(CursorStyle::PointingHand)
+    .on_click(MouseButton::Left, move |_, view, cx| on_click(view, cx))
+    .with_tooltip::<Button>(index, tooltip_text, action, theme.tooltip.clone(), cx)
+    .into_any_named("quick action bar button")
+}

crates/zed/src/zed.rs 🔗

@@ -275,6 +275,10 @@ pub fn initialize_workspace(
                                     QuickActionBar::new(buffer_search_bar, workspace)
                                 });
                                 toolbar.add_item(quick_action_bar, cx);
+                                let diagnostic_editor_controls = cx.add_view(|_| {
+                                    diagnostics::ToolbarControls::new()
+                                });
+                                toolbar.add_item(diagnostic_editor_controls, cx);
                                 let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
                                 toolbar.add_item(project_search_bar, cx);
                                 let submit_feedback_button =