Add initial inline diagnostics support (#25297)
https://github.com/user-attachments/assets/eb881707-e575-47ef-9ae0-67d8085d8065 Closes https://github.com/zed-industries/zed/pull/22668 Closes https://github.com/zed-industries/zed/issues/4901 Takes https://github.com/zed-industries/zed/pull/22668 and fixes all review items on top. Inline diagnostics are disabled by default, but can be enabled via settings permanently, or temporarily toggled with the `editor: ToggleInlineDiagnostics` action and the corresponding editor menu item <img width="242" alt="image" src="https://github.com/user-attachments/assets/8e177511-4626-4434-902b-d6aa4d3fafd0" /> Inline diagnostics does not show currently active diagnostics group, as it gets inline into the editor too, inside the text. Inline git blame takes precedence and is shown instead of the diagnostics, edit predictions dim the diagnostics if located on the same line. One notable drawback of the implementation is the inability to wrap, making inline diagnostics cut off the right side:  (same as inline git blame and other elements to the right of the text) Given that it's disabled by default and go to next/prev diagnostics will show them better, seems fine to leave in the first iteration. Release Notes: - Added initial inline diagnostics support --------- Co-authored-by: Paul J. Davis <paul.davis@tiledb.com> Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
parent
74c581b9f4
commit
5ae93ce68d
11 changed files with 579 additions and 81 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3851,7 +3851,6 @@ dependencies = [
|
|||
"pretty_assertions",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
|
|
|
@ -760,7 +760,25 @@
|
|||
// Diagnostics configuration.
|
||||
"diagnostics": {
|
||||
// Whether to show warnings or not by default.
|
||||
"include_warnings": true
|
||||
"include_warnings": true,
|
||||
// Settings for inline diagnostics
|
||||
"inline": {
|
||||
// Whether to show diagnostics inline or not
|
||||
"enabled": false,
|
||||
// The delay in milliseconds to show inline diagnostics after the
|
||||
// last diagnostic update.
|
||||
"update_debounce_ms": 150,
|
||||
// The amount of padding between the end of the source line and the start
|
||||
// of the inline diagnostic in units of em widths.
|
||||
"padding": 4,
|
||||
// The minimum column to display inline diagnostics. This setting can be
|
||||
// used to horizontally align inline diagnostics at some column. Lines
|
||||
// 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.
|
||||
"max_severity": null
|
||||
}
|
||||
},
|
||||
// Files or globs of files that will be excluded by Zed entirely. They will be skipped during file
|
||||
// scans, file searches, and not be displayed in the project file tree. Takes precedence over `file_scan_inclusions`.
|
||||
|
|
|
@ -24,7 +24,6 @@ log.workspace = true
|
|||
lsp.workspace = true
|
||||
project.workspace = true
|
||||
rand.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
pub mod items;
|
||||
mod project_diagnostics_settings;
|
||||
mod toolbar_controls;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -24,8 +23,7 @@ use language::{
|
|||
Point, Selection, SelectionGoal, ToTreeSitterPoint,
|
||||
};
|
||||
use lsp::LanguageServerId;
|
||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||
use project_diagnostics_settings::ProjectDiagnosticsSettings;
|
||||
use project::{project_settings::ProjectSettings, DiagnosticSummary, Project, ProjectPath};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
|
@ -52,7 +50,6 @@ struct IncludeWarnings(bool);
|
|||
impl Global for IncludeWarnings {}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
ProjectDiagnosticsSettings::register(cx);
|
||||
cx.observe_new(ProjectDiagnosticsEditor::register).detach();
|
||||
}
|
||||
|
||||
|
@ -178,6 +175,7 @@ impl ProjectDiagnosticsEditor {
|
|||
cx,
|
||||
);
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor.disable_inline_diagnostics();
|
||||
editor
|
||||
});
|
||||
cx.subscribe_in(
|
||||
|
@ -287,7 +285,7 @@ impl ProjectDiagnosticsEditor {
|
|||
|
||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
||||
Some(include_warnings) => include_warnings.0,
|
||||
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||
None => ProjectSettings::get_global(cx).diagnostics.include_warnings,
|
||||
};
|
||||
|
||||
let diagnostics = cx.new(|cx| {
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ProjectDiagnosticsSettings {
|
||||
pub include_warnings: bool,
|
||||
}
|
||||
|
||||
/// Diagnostics configuration.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct ProjectDiagnosticsSettingsContent {
|
||||
/// Whether to show warnings or not by default.
|
||||
///
|
||||
/// Default: true
|
||||
include_warnings: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for ProjectDiagnosticsSettings {
|
||||
const KEY: Option<&'static str> = Some("diagnostics");
|
||||
type FileContent = ProjectDiagnosticsSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
|
@ -397,6 +397,7 @@ gpui::actions!(
|
|||
ToggleGitBlameInline,
|
||||
ToggleIndentGuides,
|
||||
ToggleInlayHints,
|
||||
ToggleInlineDiagnostics,
|
||||
ToggleEditPrediction,
|
||||
ToggleLineNumbers,
|
||||
SwapSelectionEnds,
|
||||
|
|
|
@ -464,6 +464,15 @@ enum EditPredictionSettings {
|
|||
|
||||
enum InlineCompletionHighlight {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct InlineDiagnostic {
|
||||
message: SharedString,
|
||||
group_id: usize,
|
||||
is_primary: bool,
|
||||
start: Point,
|
||||
severity: DiagnosticSeverity,
|
||||
}
|
||||
|
||||
pub enum MenuInlineCompletionsPolicy {
|
||||
Never,
|
||||
ByProvider,
|
||||
|
@ -594,6 +603,10 @@ pub struct Editor {
|
|||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||
ime_transaction: Option<TransactionId>,
|
||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||
show_inline_diagnostics: bool,
|
||||
inline_diagnostics_update: Task<()>,
|
||||
inline_diagnostics_enabled: bool,
|
||||
inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
|
||||
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
|
||||
|
||||
// TODO: make this a access method
|
||||
|
@ -1304,6 +1317,9 @@ impl Editor {
|
|||
select_larger_syntax_node_stack: Vec::new(),
|
||||
ime_transaction: Default::default(),
|
||||
active_diagnostics: None,
|
||||
show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
|
||||
inline_diagnostics_update: Task::ready(()),
|
||||
inline_diagnostics: Vec::new(),
|
||||
soft_wrap_mode_override,
|
||||
completion_provider: project.clone().map(|project| Box::new(project) as _),
|
||||
semantics_provider: project.clone().map(|project| Rc::new(project) as _),
|
||||
|
@ -1368,6 +1384,7 @@ impl Editor {
|
|||
active_inline_completion: None,
|
||||
stale_inline_completion_in_menu: None,
|
||||
edit_prediction_preview: EditPredictionPreview::Inactive,
|
||||
inline_diagnostics_enabled: mode == EditorMode::Full,
|
||||
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
||||
|
||||
gutter_hovered: false,
|
||||
|
@ -11868,6 +11885,106 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Disable inline diagnostics rendering for this editor.
|
||||
pub fn disable_inline_diagnostics(&mut self) {
|
||||
self.inline_diagnostics_enabled = false;
|
||||
self.inline_diagnostics_update = Task::ready(());
|
||||
self.inline_diagnostics.clear();
|
||||
}
|
||||
|
||||
pub fn inline_diagnostics_enabled(&self) -> bool {
|
||||
self.inline_diagnostics_enabled
|
||||
}
|
||||
|
||||
pub fn show_inline_diagnostics(&self) -> bool {
|
||||
self.show_inline_diagnostics
|
||||
}
|
||||
|
||||
pub fn toggle_inline_diagnostics(
|
||||
&mut self,
|
||||
_: &ToggleInlineDiagnostics,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'_, Editor>,
|
||||
) {
|
||||
self.show_inline_diagnostics = !self.show_inline_diagnostics;
|
||||
self.refresh_inline_diagnostics(false, window, cx);
|
||||
}
|
||||
|
||||
fn refresh_inline_diagnostics(
|
||||
&mut self,
|
||||
debounce: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
|
||||
self.inline_diagnostics_update = Task::ready(());
|
||||
self.inline_diagnostics.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
let debounce_ms = ProjectSettings::get_global(cx)
|
||||
.diagnostics
|
||||
.inline
|
||||
.update_debounce_ms;
|
||||
let debounce = if debounce && debounce_ms > 0 {
|
||||
Some(Duration::from_millis(debounce_ms))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.inline_diagnostics_update = cx.spawn_in(window, |editor, mut cx| async move {
|
||||
if let Some(debounce) = debounce {
|
||||
cx.background_executor().timer(debounce).await;
|
||||
}
|
||||
let Some(snapshot) = editor
|
||||
.update(&mut cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
|
||||
.ok()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let new_inline_diagnostics = cx
|
||||
.background_spawn(async move {
|
||||
let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
|
||||
for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
|
||||
let message = diagnostic_entry
|
||||
.diagnostic
|
||||
.message
|
||||
.split_once('\n')
|
||||
.map(|(line, _)| line)
|
||||
.map(SharedString::new)
|
||||
.unwrap_or_else(|| {
|
||||
SharedString::from(diagnostic_entry.diagnostic.message)
|
||||
});
|
||||
let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
|
||||
let (Ok(i) | Err(i)) = inline_diagnostics
|
||||
.binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
|
||||
inline_diagnostics.insert(
|
||||
i,
|
||||
(
|
||||
start_anchor,
|
||||
InlineDiagnostic {
|
||||
message,
|
||||
group_id: diagnostic_entry.diagnostic.group_id,
|
||||
start: diagnostic_entry.range.start.to_point(&snapshot),
|
||||
is_primary: diagnostic_entry.diagnostic.is_primary,
|
||||
severity: diagnostic_entry.diagnostic.severity,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
inline_diagnostics
|
||||
})
|
||||
.await;
|
||||
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
editor.inline_diagnostics = new_inline_diagnostics;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_selections_from_remote(
|
||||
&mut self,
|
||||
selections: Vec<Selection<Anchor>>,
|
||||
|
@ -14333,6 +14450,7 @@ impl Editor {
|
|||
multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
|
||||
multi_buffer::Event::DiagnosticsUpdated => {
|
||||
self.refresh_active_diagnostics(cx);
|
||||
self.refresh_inline_diagnostics(true, window, cx);
|
||||
self.scrollbar_marker_state.dirty = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -14383,7 +14501,13 @@ impl Editor {
|
|||
self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
|
||||
|
||||
if self.mode == EditorMode::Full {
|
||||
let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
|
||||
let inline_blame_enabled = project_settings.git.inline_blame_enabled();
|
||||
if self.show_inline_diagnostics != show_inline_diagnostics {
|
||||
self.show_inline_diagnostics = show_inline_diagnostics;
|
||||
self.refresh_inline_diagnostics(false, window, cx);
|
||||
}
|
||||
|
||||
if self.git_blame_inline_enabled != inline_blame_enabled {
|
||||
self.toggle_git_blame_inline_internal(false, window, cx);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ use multi_buffer::{
|
|||
Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow,
|
||||
RowInfo, ToOffset,
|
||||
};
|
||||
use project::project_settings::{GitGutterSetting, ProjectSettings};
|
||||
use project::project_settings::{self, GitGutterSetting, ProjectSettings};
|
||||
use settings::Settings;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::{
|
||||
|
@ -403,6 +403,7 @@ impl EditorElement {
|
|||
register_action(editor, window, Editor::toggle_indent_guides);
|
||||
register_action(editor, window, Editor::toggle_inlay_hints);
|
||||
register_action(editor, window, Editor::toggle_inline_completions);
|
||||
register_action(editor, window, Editor::toggle_inline_diagnostics);
|
||||
register_action(editor, window, hover_popover::hover);
|
||||
register_action(editor, window, Editor::reveal_in_finder);
|
||||
register_action(editor, window, Editor::copy_path);
|
||||
|
@ -1610,6 +1611,157 @@ impl EditorElement {
|
|||
display_hunks
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_inline_diagnostics(
|
||||
&self,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
crease_trailers: &[Option<CreaseTrailerLayout>],
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
inline_completion_popover_origin: Option<gpui::Point<Pixels>>,
|
||||
start_row: DisplayRow,
|
||||
end_row: DisplayRow,
|
||||
line_height: Pixels,
|
||||
em_width: Pixels,
|
||||
style: &EditorStyle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> HashMap<DisplayRow, AnyElement> {
|
||||
let max_severity = 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,
|
||||
});
|
||||
|
||||
let active_diagnostics_group = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.active_diagnostics
|
||||
.as_ref()
|
||||
.map(|active_diagnostics| active_diagnostics.group_id);
|
||||
|
||||
let diagnostics_by_rows = self.editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
editor
|
||||
.inline_diagnostics
|
||||
.iter()
|
||||
.filter(|(_, diagnostic)| diagnostic.severity <= max_severity)
|
||||
.filter(|(_, diagnostic)| match active_diagnostics_group {
|
||||
Some(active_diagnostics_group) => {
|
||||
// Active diagnostics are all shown in the editor already, no need to display them inline
|
||||
diagnostic.group_id != active_diagnostics_group
|
||||
}
|
||||
None => true,
|
||||
})
|
||||
.map(|(point, diag)| (point.to_display_point(&snapshot), diag.clone()))
|
||||
.skip_while(|(point, _)| point.row() < start_row)
|
||||
.take_while(|(point, _)| point.row() < end_row)
|
||||
.fold(HashMap::default(), |mut acc, (point, diagnostic)| {
|
||||
acc.entry(point.row())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(diagnostic);
|
||||
acc
|
||||
})
|
||||
});
|
||||
|
||||
if diagnostics_by_rows.is_empty() {
|
||||
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,
|
||||
_ => Color::Error,
|
||||
};
|
||||
|
||||
let padding = ProjectSettings::get_global(cx).diagnostics.inline.padding as f32 * em_width;
|
||||
let min_x = ProjectSettings::get_global(cx)
|
||||
.diagnostics
|
||||
.inline
|
||||
.min_column as f32
|
||||
* em_width;
|
||||
|
||||
let mut elements = HashMap::default();
|
||||
for (row, mut diagnostics) in diagnostics_by_rows {
|
||||
diagnostics.sort_by_key(|diagnostic| {
|
||||
(
|
||||
diagnostic.severity,
|
||||
std::cmp::Reverse(diagnostic.is_primary),
|
||||
diagnostic.start.row,
|
||||
diagnostic.start.column,
|
||||
)
|
||||
});
|
||||
|
||||
let Some(diagnostic_to_render) = diagnostics
|
||||
.iter()
|
||||
.find(|diagnostic| diagnostic.is_primary)
|
||||
.or_else(|| diagnostics.first())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let pos_y = content_origin.y
|
||||
+ line_height * (row.0 as f32 - scroll_pixel_position.y / line_height);
|
||||
|
||||
let window_ix = row.minus(start_row) as usize;
|
||||
let pos_x = {
|
||||
let crease_trailer_layout = &crease_trailers[window_ix];
|
||||
let line_layout = &line_layouts[window_ix];
|
||||
|
||||
let line_end = if let Some(crease_trailer) = crease_trailer_layout {
|
||||
crease_trailer.bounds.right()
|
||||
} else {
|
||||
content_origin.x - scroll_pixel_position.x + line_layout.width
|
||||
};
|
||||
|
||||
let padded_line = line_end + padding;
|
||||
let min_start = content_origin.x - scroll_pixel_position.x + min_x;
|
||||
|
||||
cmp::max(padded_line, min_start)
|
||||
};
|
||||
|
||||
let behind_inline_completion_popover = inline_completion_popover_origin
|
||||
.as_ref()
|
||||
.map_or(false, |inline_completion_popover_origin| {
|
||||
(pos_y..pos_y + line_height).contains(&inline_completion_popover_origin.y)
|
||||
});
|
||||
let opacity = if behind_inline_completion_popover {
|
||||
0.5
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let mut element = h_flex()
|
||||
.id(("diagnostic", row.0))
|
||||
.h(line_height)
|
||||
.w_full()
|
||||
.px_1()
|
||||
.rounded_sm()
|
||||
.opacity(opacity)
|
||||
.bg(severity_to_color(&diagnostic_to_render.severity)
|
||||
.color(cx)
|
||||
.opacity(0.05))
|
||||
.text_color(severity_to_color(&diagnostic_to_render.severity).color(cx))
|
||||
.text_sm()
|
||||
.font_family(style.text.font().family)
|
||||
.child(diagnostic_to_render.message.clone())
|
||||
.into_any();
|
||||
|
||||
element.prepaint_as_root(point(pos_x, pos_y), AvailableSpace::min_size(), window, cx);
|
||||
|
||||
elements.insert(row, element);
|
||||
}
|
||||
|
||||
elements
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_inline_blame(
|
||||
&self,
|
||||
|
@ -3573,7 +3725,7 @@ impl EditorElement {
|
|||
style: &EditorStyle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<AnyElement> {
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
const PADDING_X: Pixels = Pixels(24.);
|
||||
const PADDING_Y: Pixels = Pixels(2.);
|
||||
|
||||
|
@ -3626,7 +3778,7 @@ impl EditorElement {
|
|||
let origin = start_point + point(cursor_character_x, PADDING_Y);
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
return Some(element);
|
||||
return Some((element, origin));
|
||||
} else if target_display_point.row() >= visible_row_range.end {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
|
@ -3654,7 +3806,7 @@ impl EditorElement {
|
|||
);
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
return Some(element);
|
||||
return Some((element, origin));
|
||||
} else {
|
||||
const POLE_WIDTH: Pixels = px(2.);
|
||||
|
||||
|
@ -3694,7 +3846,7 @@ impl EditorElement {
|
|||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
|
||||
return Some(element);
|
||||
return Some((element, origin));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3711,8 +3863,9 @@ impl EditorElement {
|
|||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point((text_bounds.size.width - size.width) / 2., PADDING_Y);
|
||||
|
||||
element.prepaint_at(text_bounds.origin + offset, window, cx);
|
||||
Some(element)
|
||||
let origin = text_bounds.origin + offset;
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
} else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
|
@ -3729,8 +3882,9 @@ impl EditorElement {
|
|||
text_bounds.size.height - size.height - PADDING_Y,
|
||||
);
|
||||
|
||||
element.prepaint_at(text_bounds.origin + offset, window, cx);
|
||||
Some(element)
|
||||
let origin = text_bounds.origin + offset;
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
} else {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover("Jump to Edit", None, window, cx)?
|
||||
|
@ -3743,13 +3897,9 @@ impl EditorElement {
|
|||
editor.display_to_pixel_point(target_line_end, editor_snapshot, window)
|
||||
})?;
|
||||
|
||||
element.prepaint_as_root(
|
||||
clamp_start(start_point + origin + point(PADDING_X, px(0.))),
|
||||
AvailableSpace::min_size(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
Some(element)
|
||||
let origin = clamp_start(start_point + origin + point(PADDING_X, px(0.)));
|
||||
element.prepaint_as_root(origin, AvailableSpace::min_size(), window, cx);
|
||||
Some((element, origin))
|
||||
}
|
||||
}
|
||||
InlineCompletion::Edit {
|
||||
|
@ -3805,13 +3955,9 @@ impl EditorElement {
|
|||
))
|
||||
})?;
|
||||
|
||||
element.prepaint_as_root(
|
||||
clamp_start(start_point + origin + point(PADDING_X, px(0.))),
|
||||
AvailableSpace::min_size(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return Some(element);
|
||||
let origin = clamp_start(start_point + origin + point(PADDING_X, px(0.)));
|
||||
element.prepaint_as_root(origin, AvailableSpace::min_size(), window, cx);
|
||||
return Some((element, origin));
|
||||
}
|
||||
EditDisplayMode::Inline => return None,
|
||||
EditDisplayMode::DiffPopover => {}
|
||||
|
@ -4832,6 +4978,7 @@ impl EditorElement {
|
|||
self.paint_lines(&invisible_display_ranges, layout, window, cx);
|
||||
self.paint_redactions(layout, window);
|
||||
self.paint_cursors(layout, window, cx);
|
||||
self.paint_inline_diagnostics(layout, window, cx);
|
||||
self.paint_inline_blame(layout, window, cx);
|
||||
self.paint_diff_hunk_controls(layout, window, cx);
|
||||
window.with_element_namespace("crease_trailers", |window| {
|
||||
|
@ -5507,6 +5654,17 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn paint_inline_diagnostics(
|
||||
&mut self,
|
||||
layout: &mut EditorLayout,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
for mut inline_diagnostic in layout.inline_diagnostics.drain() {
|
||||
inline_diagnostic.1.paint(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
|
||||
if let Some(mut inline_blame) = layout.inline_blame.take() {
|
||||
window.paint_layer(layout.position_map.text_hitbox.bounds, |window| {
|
||||
|
@ -7221,6 +7379,40 @@ impl Element for EditorElement {
|
|||
)
|
||||
});
|
||||
|
||||
let (inline_completion_popover, inline_completion_popover_origin) = self
|
||||
.layout_edit_prediction_popover(
|
||||
&text_hitbox.bounds,
|
||||
content_origin,
|
||||
&snapshot,
|
||||
start_row..end_row,
|
||||
scroll_position.y,
|
||||
scroll_position.y + height_in_lines,
|
||||
&line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
editor_width,
|
||||
&style,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.unzip();
|
||||
|
||||
let mut inline_diagnostics = self.layout_inline_diagnostics(
|
||||
&line_layouts,
|
||||
&crease_trailers,
|
||||
content_origin,
|
||||
scroll_pixel_position,
|
||||
inline_completion_popover_origin,
|
||||
start_row,
|
||||
end_row,
|
||||
line_height,
|
||||
em_width,
|
||||
&style,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
let mut inline_blame = None;
|
||||
if let Some(newest_selection_head) = newest_selection_head {
|
||||
let display_row = newest_selection_head.row();
|
||||
|
@ -7241,6 +7433,10 @@ impl Element for EditorElement {
|
|||
window,
|
||||
cx,
|
||||
);
|
||||
if inline_blame.is_some() {
|
||||
// Blame overrides inline diagnostics
|
||||
inline_diagnostics.remove(&display_row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7477,23 +7673,6 @@ impl Element for EditorElement {
|
|||
);
|
||||
}
|
||||
|
||||
let inline_completion_popover = self.layout_edit_prediction_popover(
|
||||
&text_hitbox.bounds,
|
||||
content_origin,
|
||||
&snapshot,
|
||||
start_row..end_row,
|
||||
scroll_position.y,
|
||||
scroll_position.y + height_in_lines,
|
||||
&line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
editor_width,
|
||||
&style,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
let mouse_context_menu = self.layout_mouse_context_menu(
|
||||
&snapshot,
|
||||
start_row..end_row,
|
||||
|
@ -7600,6 +7779,7 @@ impl Element for EditorElement {
|
|||
line_elements,
|
||||
line_numbers,
|
||||
blamed_display_rows,
|
||||
inline_diagnostics,
|
||||
inline_blame,
|
||||
blocks,
|
||||
cursors,
|
||||
|
@ -7777,6 +7957,7 @@ pub struct EditorLayout {
|
|||
line_numbers: Arc<HashMap<MultiBufferRow, LineNumberLayout>>,
|
||||
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
|
||||
blamed_display_rows: Option<Vec<AnyElement>>,
|
||||
inline_diagnostics: HashMap<DisplayRow, AnyElement>,
|
||||
inline_blame: Option<AnyElement>,
|
||||
blocks: Vec<BlockLayout>,
|
||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
|
|
|
@ -40,6 +40,10 @@ pub struct ProjectSettings {
|
|||
#[serde(default)]
|
||||
pub lsp: HashMap<LanguageServerName, LspSettings>,
|
||||
|
||||
/// Configuration for Diagnostics-related features.
|
||||
#[serde(default)]
|
||||
pub diagnostics: DiagnosticsSettings,
|
||||
|
||||
/// Configuration for Git-related features
|
||||
#[serde(default)]
|
||||
pub git: GitSettings,
|
||||
|
@ -78,6 +82,77 @@ pub enum DirenvSettings {
|
|||
Direct,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DiagnosticsSettings {
|
||||
/// Whether or not to include warning diagnostics
|
||||
#[serde(default = "true_value")]
|
||||
pub include_warnings: bool,
|
||||
|
||||
/// Settings for showing inline diagnostics
|
||||
#[serde(default)]
|
||||
pub inline: InlineDiagnosticsSettings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct InlineDiagnosticsSettings {
|
||||
/// Whether or not to show inline diagnostics
|
||||
///
|
||||
/// Default: false
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
/// Whether to only show the inline diaganostics after a delay after the
|
||||
/// last editor event.
|
||||
///
|
||||
/// Default: 150
|
||||
#[serde(default = "default_inline_diagnostics_debounce_ms")]
|
||||
pub update_debounce_ms: u64,
|
||||
/// The amount of padding between the end of the source line and the start
|
||||
/// of the inline diagnostic in units of columns.
|
||||
///
|
||||
/// Default: 4
|
||||
#[serde(default = "default_inline_diagnostics_padding")]
|
||||
pub padding: u32,
|
||||
/// The minimum column to display inline diagnostics. This setting can be
|
||||
/// used to horizontally align inline diagnostics at some position. Lines
|
||||
/// longer than this value will still push diagnostics further to the right.
|
||||
///
|
||||
/// Default: 0
|
||||
#[serde(default)]
|
||||
pub min_column: u32,
|
||||
|
||||
#[serde(default)]
|
||||
pub max_severity: Option<DiagnosticSeverity>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DiagnosticSeverity {
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
Hint,
|
||||
}
|
||||
|
||||
impl Default for InlineDiagnosticsSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
update_debounce_ms: default_inline_diagnostics_debounce_ms(),
|
||||
padding: default_inline_diagnostics_padding(),
|
||||
min_column: 0,
|
||||
max_severity: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_inline_diagnostics_debounce_ms() -> u64 {
|
||||
150
|
||||
}
|
||||
|
||||
fn default_inline_diagnostics_padding() -> u32 {
|
||||
4
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GitSettings {
|
||||
/// Whether or not to show the git gutter.
|
||||
|
@ -156,7 +231,7 @@ pub struct InlineBlameSettings {
|
|||
/// Whether to show commit summary as part of the inline blame.
|
||||
///
|
||||
/// Default: false
|
||||
#[serde(default = "false_value")]
|
||||
#[serde(default)]
|
||||
pub show_commit_summary: bool,
|
||||
}
|
||||
|
||||
|
@ -164,10 +239,6 @@ const fn true_value() -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
const fn false_value() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct BinarySettings {
|
||||
pub path: Option<String>,
|
||||
|
|
|
@ -91,6 +91,8 @@ impl Render for QuickActionBar {
|
|||
selection_menu_enabled,
|
||||
inlay_hints_enabled,
|
||||
supports_inlay_hints,
|
||||
inline_diagnostics_enabled,
|
||||
supports_inline_diagnostics,
|
||||
git_blame_inline_enabled,
|
||||
show_git_blame_gutter,
|
||||
auto_signature_help_enabled,
|
||||
|
@ -102,6 +104,8 @@ impl Render for QuickActionBar {
|
|||
let editor = editor.read(cx);
|
||||
let selection_menu_enabled = editor.selection_menu_enabled(cx);
|
||||
let inlay_hints_enabled = editor.inlay_hints_enabled();
|
||||
let show_inline_diagnostics = editor.show_inline_diagnostics();
|
||||
let supports_inline_diagnostics = editor.inline_diagnostics_enabled();
|
||||
let git_blame_inline_enabled = editor.git_blame_inline_enabled();
|
||||
let show_git_blame_gutter = editor.show_git_blame_gutter();
|
||||
let auto_signature_help_enabled = editor.auto_signature_help_enabled(cx);
|
||||
|
@ -112,6 +116,8 @@ impl Render for QuickActionBar {
|
|||
selection_menu_enabled,
|
||||
inlay_hints_enabled,
|
||||
supports_inlay_hints,
|
||||
show_inline_diagnostics,
|
||||
supports_inline_diagnostics,
|
||||
git_blame_inline_enabled,
|
||||
show_git_blame_gutter,
|
||||
auto_signature_help_enabled,
|
||||
|
@ -257,6 +263,29 @@ impl Render for QuickActionBar {
|
|||
);
|
||||
}
|
||||
|
||||
if supports_inline_diagnostics {
|
||||
menu = menu.toggleable_entry(
|
||||
"Inline Diagnostics",
|
||||
inline_diagnostics_enabled,
|
||||
IconPosition::Start,
|
||||
Some(editor::actions::ToggleInlineDiagnostics.boxed_clone()),
|
||||
{
|
||||
let editor = editor.clone();
|
||||
move |window, cx| {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.toggle_inline_diagnostics(
|
||||
&editor::actions::ToggleInlineDiagnostics,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
menu = menu.toggleable_entry(
|
||||
"Selection Menu",
|
||||
selection_menu_enabled,
|
||||
|
|
|
@ -1204,6 +1204,112 @@ To interpret all `.c` files as C++, files called `MyLockFile` as TOML and files
|
|||
}
|
||||
```
|
||||
|
||||
## Diagnostics
|
||||
|
||||
- Description: Configuration for diagnostics-related features.
|
||||
- Setting: `diagnostics`
|
||||
- Default:
|
||||
|
||||
```json
|
||||
{
|
||||
"diagnostics": {
|
||||
"include_warnings": true,
|
||||
"inline": {
|
||||
"enabled": false
|
||||
}
|
||||
"update_with_cursor": false,
|
||||
"primary_only": false,
|
||||
"use_rendered": false,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Inline Diagnostics
|
||||
|
||||
- Description: Whether or not to show diagnostics information inline.
|
||||
- Setting: `inline`
|
||||
- Default:
|
||||
|
||||
```json
|
||||
{
|
||||
"diagnostics": {
|
||||
"inline": {
|
||||
"enabled": false,
|
||||
"update_debounce_ms": 150,
|
||||
"padding": 4,
|
||||
"min_column": 0,
|
||||
"max_severity": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Options**
|
||||
|
||||
1. Enable inline diagnostics.
|
||||
|
||||
```json
|
||||
{
|
||||
"diagnostics": {
|
||||
"inline": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Delay diagnostic updates until some time after the last diagnostic update.
|
||||
|
||||
```json
|
||||
{
|
||||
"diagnostics": {
|
||||
"inline": {
|
||||
"enabled": true,
|
||||
"update_debounce_ms": 150
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Set padding between the end of the source line and the start of the diagnostic.
|
||||
|
||||
```json
|
||||
{
|
||||
"diagnostics": {
|
||||
"inline": {
|
||||
"enabled": true,
|
||||
"padding": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Horizontally align inline diagnostics at the given column.
|
||||
|
||||
```json
|
||||
{
|
||||
"diagnostics": {
|
||||
"inline": {
|
||||
"enabled": true,
|
||||
"min_column": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. Show only warning and error diagnostics.
|
||||
|
||||
```json
|
||||
{
|
||||
"diagnostics": {
|
||||
"inline": {
|
||||
"enabled": true,
|
||||
"max_severity": "warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Git
|
||||
|
||||
- Description: Configuration for git-related features.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue