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

* 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 ...
This commit is contained in:
Kirill Bulatov 2025-05-09 00:47:32 +03:00 committed by GitHub
parent 9e5d115e72
commit a8312d623d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 279 additions and 96 deletions

View file

@ -218,6 +218,23 @@
// 1. Do nothing: `none` // 1. Do nothing: `none`
// 2. Find references for the same symbol: `find_all_references` (default) // 2. Find references for the same symbol: `find_all_references` (default)
"go_to_definition_fallback": "find_all_references", "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. // Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value // 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 // 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. // longer than this value will still push diagnostics further to the right.
"min_column": 0, "min_column": 0,
// The minimum severity of the diagnostics to show inline. // 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 "max_severity": null
}, },
"cargo": { "cargo": {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,6 @@
use gpui::App; use gpui::App;
use language::CursorShape; use language::CursorShape;
use project::project_settings::DiagnosticSeverity;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, VsCodeSettings}; use settings::{Settings, SettingsSources, VsCodeSettings};
@ -41,6 +42,8 @@ pub struct EditorSettings {
pub jupyter: Jupyter, pub jupyter: Jupyter,
pub hide_mouse: Option<HideMouseMode>, pub hide_mouse: Option<HideMouseMode>,
pub snippet_sort_order: SnippetSortOrder, pub snippet_sort_order: SnippetSortOrder,
#[serde(default)]
pub diagnostics_max_severity: Option<DiagnosticSeverity>,
} }
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@ -451,6 +454,19 @@ pub struct EditorSettingsContent {
/// Jupyter REPL settings. /// Jupyter REPL settings.
pub jupyter: Option<JupyterContent>, 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 // Toolbar related settings

View file

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

View file

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

View file

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

View file

@ -181,15 +181,31 @@ pub struct CargoDiagnosticsSettings {
pub fetch_cargo_diagnostics: bool, 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")] #[serde(rename_all = "snake_case")]
pub enum DiagnosticSeverity { pub enum DiagnosticSeverity {
// No diagnostics are shown.
Off,
Error, Error,
Warning, Warning,
Info, Info,
Hint, 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 { impl Default for InlineDiagnosticsSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {

View file

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