From 59f160a30affb0164dc345f2a4740f6036d540ce Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 29 Sep 2023 13:13:04 +0100 Subject: [PATCH] Introduce the ability to include or exclude warnings from project diagnostics (#3056) ![CleanShot 2023-09-27 at 18 09 37](https://github.com/zed-industries/zed/assets/482957/317d31e4-81f8-44d8-b94f-8ca7150d3fd2) Release Notes: - Added the ability to exclude warnings from project diagnostics. By default, they will be on but they can be disabled temporarily by clicking on the warnings icon. The default behavior can be changed by changing the new `diagnostics.include_warnings` setting. --- Cargo.lock | 3 + assets/settings/default.json | 5 + crates/diagnostics/Cargo.toml | 3 + crates/diagnostics/src/diagnostics.rs | 31 ++++- .../src/project_diagnostics_settings.rs | 28 +++++ crates/diagnostics/src/toolbar_controls.rs | 115 ++++++++++++++++++ crates/zed/src/zed.rs | 4 + 7 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 crates/diagnostics/src/project_diagnostics_settings.rs create mode 100644 crates/diagnostics/src/toolbar_controls.rs diff --git a/Cargo.lock b/Cargo.lock index 3342bf39b8f6f6b621ed97d5f06a9f1be43c2852..7a44033d71051aed973fc3374000a3af3f07be7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2210,6 +2210,9 @@ dependencies = [ "lsp", "postage", "project", + "schemars", + "serde", + "serde_derive", "serde_json", "settings", "smallvec", diff --git a/assets/settings/default.json b/assets/settings/default.json index c314295c8d0f85a7c6fb87ed48defc6157b3251e..95f99a78e9a058660bef626e5b8575a5c203dd5b 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -227,6 +227,11 @@ }, // Automatically update Zed "auto_update": true, + // Diagnostics configuration. + "diagnostics": { + // Whether to show warnings or not by default. + "include_warnings": true + }, // Git gutter behavior configuration. "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 4e898cca0a523c63ddc834fedfef34f40753efae..b0b2450f053fb9c8925b4bfe0131af24d5d24fa7 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -21,6 +21,9 @@ util = { path = "../util" } workspace = { path = "../workspace" } anyhow.workspace = true +schemars.workspace = true +serde.workspace = true +serde_derive.workspace = true smallvec.workspace = true postage.workspace = true diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index ac45bcbb79df790c1841978f4dc187933ecfcd44..0b1c6f8470369cbe18538e15b362258756d1aaeb 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1,4 +1,6 @@ pub mod items; +mod project_diagnostics_settings; +mod toolbar_controls; use anyhow::Result; use collections::{BTreeSet, HashSet}; @@ -19,6 +21,7 @@ use language::{ }; use lsp::LanguageServerId; use project::{DiagnosticSummary, Project, ProjectPath}; +use project_diagnostics_settings::ProjectDiagnosticsSettings; use serde_json::json; use smallvec::SmallVec; use std::{ @@ -30,18 +33,21 @@ 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) { + settings::register::(cx); cx.add_action(ProjectDiagnosticsEditor::deploy); + cx.add_action(ProjectDiagnosticsEditor::toggle_warnings); items::init(cx); } @@ -55,6 +61,7 @@ struct ProjectDiagnosticsEditor { excerpts: ModelHandle, path_states: Vec, paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>, + include_warnings: bool, } struct PathState { @@ -187,6 +194,7 @@ impl ProjectDiagnosticsEditor { editor, path_states: Default::default(), paths_to_update, + include_warnings: settings::get::(cx).include_warnings, }; this.update_excerpts(None, cx); this @@ -204,6 +212,18 @@ impl ProjectDiagnosticsEditor { } } + fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext) { + 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, @@ -277,14 +297,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 { @@ -1501,6 +1525,7 @@ mod tests { client::init_settings(cx); workspace::init_settings(cx); Project::init_settings(cx); + crate::init(cx); }); } diff --git a/crates/diagnostics/src/project_diagnostics_settings.rs b/crates/diagnostics/src/project_diagnostics_settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..1592d3c7f0a1b4837bfd38a1db1aba90c69b3b3a --- /dev/null +++ b/crates/diagnostics/src/project_diagnostics_settings.rs @@ -0,0 +1,28 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Debug)] +pub struct ProjectDiagnosticsSettings { + pub include_warnings: bool, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct ProjectDiagnosticsSettingsContent { + include_warnings: Option, +} + +impl settings::Setting for ProjectDiagnosticsSettings { + const KEY: Option<&'static str> = Some("diagnostics"); + type FileContent = ProjectDiagnosticsSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _cx: &gpui::AppContext, + ) -> anyhow::Result + where + Self: Sized, + { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/diagnostics/src/toolbar_controls.rs b/crates/diagnostics/src/toolbar_controls.rs new file mode 100644 index 0000000000000000000000000000000000000000..421571eede19062429c1ce296ccef3968222ed3b --- /dev/null +++ b/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>, +} + +impl Entity for ToolbarControls { + type Event = (); +} + +impl View for ToolbarControls { + fn ui_name() -> &'static str { + "ToolbarControls" + } + + fn render(&mut self, cx: &mut ViewContext) -> AnyElement { + 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, + ) -> ToolbarItemLocation { + if let Some(pane_item) = active_pane_item.as_ref() { + if let Some(editor) = pane_item.downcast::() { + 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), +>( + index: usize, + icon: &'static str, + toggled: bool, + tooltip: (String, Option>), + cx: &mut ViewContext, + on_click: F, +) -> AnyElement { + enum Button {} + + let theme = theme::current(cx); + let (tooltip_text, action) = tooltip; + + MouseEventHandler::new::(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::