Files
oak-gpui/crates/diagnostics/src/toolbar_controls.rs
T
Lukas Wirth 73366bef62 diagnostics: Live update diagnostics view on edits while focused (#41829)
Prior we were only updating the diagnostics pane when it is either
unfocued, saved or when a disk based diagnostic run finishes (aka cargo
check). The reason for this is simple, we do not want to take away the
excerpt under the users cursor while they are typing if they manage to
fix the diagnostic. Additionally we need to prevent dropping the changed
buffer before it is saved.

Delaying updates was a simple way to work around these kind of issues,
but comes at a huge annoyance that the diagnostics pane is not actually
reflecting the current state of the world but some snapshot of it
instead making it less than ideal to work within it for languages that
do not leverage disk based diagnostics (that is not rust-analyzer, and
even for rust-analyzer its annoying).

This PR changes this. We now always live update the view but take care
to retain unsaved buffers as well as buffers that contain a cursor in
them (as well as some other "checkpoint" properties).

Release Notes:

- Improved diagnostics pane to live update when editing within its
editor
2025-11-03 16:16:05 +00:00

152 lines
5.6 KiB
Rust

use crate::{BufferDiagnosticsEditor, ProjectDiagnosticsEditor, ToggleDiagnosticsRefresh};
use gpui::{Context, EventEmitter, ParentElement, Render, Window};
use language::DiagnosticEntry;
use text::{Anchor, BufferId};
use ui::prelude::*;
use ui::{IconButton, IconButtonShape, IconName, Tooltip};
use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, item::ItemHandle};
pub struct ToolbarControls {
editor: Option<Box<dyn DiagnosticsToolbarEditor>>,
}
pub(crate) trait DiagnosticsToolbarEditor: Send + Sync {
/// Informs the toolbar whether warnings are included in the diagnostics.
fn include_warnings(&self, cx: &App) -> bool;
/// Toggles whether warning diagnostics should be displayed by the
/// diagnostics editor.
fn toggle_warnings(&self, window: &mut Window, cx: &mut App);
/// Indicates whether the diagnostics editor is currently updating the
/// diagnostics.
fn is_updating(&self, cx: &App) -> bool;
/// Requests that the diagnostics editor stop updating the diagnostics.
fn stop_updating(&self, cx: &mut App);
/// Requests that the diagnostics editor updates the displayed diagnostics
/// with the latest information.
fn refresh_diagnostics(&self, window: &mut Window, cx: &mut App);
/// Returns a list of diagnostics for the provided buffer id.
fn get_diagnostics_for_buffer(
&self,
buffer_id: BufferId,
cx: &App,
) -> Vec<DiagnosticEntry<Anchor>>;
}
impl Render for ToolbarControls {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let mut include_warnings = false;
let mut is_updating = false;
match &self.editor {
Some(editor) => {
include_warnings = editor.include_warnings(cx);
is_updating = editor.is_updating(cx);
}
None => {}
}
let warning_tooltip = if include_warnings {
"Exclude Warnings"
} else {
"Include Warnings"
};
let warning_color = if include_warnings {
Color::Warning
} else {
Color::Muted
};
h_flex()
.gap_1()
.map(|div| {
if is_updating {
div.child(
IconButton::new("stop-updating", IconName::Stop)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::for_action_title(
"Stop diagnostics update",
&ToggleDiagnosticsRefresh,
))
.on_click(cx.listener(move |toolbar_controls, _, _, cx| {
if let Some(editor) = toolbar_controls.editor() {
editor.stop_updating(cx);
cx.notify();
}
})),
)
} else {
div.child(
IconButton::new("refresh-diagnostics", IconName::ArrowCircle)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::for_action_title(
"Refresh diagnostics",
&ToggleDiagnosticsRefresh,
))
.on_click(cx.listener({
move |toolbar_controls, _, window, cx| {
if let Some(editor) = toolbar_controls.editor() {
editor.refresh_diagnostics(window, cx)
}
}
})),
)
}
})
.child(
IconButton::new("toggle-warnings", IconName::Warning)
.icon_color(warning_color)
.shape(IconButtonShape::Square)
.tooltip(Tooltip::text(warning_tooltip))
.on_click(cx.listener(|this, _, window, cx| {
if let Some(editor) = &this.editor {
editor.toggle_warnings(window, cx)
}
})),
)
}
}
impl EventEmitter<ToolbarItemEvent> for ToolbarControls {}
impl ToolbarItemView for ToolbarControls {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
_window: &mut Window,
_: &mut Context<Self>,
) -> ToolbarItemLocation {
if let Some(pane_item) = active_pane_item.as_ref() {
if let Some(editor) = pane_item.downcast::<ProjectDiagnosticsEditor>() {
self.editor = Some(Box::new(editor.downgrade()));
ToolbarItemLocation::PrimaryRight
} else if let Some(editor) = pane_item.downcast::<BufferDiagnosticsEditor>() {
self.editor = Some(Box::new(editor.downgrade()));
ToolbarItemLocation::PrimaryRight
} else {
ToolbarItemLocation::Hidden
}
} else {
ToolbarItemLocation::Hidden
}
}
}
impl Default for ToolbarControls {
fn default() -> Self {
Self::new()
}
}
impl ToolbarControls {
pub fn new() -> Self {
ToolbarControls { editor: None }
}
fn editor(&self) -> Option<&dyn DiagnosticsToolbarEditor> {
self.editor.as_deref()
}
}