Add severity argument to GoToDiagnostic actions (#33995)

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
This commit is contained in:
Hilmar Wiegand 2025-07-15 16:03:57 +02:00 committed by GitHub
parent 858e176a1c
commit 050ed85d71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 312 additions and 51 deletions

View file

@ -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();

View file

@ -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,
);
})
}
}