Allow to temporarily toggle diagnostics in the editor (#30316)

Kirill Bulatov created

* Adds a `diagnostics_max_severity: null` editor settings that has
previous hardcoded default, `warning`
* Make inline diagnostics to inherit this setting by default (can be
overridden with its own max_severity setting)
* Allows to toggle diagnostics in the editor menu and via new action,
`editor::ToggleDiagnostics`

Closes https://github.com/zed-industries/zed/issues/4686

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

assets/settings/default.json              |  19 +++
crates/diagnostics/src/diagnostics.rs     |  28 ++++
crates/editor/src/actions.rs              |   1 
crates/editor/src/display_map.rs          |  56 +++++++---
crates/editor/src/display_map/fold_map.rs |   3 
crates/editor/src/editor.rs               | 133 ++++++++++++++++++------
crates/editor/src/editor_settings.rs      |  16 +++
crates/editor/src/element.rs              |  51 ++++----
crates/editor/src/movement.rs             |   4 
crates/editor/src/test.rs                 |   3 
crates/project/src/project_settings.rs    |  18 +++
crates/zed/src/zed/quick_action_bar.rs    |  43 ++++++-
12 files changed, 279 insertions(+), 96 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -218,6 +218,23 @@
   // 1. Do nothing: `none`
   // 2. Find references for the same symbol: `find_all_references` (default)
   "go_to_definition_fallback": "find_all_references",
+  // Which level to use to filter out diagnostics displayed in the editor.
+  //
+  // Affects the editor rendering only, and does not interrupt
+  // the functionality of diagnostics fetching and project diagnostics editor.
+  // Which files containing diagnostic errors/warnings to mark in the tabs.
+  // Diagnostics are only shown when file icons are also active.
+  // This setting only works when can take the following three values:
+  //
+  // Which diagnostic indicators to show in the scrollbar, their level should be more or equal to the specified severity level.
+  // Possible values:
+  //  - "off" — no diagnostics are allowed
+  //  - "error"
+  //  - "warning" (default)
+  //  - "info"
+  //  - "hint"
+  //  - null — allow all diagnostics
+  "diagnostics_max_severity": "warning",
   // Whether to show wrap guides (vertical rulers) in the editor.
   // Setting this to true will show a guide at the 'preferred_line_length' value
   // if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -1002,7 +1019,7 @@
       // longer than this value will still push diagnostics further to the right.
       "min_column": 0,
       // The minimum severity of the diagnostics to show inline.
-      // Shows all diagnostics when not specified.
+      // Inherits editor's diagnostics' max severity settings when `null`.
       "max_severity": null
     },
     "cargo": {

crates/diagnostics/src/diagnostics.rs 🔗

@@ -23,11 +23,10 @@ use gpui::{
 use language::{
     Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, Point, ToTreeSitterPoint,
 };
-use lsp::DiagnosticSeverity;
 use project::{
     DiagnosticSummary, Project, ProjectPath,
     lsp_store::rust_analyzer_ext::{cancel_flycheck, run_flycheck},
-    project_settings::ProjectSettings,
+    project_settings::{DiagnosticSeverity, ProjectSettings},
 };
 use settings::Settings;
 use std::{
@@ -203,6 +202,14 @@ impl ProjectDiagnosticsEditor {
                 Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), window, cx);
             editor.set_vertical_scroll_margin(5, cx);
             editor.disable_inline_diagnostics();
+            editor.set_max_diagnostics_severity(
+                if include_warnings {
+                    DiagnosticSeverity::Warning
+                } else {
+                    DiagnosticSeverity::Error
+                },
+                cx,
+            );
             editor.set_all_diagnostics_active(cx);
             editor
         });
@@ -224,7 +231,18 @@ impl ProjectDiagnosticsEditor {
         )
         .detach();
         cx.observe_global_in::<IncludeWarnings>(window, |this, window, cx| {
-            this.include_warnings = cx.global::<IncludeWarnings>().0;
+            let include_warnings = cx.global::<IncludeWarnings>().0;
+            this.include_warnings = include_warnings;
+            this.editor.update(cx, |editor, cx| {
+                editor.set_max_diagnostics_severity(
+                    if include_warnings {
+                        DiagnosticSeverity::Warning
+                    } else {
+                        DiagnosticSeverity::Error
+                    },
+                    cx,
+                )
+            });
             this.diagnostics.clear();
             this.update_all_diagnostics(false, window, cx);
         })
