Add severity argument to GoToDiagnostic actions (#33995)

Hilmar Wiegand created

This PR adds a `severity` argument so severity can be defined when
navigating through diagnostics. This allows keybinds like the following:

```json
{
  "] e": ["editor::GoToDiagnostic", { "severity": "error" }],
  "[ e": ["editor::GoToDiagnostic", { "severity": "error" }]
}
```

I've added test comments and a test. Let me know if there's anything
else you need!

Release Notes:

- Add `severity` argument to `editor::GoToDiagnostic`,
`editor::GoToPreviousDiagnostic`, `project_panel::SelectNextDiagnostic`
and `project_panel::SelectPrevDiagnostic` actions

Change summary

assets/keymaps/default-linux.json           |   4 
assets/keymaps/default-macos.json           |   4 
crates/diagnostics/src/diagnostics_tests.rs | 151 +++++++++++++++++++++-
crates/diagnostics/src/items.rs             |  11 +
crates/editor/src/actions.rs                |  23 ++
crates/editor/src/editor.rs                 |  15 +
crates/editor/src/editor_tests.rs           |   8 
crates/project/src/project_settings.rs      |  73 +++++++++++
crates/project_panel/src/project_panel.rs   |  33 +++-
crates/vim/src/command.rs                   |  27 +++
crates/zed/src/zed/app_menus.rs             |   7 
crates/zed/src/zed/quick_action_bar.rs      |   7 
12 files changed, 312 insertions(+), 51 deletions(-)

Detailed changes

assets/keymaps/default-linux.json 🔗

@@ -475,8 +475,8 @@
       "ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
       "ctrl-u": "editor::UndoSelection",
       "ctrl-shift-u": "editor::RedoSelection",
-      "f8": "editor::GoToDiagnostic",
-      "shift-f8": "editor::GoToPreviousDiagnostic",
+      "f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
+      "shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
       "f2": "editor::Rename",
       "f12": "editor::GoToDefinition",
       "alt-f12": "editor::GoToDefinitionSplit",

assets/keymaps/default-macos.json 🔗

@@ -528,8 +528,8 @@
       "cmd-/": ["editor::ToggleComments", { "advance_downwards": false }],
       "cmd-u": "editor::UndoSelection",
       "cmd-shift-u": "editor::RedoSelection",
-      "f8": "editor::GoToDiagnostic",
-      "shift-f8": "editor::GoToPreviousDiagnostic",
+      "f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
+      "shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
       "f2": "editor::Rename",
       "f12": "editor::GoToDefinition",
       "alt-f12": "editor::GoToDefinitionSplit",

crates/diagnostics/src/diagnostics_tests.rs 🔗

@@ -14,7 +14,10 @@ use indoc::indoc;
 use language::{DiagnosticSourceKind, Rope};
 use lsp::LanguageServerId;
 use pretty_assertions::assert_eq;
-use project::FakeFs;
+use project::{
+    FakeFs,
+    project_settings::{GoToDiagnosticSeverity, GoToDiagnosticSeverityFilter},
+};
 use rand::{Rng, rngs::StdRng, seq::IteratorRandom as _};
 use serde_json::json;
 use settings::SettingsStore;
@@ -1005,7 +1008,7 @@ async fn active_diagnostics_dismiss_after_invalidation(cx: &mut TestAppContext)
     cx.run_until_parked();
 
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
+        editor.go_to_diagnostic(&GoToDiagnostic::default(), window, cx);
         assert_eq!(
             editor
                 .active_diagnostic_group()
@@ -1047,7 +1050,7 @@ async fn active_diagnostics_dismiss_after_invalidation(cx: &mut TestAppContext)
     "});
 
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
+        editor.go_to_diagnostic(&GoToDiagnostic::default(), window, cx);
         assert_eq!(editor.active_diagnostic_group(), None);
     });
     cx.assert_editor_state(indoc! {"
@@ -1126,7 +1129,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // Fourth diagnostic
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abc def: i32) -> ˇu32 {
@@ -1135,7 +1138,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // Third diagnostic
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abc ˇdef: i32) -> u32 {
@@ -1144,7 +1147,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // Second diagnostic, same place
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abc ˇdef: i32) -> u32 {
@@ -1153,7 +1156,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // First diagnostic
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abcˇ def: i32) -> u32 {
@@ -1162,7 +1165,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // Wrapped over, fourth diagnostic
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abc def: i32) -> ˇu32 {
@@ -1181,7 +1184,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // First diagnostic
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
+        editor.go_to_diagnostic(&GoToDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abcˇ def: i32) -> u32 {
@@ -1190,7 +1193,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // Second diagnostic
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
+        editor.go_to_diagnostic(&GoToDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abc ˇdef: i32) -> u32 {
@@ -1199,7 +1202,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // Third diagnostic, same place
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
+        editor.go_to_diagnostic(&GoToDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abc ˇdef: i32) -> u32 {
@@ -1208,7 +1211,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // Fourth diagnostic
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
+        editor.go_to_diagnostic(&GoToDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abc def: i32) -> ˇu32 {
@@ -1217,7 +1220,7 @@ async fn cycle_through_same_place_diagnostics(cx: &mut TestAppContext) {
 
     // Wrapped around, first diagnostic
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
+        editor.go_to_diagnostic(&GoToDiagnostic::default(), window, cx);
     });
     cx.assert_editor_state(indoc! {"
         fn func(abcˇ def: i32) -> u32 {
@@ -1441,6 +1444,128 @@ async fn test_diagnostics_with_code(cx: &mut TestAppContext) {
     );
 }
 
+#[gpui::test]
+async fn go_to_diagnostic_with_severity(cx: &mut TestAppContext) {
+    init_test(cx);
+
+    let mut cx = EditorTestContext::new(cx).await;
+    let lsp_store =
+        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
+
+    cx.set_state(indoc! {"error warning info hiˇnt"});
+
+    cx.update(|_, cx| {
+        lsp_store.update(cx, |lsp_store, cx| {
+            lsp_store
+                .update_diagnostics(
+                    LanguageServerId(0),
+                    lsp::PublishDiagnosticsParams {
+                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
+                        version: None,
+                        diagnostics: vec![
+                            lsp::Diagnostic {
+                                range: lsp::Range::new(
+                                    lsp::Position::new(0, 0),
+                                    lsp::Position::new(0, 5),
+                                ),
+                                severity: Some(lsp::DiagnosticSeverity::ERROR),
+                                ..Default::default()
+                            },
+                            lsp::Diagnostic {
+                                range: lsp::Range::new(
+                                    lsp::Position::new(0, 6),
+                                    lsp::Position::new(0, 13),
+                                ),
+                                severity: Some(lsp::DiagnosticSeverity::WARNING),
+                                ..Default::default()
+                            },
+                            lsp::Diagnostic {
+                                range: lsp::Range::new(
+                                    lsp::Position::new(0, 14),
+                                    lsp::Position::new(0, 18),
+                                ),
+                                severity: Some(lsp::DiagnosticSeverity::INFORMATION),
+                                ..Default::default()
+                            },
+                            lsp::Diagnostic {
+                                range: lsp::Range::new(
+                                    lsp::Position::new(0, 19),
+                                    lsp::Position::new(0, 23),
+                                ),
+                                severity: Some(lsp::DiagnosticSeverity::HINT),
+                                ..Default::default()
+                            },
+                        ],
+                    },
+                    None,
+                    DiagnosticSourceKind::Pushed,
+                    &[],
+                    cx,
+                )
+                .unwrap()
+        });
+    });
+    cx.run_until_parked();
+
+    macro_rules! go {
+        ($severity:expr) => {
+            cx.update_editor(|editor, window, cx| {
+                editor.go_to_diagnostic(
+                    &GoToDiagnostic {
+                        severity: $severity,
+                    },
+                    window,
+                    cx,
+                );
+            });
+        };
+    }
+
+    // Default, should cycle through all diagnostics
+    go!(GoToDiagnosticSeverityFilter::default());
+    cx.assert_editor_state(indoc! {"ˇerror warning info hint"});
+    go!(GoToDiagnosticSeverityFilter::default());
+    cx.assert_editor_state(indoc! {"error ˇwarning info hint"});
+    go!(GoToDiagnosticSeverityFilter::default());
+    cx.assert_editor_state(indoc! {"error warning ˇinfo hint"});
+    go!(GoToDiagnosticSeverityFilter::default());
+    cx.assert_editor_state(indoc! {"error warning info ˇhint"});
+    go!(GoToDiagnosticSeverityFilter::default());
+    cx.assert_editor_state(indoc! {"ˇerror warning info hint"});
+
+    let only_info = GoToDiagnosticSeverityFilter::Only(GoToDiagnosticSeverity::Information);
+    go!(only_info);
+    cx.assert_editor_state(indoc! {"error warning ˇinfo hint"});
+    go!(only_info);
+    cx.assert_editor_state(indoc! {"error warning ˇinfo hint"});
+
+    let no_hints = GoToDiagnosticSeverityFilter::Range {
+        min: GoToDiagnosticSeverity::Information,
+        max: GoToDiagnosticSeverity::Error,
+    };
+
+    go!(no_hints);
+    cx.assert_editor_state(indoc! {"ˇerror warning info hint"});
+    go!(no_hints);
+    cx.assert_editor_state(indoc! {"error ˇwarning info hint"});
+    go!(no_hints);
+    cx.assert_editor_state(indoc! {"error warning ˇinfo hint"});
+    go!(no_hints);
+    cx.assert_editor_state(indoc! {"ˇerror warning info hint"});
+
+    let warning_info = GoToDiagnosticSeverityFilter::Range {
+        min: GoToDiagnosticSeverity::Information,
+        max: GoToDiagnosticSeverity::Warning,
+    };
+
+    go!(warning_info);
+    cx.assert_editor_state(indoc! {"error ˇwarning info hint"});
+    go!(warning_info);
+    cx.assert_editor_state(indoc! {"error warning ˇinfo hint"});
+    go!(warning_info);
+    cx.assert_editor_state(indoc! {"error ˇwarning info hint"});
+}
+
 fn init_test(cx: &mut TestAppContext) {
     cx.update(|cx| {
         zlog::init_test();

crates/diagnostics/src/items.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
     WeakEntity, Window,
 };
 use language::Diagnostic;
-use project::project_settings::ProjectSettings;
+use project::project_settings::{GoToDiagnosticSeverityFilter, ProjectSettings};
 use settings::Settings;
 use ui::{Button, ButtonLike, Color, Icon, IconName, Label, Tooltip, h_flex, prelude::*};
 use workspace::{StatusItemView, ToolbarItemEvent, Workspace, item::ItemHandle};
@@ -77,7 +77,7 @@ impl Render for DiagnosticIndicator {
                     .tooltip(|window, cx| {
                         Tooltip::for_action(
                             "Next Diagnostic",
-                            &editor::actions::GoToDiagnostic,
+                            &editor::actions::GoToDiagnostic::default(),
                             window,
                             cx,
                         )
@@ -156,7 +156,12 @@ impl DiagnosticIndicator {
     fn go_to_next_diagnostic(&mut self, window: &mut Window, cx: &mut Context<Self>) {
         if let Some(editor) = self.active_editor.as_ref().and_then(|e| e.upgrade()) {
             editor.update(cx, |editor, cx| {
-                editor.go_to_diagnostic_impl(editor::Direction::Next, window, cx);
+                editor.go_to_diagnostic_impl(
+                    editor::Direction::Next,
+                    GoToDiagnosticSeverityFilter::default(),
+                    window,
+                    cx,
+                );
             })
         }
     }

crates/editor/src/actions.rs 🔗

@@ -1,6 +1,7 @@
 //! This module contains all actions supported by [`Editor`].
 use super::*;
 use gpui::{Action, actions};
+use project::project_settings::GoToDiagnosticSeverityFilter;
 use schemars::JsonSchema;
 use util::serde::default_true;
 
@@ -265,6 +266,24 @@ pub enum UuidVersion {
     V7,
 }
 
+/// Goes to the next diagnostic in the file.
+#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
+#[action(namespace = editor)]
+#[serde(deny_unknown_fields)]
+pub struct GoToDiagnostic {
+    #[serde(default)]
+    pub severity: GoToDiagnosticSeverityFilter,
+}
+
+/// Goes to the previous diagnostic in the file.
+#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
+#[action(namespace = editor)]
+#[serde(deny_unknown_fields)]
+pub struct GoToPreviousDiagnostic {
+    #[serde(default)]
+    pub severity: GoToDiagnosticSeverityFilter,
+}
+
 actions!(
     debugger,
     [
@@ -424,8 +443,6 @@ actions!(
         GoToDefinition,
         /// Goes to definition in a split pane.
         GoToDefinitionSplit,
-        /// Goes to the next diagnostic in the file.
-        GoToDiagnostic,
         /// Goes to the next diff hunk.
         GoToHunk,
         /// Goes to the previous diff hunk.
@@ -440,8 +457,6 @@ actions!(
         GoToParentModule,
         /// Goes to the previous change in the file.
         GoToPreviousChange,
-        /// Goes to the previous diagnostic in the file.
-        GoToPreviousDiagnostic,
         /// Goes to the type definition of the symbol at cursor.
         GoToTypeDefinition,
         /// Goes to type definition in a split pane.

crates/editor/src/editor.rs 🔗

@@ -134,7 +134,7 @@ use project::{
         session::{Session, SessionEvent},
     },
     git_store::{GitStoreEvent, RepositoryEvent},
-    project_settings::DiagnosticSeverity,
+    project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
 };
 
 pub use git::blame::BlameRenderer;
@@ -15086,7 +15086,7 @@ impl Editor {
 
     pub fn go_to_diagnostic(
         &mut self,
-        _: &GoToDiagnostic,
+        action: &GoToDiagnostic,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -15094,12 +15094,12 @@ impl Editor {
             return;
         }
         self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
-        self.go_to_diagnostic_impl(Direction::Next, window, cx)
+        self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
     }
 
     pub fn go_to_prev_diagnostic(
         &mut self,
-        _: &GoToPreviousDiagnostic,
+        action: &GoToPreviousDiagnostic,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -15107,12 +15107,13 @@ impl Editor {
             return;
         }
         self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
-        self.go_to_diagnostic_impl(Direction::Prev, window, cx)
+        self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
     }
 
     pub fn go_to_diagnostic_impl(
         &mut self,
         direction: Direction,
+        severity: GoToDiagnosticSeverityFilter,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -15128,9 +15129,11 @@ impl Editor {
 
         fn filtered(
             snapshot: EditorSnapshot,
+            severity: GoToDiagnosticSeverityFilter,
             diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
         ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
             diagnostics
+                .filter(move |entry| severity.matches(entry.diagnostic.severity))
                 .filter(|entry| entry.range.start != entry.range.end)
                 .filter(|entry| !entry.diagnostic.is_unnecessary)
                 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
@@ -15139,12 +15142,14 @@ impl Editor {
         let snapshot = self.snapshot(window, cx);
         let before = filtered(
             snapshot.clone(),
+            severity,
             buffer
                 .diagnostics_in_range(0..selection.start)
                 .filter(|entry| entry.range.start <= selection.start),
         );
         let after = filtered(
             snapshot,
+            severity,
             buffer
                 .diagnostics_in_range(selection.start..buffer.len())
                 .filter(|entry| entry.range.start >= selection.start),

crates/editor/src/editor_tests.rs 🔗

@@ -14734,7 +14734,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
     executor.run_until_parked();
 
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
 
     cx.assert_editor_state(indoc! {"
@@ -14743,7 +14743,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
     "});
 
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
 
     cx.assert_editor_state(indoc! {"
@@ -14752,7 +14752,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
     "});
 
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
 
     cx.assert_editor_state(indoc! {"
@@ -14761,7 +14761,7 @@ async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mu
     "});
 
     cx.update_editor(|editor, window, cx| {
-        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
+        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
     });
 
     cx.assert_editor_state(indoc! {"

crates/project/src/project_settings.rs 🔗

@@ -326,6 +326,79 @@ impl DiagnosticSeverity {
     }
 }
 
+/// Determines the severity of the diagnostic that should be moved to.
+#[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Eq, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum GoToDiagnosticSeverity {
+    /// Errors
+    Error = 3,
+    /// Warnings
+    Warning = 2,
+    /// Information
+    Information = 1,
+    /// Hints
+    Hint = 0,
+}
+
+impl From<lsp::DiagnosticSeverity> for GoToDiagnosticSeverity {
+    fn from(severity: lsp::DiagnosticSeverity) -> Self {
+        match severity {
+            lsp::DiagnosticSeverity::ERROR => Self::Error,
+            lsp::DiagnosticSeverity::WARNING => Self::Warning,
+            lsp::DiagnosticSeverity::INFORMATION => Self::Information,
+            lsp::DiagnosticSeverity::HINT => Self::Hint,
+            _ => Self::Error,
+        }
+    }
+}
+
+impl GoToDiagnosticSeverity {
+    pub fn min() -> Self {
+        Self::Hint
+    }
+
+    pub fn max() -> Self {
+        Self::Error
+    }
+}
+
+/// Allows filtering diagnostics that should be moved to.
+#[derive(PartialEq, Clone, Copy, Debug, Deserialize, JsonSchema)]
+#[serde(untagged)]
+pub enum GoToDiagnosticSeverityFilter {
+    /// Move to diagnostics of a specific severity.
+    Only(GoToDiagnosticSeverity),
+
+    /// Specify a range of severities to include.
+    Range {
+        /// Minimum severity to move to. Defaults no "error".
+        #[serde(default = "GoToDiagnosticSeverity::min")]
+        min: GoToDiagnosticSeverity,
+        /// Maximum severity to move to. Defaults to "hint".
+        #[serde(default = "GoToDiagnosticSeverity::max")]
+        max: GoToDiagnosticSeverity,
+    },
+}
+
+impl Default for GoToDiagnosticSeverityFilter {
+    fn default() -> Self {
+        Self::Range {
+            min: GoToDiagnosticSeverity::min(),
+            max: GoToDiagnosticSeverity::max(),
+        }
+    }
+}
+
+impl GoToDiagnosticSeverityFilter {
+    pub fn matches(&self, severity: lsp::DiagnosticSeverity) -> bool {
+        let severity: GoToDiagnosticSeverity = severity.into();
+        match self {
+            Self::Only(target) => *target == severity,
+            Self::Range { min, max } => severity >= *min && severity <= *max,
+        }
+    }
+}
+
 #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 pub struct GitSettings {
     /// Whether or not to show the git gutter.

crates/project_panel/src/project_panel.rs 🔗

@@ -33,6 +33,7 @@ use project::{
     Entry, EntryKind, Fs, GitEntry, GitEntryRef, GitTraversal, Project, ProjectEntryId,
     ProjectPath, Worktree, WorktreeId,
     git_store::{GitStoreEvent, git_traversal::ChildEntriesGitIter},
+    project_settings::GoToDiagnosticSeverityFilter,
     relativize_path,
 };
 use project_panel_settings::{
@@ -206,6 +207,24 @@ struct Trash {
     pub skip_prompt: bool,
 }
 
+/// Selects the next entry with diagnostics.
+#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
+#[action(namespace = project_panel)]
+#[serde(deny_unknown_fields)]
+struct SelectNextDiagnostic {
+    #[serde(default)]
+    pub severity: GoToDiagnosticSeverityFilter,
+}
+
+/// Selects the previous entry with diagnostics.
+#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
+#[action(namespace = project_panel)]
+#[serde(deny_unknown_fields)]
+struct SelectPrevDiagnostic {
+    #[serde(default)]
+    pub severity: GoToDiagnosticSeverityFilter,
+}
+
 actions!(
     project_panel,
     [
@@ -255,10 +274,6 @@ actions!(
         SelectNextGitEntry,
         /// Selects the previous entry with git changes.
         SelectPrevGitEntry,
-        /// Selects the next entry with diagnostics.
-        SelectNextDiagnostic,
-        /// Selects the previous entry with diagnostics.
-        SelectPrevDiagnostic,
         /// Selects the next directory.
         SelectNextDirectory,
         /// Selects the previous directory.
@@ -1954,7 +1969,7 @@ impl ProjectPanel {
 
     fn select_prev_diagnostic(
         &mut self,
-        _: &SelectPrevDiagnostic,
+        action: &SelectPrevDiagnostic,
         _: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -1973,7 +1988,8 @@ impl ProjectPanel {
                     && entry.is_file()
                     && self
                         .diagnostics
-                        .contains_key(&(worktree_id, entry.path.to_path_buf()))
+                        .get(&(worktree_id, entry.path.to_path_buf()))
+                        .is_some_and(|severity| action.severity.matches(*severity))
             },
             cx,
         );
@@ -1989,7 +2005,7 @@ impl ProjectPanel {
 
     fn select_next_diagnostic(
         &mut self,
-        _: &SelectNextDiagnostic,
+        action: &SelectNextDiagnostic,
         _: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -2008,7 +2024,8 @@ impl ProjectPanel {
                     && entry.is_file()
                     && self
                         .diagnostics
-                        .contains_key(&(worktree_id, entry.path.to_path_buf()))
+                        .get(&(worktree_id, entry.path.to_path_buf()))
+                        .is_some_and(|severity| action.severity.matches(*severity))
             },
             cx,
         );

crates/vim/src/command.rs 🔗

@@ -1106,13 +1106,28 @@ fn generate_commands(_: &App) -> Vec<VimCommand> {
         VimCommand::str(("cl", "ist"), "diagnostics::Deploy"),
         VimCommand::new(("cc", ""), editor::actions::Hover),
         VimCommand::new(("ll", ""), editor::actions::Hover),
-        VimCommand::new(("cn", "ext"), editor::actions::GoToDiagnostic).range(wrap_count),
-        VimCommand::new(("cp", "revious"), editor::actions::GoToPreviousDiagnostic)
+        VimCommand::new(("cn", "ext"), editor::actions::GoToDiagnostic::default())
             .range(wrap_count),
-        VimCommand::new(("cN", "ext"), editor::actions::GoToPreviousDiagnostic).range(wrap_count),
-        VimCommand::new(("lp", "revious"), editor::actions::GoToPreviousDiagnostic)
-            .range(wrap_count),
-        VimCommand::new(("lN", "ext"), editor::actions::GoToPreviousDiagnostic).range(wrap_count),
+        VimCommand::new(
+            ("cp", "revious"),
+            editor::actions::GoToPreviousDiagnostic::default(),
+        )
+        .range(wrap_count),
+        VimCommand::new(
+            ("cN", "ext"),
+            editor::actions::GoToPreviousDiagnostic::default(),
+        )
+        .range(wrap_count),
+        VimCommand::new(
+            ("lp", "revious"),
+            editor::actions::GoToPreviousDiagnostic::default(),
+        )
+        .range(wrap_count),
+        VimCommand::new(
+            ("lN", "ext"),
+            editor::actions::GoToPreviousDiagnostic::default(),
+        )
+        .range(wrap_count),
         VimCommand::new(("j", "oin"), JoinLines).range(select_range),
         VimCommand::new(("fo", "ld"), editor::actions::FoldSelectedRanges).range(act_on_range),
         VimCommand::new(("foldo", "pen"), editor::actions::UnfoldLines)

crates/zed/src/zed/app_menus.rs 🔗

@@ -199,8 +199,11 @@ pub fn app_menus() -> Vec<Menu> {
                 MenuItem::action("Go to Type Definition", editor::actions::GoToTypeDefinition),
                 MenuItem::action("Find All References", editor::actions::FindAllReferences),
                 MenuItem::separator(),
-                MenuItem::action("Next Problem", editor::actions::GoToDiagnostic),
-                MenuItem::action("Previous Problem", editor::actions::GoToPreviousDiagnostic),
+                MenuItem::action("Next Problem", editor::actions::GoToDiagnostic::default()),
+                MenuItem::action(
+                    "Previous Problem",
+                    editor::actions::GoToPreviousDiagnostic::default(),
+                ),
             ],
         },
         Menu {

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

@@ -255,8 +255,11 @@ impl Render for QuickActionBar {
                             .action("Go to Symbol", Box::new(ToggleOutline))
                             .action("Go to Line/Column", Box::new(ToggleGoToLine))
                             .separator()
-                            .action("Next Problem", Box::new(GoToDiagnostic))
-                            .action("Previous Problem", Box::new(GoToPreviousDiagnostic))
+                            .action("Next Problem", Box::new(GoToDiagnostic::default()))
+                            .action(
+                                "Previous Problem",
+                                Box::new(GoToPreviousDiagnostic::default()),
+                            )
                             .separator()
                             .action_disabled_when(!has_diff_hunks, "Next Hunk", Box::new(GoToHunk))
                             .action_disabled_when(