@@ -488,9 +506,9 @@ impl ProjectDiagnosticsEditor {
         let buffer_snapshot = buffer.read(cx).snapshot();
         let buffer_id = buffer_snapshot.remote_id();
         let max_severity = if self.include_warnings {
-            DiagnosticSeverity::WARNING
+            lsp::DiagnosticSeverity::WARNING
         } else {
-            DiagnosticSeverity::ERROR
+            lsp::DiagnosticSeverity::ERROR
         };
 
         cx.spawn_in(window, async move |this, mut cx| {

crates/editor/src/actions.rs 🔗

@@ -425,6 +425,7 @@ actions!(
         ToggleAutoSignatureHelp,
         ToggleGitBlameInline,
         OpenGitBlameCommit,
+        ToggleDiagnostics,
         ToggleIndentGuides,
         ToggleInlayHints,
         ToggleInlineValues,

crates/editor/src/display_map.rs 🔗

@@ -47,12 +47,13 @@ pub use invisibles::{is_invisible, replacement};
 use language::{
     OffsetUtf16, Point, Subscription as BufferSubscription, language_settings::language_settings,
 };
-use lsp::DiagnosticSeverity;
 use multi_buffer::{
     Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferPoint, MultiBufferRow,
     MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
 };
+use project::project_settings::DiagnosticSeverity;
 use serde::Deserialize;
+
 use std::{
     any::TypeId,
     borrow::Cow,
@@ -109,6 +110,7 @@ pub struct DisplayMap {
     pub(crate) fold_placeholder: FoldPlaceholder,
     pub clip_at_line_ends: bool,
     pub(crate) masked: bool,
+    pub(crate) diagnostics_max_severity: DiagnosticSeverity,
 }
 
 impl DisplayMap {
@@ -120,6 +122,7 @@ impl DisplayMap {
         buffer_header_height: u32,
         excerpt_header_height: u32,
         fold_placeholder: FoldPlaceholder,
+        diagnostics_max_severity: DiagnosticSeverity,
         cx: &mut Context<Self>,
     ) -> Self {
         let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
@@ -145,6 +148,7 @@ impl DisplayMap {
             block_map,
             crease_map,
             fold_placeholder,
+            diagnostics_max_severity,
             text_highlights: Default::default(),
             inlay_highlights: Default::default(),
             clip_at_line_ends: false,
@@ -171,6 +175,7 @@ impl DisplayMap {
             tab_snapshot,
             wrap_snapshot,
             block_snapshot,
+            diagnostics_max_severity: self.diagnostics_max_severity,
             crease_snapshot: self.crease_map.snapshot(),
             text_highlights: self.text_highlights.clone(),
             inlay_highlights: self.inlay_highlights.clone(),
@@ -745,6 +750,7 @@ pub struct DisplaySnapshot {
     inlay_highlights: InlayHighlights,
     clip_at_line_ends: bool,
     masked: bool,
+    diagnostics_max_severity: DiagnosticSeverity,
     pub(crate) fold_placeholder: FoldPlaceholder,
 }
 
@@ -947,21 +953,22 @@ impl DisplaySnapshot {
 
             let mut diagnostic_highlight = HighlightStyle::default();
 
-            if chunk.is_unnecessary {
-                diagnostic_highlight.fade_out = Some(editor_style.unnecessary_code_fade);
-            }
-
-            // Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
             if let Some(severity) = chunk.diagnostic_severity.filter(|severity| {
-                editor_style.show_underlines
-                    && (!chunk.is_unnecessary || *severity <= DiagnosticSeverity::WARNING)
+                self.diagnostics_max_severity
+                    .into_lsp()
+                    .map_or(false, |max_severity| severity <= &max_severity)
             }) {
-                let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
-                diagnostic_highlight.underline = Some(UnderlineStyle {
-                    color: Some(diagnostic_color),
-                    thickness: 1.0.into(),
-                    wavy: true,
-                });
+                if chunk.is_unnecessary {
+                    diagnostic_highlight.fade_out = Some(editor_style.unnecessary_code_fade);
+                }
+                if editor_style.show_underlines {
+                    let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
+                    diagnostic_highlight.underline = Some(UnderlineStyle {
+                        color: Some(diagnostic_color),
+                        thickness: 1.0.into(),
+                        wavy: true,
+                    });
+                }
             }
 
             if let Some(highlight_style) = highlight_style.as_mut() {
@@ -1546,6 +1553,7 @@ pub mod tests {
                 buffer_start_excerpt_header_height,
                 excerpt_header_height,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -1794,6 +1802,7 @@ pub mod tests {
                     1,
                     1,
                     FoldPlaceholder::test(),
+                    DiagnosticSeverity::Warning,
                     cx,
                 )
             });
@@ -1903,6 +1912,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -1964,6 +1974,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -2057,6 +2068,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -2157,6 +2169,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -2238,7 +2251,7 @@ pub mod tests {
                     [DiagnosticEntry {
                         range: PointUtf16::new(0, 0)..PointUtf16::new(2, 1),
                         diagnostic: Diagnostic {
-                            severity: DiagnosticSeverity::ERROR,
+                            severity: lsp::DiagnosticSeverity::ERROR,
                             group_id: 1,
                             message: "hi".into(),
                             ..Default::default()
@@ -2262,6 +2275,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -2297,7 +2311,7 @@ pub mod tests {
         });
 
         let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
-        let mut chunks = Vec::<(String, Option<DiagnosticSeverity>, Rgba)>::new();
+        let mut chunks = Vec::<(String, Option<lsp::DiagnosticSeverity>, Rgba)>::new();
         for chunk in snapshot.chunks(DisplayRow(0)..DisplayRow(5), true, Default::default()) {
             let color = chunk
                 .highlight_style
@@ -2318,11 +2332,11 @@ pub mod tests {
             [
                 (
                     "struct A {\n    b: usize;\n".into(),
-                    Some(DiagnosticSeverity::ERROR),
+                    Some(lsp::DiagnosticSeverity::ERROR),
                     black
                 ),
                 ("\n".into(), None, black),
-                ("}".into(), Some(DiagnosticSeverity::ERROR), black),
+                ("}".into(), Some(lsp::DiagnosticSeverity::ERROR), black),
                 ("\nconst c: ".into(), None, black),
                 ("usize".into(), None, red),
                 (" = ".into(), None, black),
@@ -2350,6 +2364,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -2491,6 +2506,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -2573,6 +2589,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -2697,6 +2714,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             );
             let snapshot = map.buffer.read(cx).snapshot(cx);
@@ -2734,6 +2752,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -2809,6 +2828,7 @@ pub mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });

crates/editor/src/display_map/fold_map.rs 🔗

@@ -4,7 +4,6 @@ use super::{
 };
 use gpui::{AnyElement, App, ElementId, HighlightStyle, Pixels, Window};
 use language::{Edit, HighlightId, Point, TextSummary};
-use lsp::DiagnosticSeverity;
 use multi_buffer::{
     Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
 };
@@ -1253,7 +1252,7 @@ pub struct Chunk<'a> {
     /// the editor.
     pub highlight_style: Option<HighlightStyle>,
     /// The severity of diagnostic associated with this chunk, if any.
-    pub diagnostic_severity: Option<DiagnosticSeverity>,
+    pub diagnostic_severity: Option<lsp::DiagnosticSeverity>,
     /// Whether this chunk of text is marked as unnecessary.
     pub is_unnecessary: bool,
     /// Whether this chunk of text was originally a tab character.

crates/editor/src/editor.rs 🔗

@@ -129,6 +129,7 @@ use project::{
         },
         session::{Session, SessionEvent},
     },
+    project_settings::DiagnosticSeverity,
 };
 
 pub use git::blame::BlameRenderer;
@@ -141,8 +142,8 @@ use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
 
 pub use lsp::CompletionContext;
 use lsp::{
-    CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
-    InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
+    CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
+    LanguageServerId, LanguageServerName,
 };
 
 use language::BufferSnapshot;
@@ -628,7 +629,7 @@ struct InlineDiagnostic {
     group_id: usize,
     is_primary: bool,
     start: Point,
-    severity: DiagnosticSeverity,
+    severity: lsp::DiagnosticSeverity,
 }
 
 pub enum MenuInlineCompletionsPolicy {
@@ -860,6 +861,7 @@ pub struct Editor {
     snippet_stack: InvalidationStack<SnippetState>,
     select_syntax_node_history: SelectSyntaxNodeHistory,
     ime_transaction: Option<TransactionId>,
+    pub diagnostics_max_severity: DiagnosticSeverity,
     active_diagnostics: ActiveDiagnostic,
     show_inline_diagnostics: bool,
     inline_diagnostics_update: Task<()>,
@@ -1506,6 +1508,15 @@ impl Editor {
             display_map.is_none() || mode.is_minimap(),
             "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
         );
+
+        let full_mode = mode.is_full();
+        let diagnostics_max_severity = if full_mode {
+            EditorSettings::get_global(cx)
+                .diagnostics_max_severity
+                .unwrap_or(DiagnosticSeverity::Hint)
+        } else {
+            DiagnosticSeverity::Off
+        };
         let style = window.text_style();
         let font_size = style.font_size.to_pixels(window.rem_size());
         let editor = cx.entity().downgrade();
@@ -1539,7 +1550,7 @@ impl Editor {
                     .into_any()
             }),
             merge_adjacent: true,
-            ..Default::default()
+            ..FoldPlaceholder::default()
         };
         let display_map = display_map.unwrap_or_else(|| {
             cx.new(|cx| {
@@ -1551,6 +1562,7 @@ impl Editor {
                     FILE_HEADER_HEIGHT,
                     MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
                     fold_placeholder,
+                    diagnostics_max_severity,
                     cx,
                 )
             })
@@ -1678,8 +1690,6 @@ impl Editor {
             code_action_providers.push(Rc::new(project) as Rc<_>);
         }
 
-        let full_mode = mode.is_full();
-
         let mut this = Self {
             focus_handle,
             show_cursor_when_unfocused: false,
@@ -1692,16 +1702,17 @@ impl Editor {
             add_selections_state: None,
             select_next_state: None,
             select_prev_state: None,
-            selection_history: Default::default(),
-            autoclose_regions: Default::default(),
-            snippet_stack: Default::default(),
+            selection_history: SelectionHistory::default(),
+            autoclose_regions: Vec::new(),
+            snippet_stack: InvalidationStack::default(),
             select_syntax_node_history: SelectSyntaxNodeHistory::default(),
-            ime_transaction: Default::default(),
+            ime_transaction: None,
             active_diagnostics: ActiveDiagnostic::None,
             show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
             inline_diagnostics_update: Task::ready(()),
             inline_diagnostics: Vec::new(),
             soft_wrap_mode_override,
+            diagnostics_max_severity,
             hard_wrap: None,
             completion_provider: project.clone().map(|project| Box::new(project) as _),
             semantics_provider: project.clone().map(|project| Rc::new(project) as _),
@@ -1725,7 +1736,7 @@ impl Editor {
             placeholder_text: None,
             highlight_order: 0,
             highlighted_rows: HashMap::default(),
-            background_highlights: Default::default(),
+            background_highlights: TreeMap::default(),
             gutter_highlights: TreeMap::default(),
             scrollbar_marker_state: ScrollbarMarkerState::default(),
             active_indent_guides_state: ActiveIndentGuidesState::default(),
@@ -1733,21 +1744,21 @@ impl Editor {
             context_menu: RefCell::new(None),
             context_menu_options: None,
             mouse_context_menu: None,
-            completion_tasks: Default::default(),
-            inline_blame_popover: Default::default(),
+            completion_tasks: Vec::new(),
+            inline_blame_popover: None,
             signature_help_state: SignatureHelpState::default(),
             auto_signature_help: None,
             find_all_references_task_sources: Vec::new(),
             next_completion_id: 0,
             next_inlay_id: 0,
             code_action_providers,
-            available_code_actions: Default::default(),
-            code_actions_task: Default::default(),
-            quick_selection_highlight_task: Default::default(),
-            debounced_selection_highlight_task: Default::default(),
-            document_highlights_task: Default::default(),
-            linked_editing_range_task: Default::default(),
-            pending_rename: Default::default(),
+            available_code_actions: None,
+            code_actions_task: None,
+            quick_selection_highlight_task: None,
+            debounced_selection_highlight_task: None,
+            document_highlights_task: None,
+            linked_editing_range_task: None,
+            pending_rename: None,
             searchable: true,
             cursor_shape: EditorSettings::get_global(cx)
                 .cursor_shape
@@ -1765,9 +1776,9 @@ impl Editor {
             jsx_tag_auto_close_enabled_in_any_buffer: false,
             leader_id: None,
             remote_id: None,
-            hover_state: Default::default(),
+            hover_state: HoverState::default(),
             pending_mouse_down: None,
-            hovered_link_state: Default::default(),
+            hovered_link_state: None,
             edit_prediction_provider: None,
             active_inline_completion: None,
             stale_inline_completion_in_menu: None,
@@ -1786,7 +1797,7 @@ impl Editor {
             gutter_dimensions: GutterDimensions::default(),
             style: None,
             show_cursor_names: false,
-            hovered_cursors: Default::default(),
+            hovered_cursors: HashMap::default(),
             next_editor_action_id: EditorActionId::default(),
             editor_actions: Rc::default(),
             inline_completions_hidden_for_vim_mode: false,
@@ -1808,7 +1819,7 @@ impl Editor {
                     .restore_unsaved_buffers,
             blame: None,
             blame_subscription: None,
-            tasks: Default::default(),
+            tasks: BTreeMap::default(),
 
             breakpoint_store,
             gutter_breakpoint_indicator: (None, None),
@@ -15093,8 +15104,12 @@ impl Editor {
         self.inline_diagnostics.clear();
     }
 
+    pub fn diagnostics_enabled(&self) -> bool {
+        self.mode.is_full()
+    }
+
     pub fn inline_diagnostics_enabled(&self) -> bool {
-        self.inline_diagnostics_enabled
+        self.diagnostics_enabled() && self.inline_diagnostics_enabled
     }
 
     pub fn show_inline_diagnostics(&self) -> bool {
@@ -15111,6 +15126,43 @@ impl Editor {
         self.refresh_inline_diagnostics(false, window, cx);
     }
 
+    pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
+        self.diagnostics_max_severity = severity;
+        self.display_map.update(cx, |display_map, _| {
+            display_map.diagnostics_max_severity = self.diagnostics_max_severity;
+        });
+    }
+
+    pub fn toggle_diagnostics(
+        &mut self,
+        _: &ToggleDiagnostics,
+        window: &mut Window,
+        cx: &mut Context<Editor>,
+    ) {
+        if !self.diagnostics_enabled() {
+            return;
+        }
+
+        let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
+            EditorSettings::get_global(cx)
+                .diagnostics_max_severity
+                .filter(|severity| severity != &DiagnosticSeverity::Off)
+                .unwrap_or(DiagnosticSeverity::Hint)
+        } else {
+            DiagnosticSeverity::Off
+        };
+        self.set_max_diagnostics_severity(new_severity, cx);
+        if self.diagnostics_max_severity == DiagnosticSeverity::Off {
+            self.active_diagnostics = ActiveDiagnostic::None;
+            self.inline_diagnostics_update = Task::ready(());
+            self.inline_diagnostics.clear();
+        } else {
+            self.refresh_inline_diagnostics(false, window, cx);
+        }
+
+        cx.notify();
+    }
+
     pub fn toggle_minimap(
         &mut self,
         _: &ToggleMinimap,
@@ -15128,9 +15180,16 @@ impl Editor {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
+        let max_severity = ProjectSettings::get_global(cx)
+            .diagnostics
+            .inline
+            .max_severity
+            .unwrap_or(self.diagnostics_max_severity);
+
         if self.mode.is_minimap()
-            || !self.inline_diagnostics_enabled
+            || !self.inline_diagnostics_enabled()
             || !self.show_inline_diagnostics
+            || max_severity == DiagnosticSeverity::Off
         {
             self.inline_diagnostics_update = Task::ready(());
             self.inline_diagnostics.clear();
@@ -18050,6 +18109,14 @@ impl Editor {
     }
 
     fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        let new_severity = if self.diagnostics_enabled() {
+            EditorSettings::get_global(cx)
+                .diagnostics_max_severity
+                .unwrap_or(DiagnosticSeverity::Hint)
+        } else {
+            DiagnosticSeverity::Off
+        };
+        self.set_max_diagnostics_severity(new_severity, cx);
         self.tasks_update_task = Some(self.refresh_runnables(window, cx));
         self.update_edit_prediction_settings(cx);
         self.refresh_inline_completion(true, false, window, cx);
@@ -20471,8 +20538,6 @@ impl Render for Editor {
             EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
         };
 
-        let show_underlines = !self.mode.is_minimap();
-
         EditorElement::new(
             &cx.entity(),
             EditorStyle {
@@ -20485,7 +20550,7 @@ impl Render for Editor {
                 inlay_hints_style: make_inlay_hints_style(cx),
                 inline_completion_styles: make_suggestion_styles(cx),
                 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
-                show_underlines,
+                show_underlines: !self.mode.is_minimap(),
             },
         )
     }
@@ -20896,12 +20961,12 @@ fn inline_completion_edit_text(
     edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
 }
 
-pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
+pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
     match severity {
-        DiagnosticSeverity::ERROR => colors.error,
-        DiagnosticSeverity::WARNING => colors.warning,
-        DiagnosticSeverity::INFORMATION => colors.info,
-        DiagnosticSeverity::HINT => colors.info,
+        lsp::DiagnosticSeverity::ERROR => colors.error,
+        lsp::DiagnosticSeverity::WARNING => colors.warning,
+        lsp::DiagnosticSeverity::INFORMATION => colors.info,
+        lsp::DiagnosticSeverity::HINT => colors.info,
         _ => colors.ignored,
     }
 }

crates/editor/src/editor_settings.rs 🔗

@@ -1,5 +1,6 @@
 use gpui::App;
 use language::CursorShape;
+use project::project_settings::DiagnosticSeverity;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources, VsCodeSettings};
@@ -41,6 +42,8 @@ pub struct EditorSettings {
     pub jupyter: Jupyter,
     pub hide_mouse: Option<HideMouseMode>,
     pub snippet_sort_order: SnippetSortOrder,
+    #[serde(default)]
+    pub diagnostics_max_severity: Option<DiagnosticSeverity>,
 }
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -451,6 +454,19 @@ pub struct EditorSettingsContent {
 
     /// Jupyter REPL settings.
     pub jupyter: Option<JupyterContent>,
+
+    /// Which level to use to filter out diagnostics displayed in the editor.
+    ///
+    /// Affects the editor rendering only, and does not interrupt
+    /// the functionality of diagnostics fetching and project diagnostics editor.
+    /// Which files containing diagnostic errors/warnings to mark in the tabs.
+    /// Diagnostics are only shown when file icons are also active.
+    ///
+    /// Shows all diagnostics if not specified.
+    ///
+    /// Default: warning
+    #[serde(default)]
+    pub diagnostics_max_severity: Option<DiagnosticSeverity>,
 }
 
 // Toolbar related settings

crates/editor/src/element.rs 🔗

@@ -54,7 +54,6 @@ use itertools::Itertools;
 use language::language_settings::{
     IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, ShowWhitespaceSetting,
 };
-use lsp::DiagnosticSeverity;
 use markdown::Markdown;
 use multi_buffer::{
     Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
@@ -64,7 +63,7 @@ use multi_buffer::{
 use project::{
     ProjectPath,
     debugger::breakpoint_store::Breakpoint,
-    project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
+    project_settings::{GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
 };
 use settings::Settings;
 use smallvec::{SmallVec, smallvec};
@@ -1474,7 +1473,8 @@ impl EditorElement {
             });
         }
 
-        let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
+        let editor_settings = EditorSettings::get_global(cx);
+        let scrollbar_settings = editor_settings.scrollbar;
         let show_scrollbars = match scrollbar_settings.show {
             ShowScrollbar::Auto => {
                 let editor = self.editor.read(cx);
@@ -1796,16 +1796,17 @@ impl EditorElement {
         if self.editor.read(cx).mode().is_minimap() {
             return HashMap::default();
         }
-        let max_severity = ProjectSettings::get_global(cx)
+
+        let max_severity = match ProjectSettings::get_global(cx)
             .diagnostics
             .inline
             .max_severity
-            .map_or(DiagnosticSeverity::HINT, |severity| match severity {
-                project_settings::DiagnosticSeverity::Error => DiagnosticSeverity::ERROR,
-                project_settings::DiagnosticSeverity::Warning => DiagnosticSeverity::WARNING,
-                project_settings::DiagnosticSeverity::Info => DiagnosticSeverity::INFORMATION,
-                project_settings::DiagnosticSeverity::Hint => DiagnosticSeverity::HINT,
-            });
+            .unwrap_or_else(|| self.editor.read(cx).diagnostics_max_severity)
+            .into_lsp()
+        {
+            Some(max_severity) => max_severity,
+            None => return HashMap::default(),
+        };
 
         let active_diagnostics_group =
             if let ActiveDiagnostic::Group(group) = &self.editor.read(cx).active_diagnostics {
@@ -1843,11 +1844,11 @@ impl EditorElement {
             return HashMap::default();
         }
 
-        let severity_to_color = |sev: &DiagnosticSeverity| match sev {
-            &DiagnosticSeverity::ERROR => Color::Error,
-            &DiagnosticSeverity::WARNING => Color::Warning,
-            &DiagnosticSeverity::INFORMATION => Color::Info,
-            &DiagnosticSeverity::HINT => Color::Hint,
+        let severity_to_color = |sev: &lsp::DiagnosticSeverity| match sev {
+            &lsp::DiagnosticSeverity::ERROR => Color::Error,
+            &lsp::DiagnosticSeverity::WARNING => Color::Warning,
+            &lsp::DiagnosticSeverity::INFORMATION => Color::Info,
+            &lsp::DiagnosticSeverity::HINT => Color::Hint,
             _ => Color::Error,
         };
 
@@ -2813,7 +2814,7 @@ impl EditorElement {
                         font: style.text.font(),
                         color: placeholder_color,
                         background_color: None,
-                        underline: Default::default(),
+                        underline: None,
                         strikethrough: None,
                     };
                     window
@@ -5587,18 +5588,18 @@ impl EditorElement {
                                             (ScrollbarDiagnostics::All, _) => true,
                                             (
                                                 ScrollbarDiagnostics::Error,
-                                                DiagnosticSeverity::ERROR,
+                                                lsp::DiagnosticSeverity::ERROR,
                                             ) => true,
                                             (
                                                 ScrollbarDiagnostics::Warning,
-                                                DiagnosticSeverity::ERROR
-                                                | DiagnosticSeverity::WARNING,
+                                                lsp::DiagnosticSeverity::ERROR
+                                                | lsp::DiagnosticSeverity::WARNING,
                                             ) => true,
                                             (
                                                 ScrollbarDiagnostics::Information,
-                                                DiagnosticSeverity::ERROR
-                                                | DiagnosticSeverity::WARNING
-                                                | DiagnosticSeverity::INFORMATION,
+                                                lsp::DiagnosticSeverity::ERROR
+                                                | lsp::DiagnosticSeverity::WARNING
+                                                | lsp::DiagnosticSeverity::INFORMATION,
                                             ) => true,
                                             (_, _) => false,
                                         }
@@ -5618,9 +5619,9 @@ impl EditorElement {
                                         .end
                                         .to_display_point(&snapshot.display_snapshot);
                                     let color = match diagnostic.diagnostic.severity {
-                                        DiagnosticSeverity::ERROR => theme.status().error,
-                                        DiagnosticSeverity::WARNING => theme.status().warning,
-                                        DiagnosticSeverity::INFORMATION => theme.status().info,
+                                        lsp::DiagnosticSeverity::ERROR => theme.status().error,
+                                        lsp::DiagnosticSeverity::WARNING => theme.status().warning,
+                                        lsp::DiagnosticSeverity::INFORMATION => theme.status().info,
                                         _ => theme.status().hint,
                                     };
                                     ColoredRange {

crates/editor/src/movement.rs 🔗

@@ -772,7 +772,7 @@ mod tests {
     };
     use gpui::{AppContext as _, font, px};
     use language::Capability;
-    use project::Project;
+    use project::{Project, project_settings::DiagnosticSeverity};
     use settings::SettingsStore;
     use util::post_inc;
 
@@ -896,6 +896,7 @@ mod tests {
                 1,
                 1,
                 FoldPlaceholder::test(),
+                DiagnosticSeverity::Warning,
                 cx,
             )
         });
@@ -1105,6 +1106,7 @@ mod tests {
                     0,
                     1,
                     FoldPlaceholder::test(),
+                    DiagnosticSeverity::Warning,
                     cx,
                 )
             });

crates/editor/src/test.rs 🔗

@@ -18,7 +18,7 @@ use gpui::{
 };
 use multi_buffer::ToPoint;
 use pretty_assertions::assert_eq;
-use project::Project;
+use project::{Project, project_settings::DiagnosticSeverity};
 use ui::{App, BorrowAppContext, px};
 use util::test::{marked_text_offsets, marked_text_ranges};
 
@@ -72,6 +72,7 @@ pub fn marked_display_snapshot(
             1,
             1,
             FoldPlaceholder::test(),
+            DiagnosticSeverity::Warning,
             cx,
         )
     });

crates/project/src/project_settings.rs 🔗

@@ -181,15 +181,31 @@ pub struct CargoDiagnosticsSettings {
     pub fetch_cargo_diagnostics: bool,
 }
 
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
+#[derive(
+    Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema,
+)]
 #[serde(rename_all = "snake_case")]
 pub enum DiagnosticSeverity {
+    // No diagnostics are shown.
+    Off,
     Error,
     Warning,
     Info,
     Hint,
 }
 
+impl DiagnosticSeverity {
+    pub fn into_lsp(self) -> Option<lsp::DiagnosticSeverity> {
+        match self {
+            DiagnosticSeverity::Off => None,
+            DiagnosticSeverity::Error => Some(lsp::DiagnosticSeverity::ERROR),
+            DiagnosticSeverity::Warning => Some(lsp::DiagnosticSeverity::WARNING),
+            DiagnosticSeverity::Info => Some(lsp::DiagnosticSeverity::INFORMATION),
+            DiagnosticSeverity::Hint => Some(lsp::DiagnosticSeverity::HINT),
+        }
+    }
+}
+
 impl Default for InlineDiagnosticsSettings {
     fn default() -> Self {
         Self {

crates/zed/src/zed/quick_action_bar.rs 🔗

@@ -5,13 +5,15 @@ use assistant_settings::AssistantSettings;
 use editor::actions::{
     AddSelectionAbove, AddSelectionBelow, DuplicateLineDown, GoToDiagnostic, GoToHunk,
     GoToPreviousDiagnostic, GoToPreviousHunk, MoveLineDown, MoveLineUp, SelectAll,
-    SelectLargerSyntaxNode, SelectNext, SelectSmallerSyntaxNode, ToggleGoToLine,
+    SelectLargerSyntaxNode, SelectNext, SelectSmallerSyntaxNode, ToggleDiagnostics, ToggleGoToLine,
+    ToggleInlineDiagnostics,
 };
 use editor::{Editor, EditorSettings};
 use gpui::{
     Action, ClickEvent, Context, Corner, ElementId, Entity, EventEmitter, FocusHandle, Focusable,
     InteractiveElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window,
 };
+use project::project_settings::DiagnosticSeverity;
 use search::{BufferSearchBar, buffer_search};
 use settings::{Settings, SettingsStore};
 use ui::{
@@ -91,8 +93,10 @@ impl Render for QuickActionBar {
         let selection_menu_enabled = editor_value.selection_menu_enabled(cx);
         let inlay_hints_enabled = editor_value.inlay_hints_enabled();
         let inline_values_enabled = editor_value.inline_values_enabled();
-        let inline_diagnostics_enabled = editor_value.show_inline_diagnostics();
+        let supports_diagnostics = editor_value.mode().is_full();
+        let diagnostics_enabled = editor_value.diagnostics_max_severity != DiagnosticSeverity::Off;
         let supports_inline_diagnostics = editor_value.inline_diagnostics_enabled();
+        let inline_diagnostics_enabled = editor_value.show_inline_diagnostics();
         let git_blame_inline_enabled = editor_value.git_blame_inline_enabled();
         let show_git_blame_gutter = editor_value.show_git_blame_gutter();
         let auto_signature_help_enabled = editor_value.auto_signature_help_enabled(cx);
@@ -248,19 +252,19 @@ impl Render for QuickActionBar {
                                 );
                             }
 
-                            if supports_inline_diagnostics {
+                            if supports_diagnostics {
                                 menu = menu.toggleable_entry(
-                                    "Inline Diagnostics",
-                                    inline_diagnostics_enabled,
+                                    "Diagnostics",
+                                    diagnostics_enabled,
                                     IconPosition::Start,
-                                    Some(editor::actions::ToggleInlineDiagnostics.boxed_clone()),
+                                    Some(ToggleDiagnostics.boxed_clone()),
                                     {
                                         let editor = editor.clone();
                                         move |window, cx| {
                                             editor
                                                 .update(cx, |editor, cx| {
-                                                    editor.toggle_inline_diagnostics(
-                                                        &editor::actions::ToggleInlineDiagnostics,
+                                                    editor.toggle_diagnostics(
+                                                        &ToggleDiagnostics,
                                                         window,
                                                         cx,
                                                     );
@@ -269,6 +273,29 @@ impl Render for QuickActionBar {
                                         }
                                     },
                                 );
+
+                                if supports_inline_diagnostics {
+                                    menu = menu.toggleable_entry(
+                                        "Inline Diagnostics",
+                                        inline_diagnostics_enabled,
+                                        IconPosition::Start,
+                                        Some(ToggleInlineDiagnostics.boxed_clone()),
+                                        {
+                                            let editor = editor.clone();
+                                            move |window, cx| {
+                                                editor
+                                                    .update(cx, |editor, cx| {
+                                                        editor.toggle_inline_diagnostics(
+                                                            &ToggleInlineDiagnostics,
+                                                            window,
+                                                            cx,
+                                                        );
+                                                    })
+                                                    .ok();
+                                            }
+                                        },
+                                    );
+                                }
                             }
 
                             if supports_minimap {