From 73366bef6267bcab288e24102df8071ad9d4bb77 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 3 Nov 2025 17:16:05 +0100 Subject: [PATCH] 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 --- Cargo.lock | 1 + crates/diagnostics/Cargo.toml | 1 + crates/diagnostics/src/buffer_diagnostics.rs | 8 +- crates/diagnostics/src/diagnostics.rs | 234 ++++++++++++------- crates/diagnostics/src/diagnostics_tests.rs | 50 ++-- crates/diagnostics/src/toolbar_controls.rs | 6 - crates/multi_buffer/src/path_key.rs | 11 +- 7 files changed, 195 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd36815d55a948eec5a9bf8bbc2619ab72446c95..ebcdd881a2921e28e0d1c32d8625793d2ef58f52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4936,6 +4936,7 @@ dependencies = [ "editor", "gpui", "indoc", + "itertools 0.14.0", "language", "log", "lsp", diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index 5bb6892f0cea9500fd66671f8e8e86ab9a6d901a..0eccf44c357125e5e11fcdccda8280c22006c6fa 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -34,6 +34,7 @@ theme.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true +itertools.workspace = true [dev-dependencies] client = { workspace = true, features = ["test-support"] } diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index 1205cef385fdd91af8e3f986b432b9fff4ad3ac6..8fe503a706027fb6ed2f0b9114450eb79c2aa027 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -1,5 +1,5 @@ use crate::{ - DIAGNOSTICS_UPDATE_DELAY, IncludeWarnings, ToggleWarnings, context_range_for_entry, + DIAGNOSTICS_UPDATE_DEBOUNCE, IncludeWarnings, ToggleWarnings, context_range_for_entry, diagnostic_renderer::{DiagnosticBlock, DiagnosticRenderer}, toolbar_controls::DiagnosticsToolbarEditor, }; @@ -283,7 +283,7 @@ impl BufferDiagnosticsEditor { self.update_excerpts_task = Some(cx.spawn_in(window, async move |editor, cx| { cx.background_executor() - .timer(DIAGNOSTICS_UPDATE_DELAY) + .timer(DIAGNOSTICS_UPDATE_DEBOUNCE) .await; if let Some(buffer) = buffer { @@ -938,10 +938,6 @@ impl DiagnosticsToolbarEditor for WeakEntity { .unwrap_or(false) } - fn has_stale_excerpts(&self, _cx: &App) -> bool { - false - } - fn is_updating(&self, cx: &App) -> bool { self.read_with(cx, |buffer_diagnostics_editor, cx| { buffer_diagnostics_editor.update_excerpts_task.is_some() diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 5a43fd135391a5e3d97d5c65e6d3be826210f102..5506cdba9ae4aaa7fbf1246aaa7b07e653ad0efc 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -9,7 +9,7 @@ mod diagnostics_tests; use anyhow::Result; use buffer_diagnostics::BufferDiagnosticsEditor; -use collections::{BTreeSet, HashMap}; +use collections::{BTreeSet, HashMap, HashSet}; use diagnostic_renderer::DiagnosticBlock; use editor::{ Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, @@ -17,10 +17,11 @@ use editor::{ multibuffer_context_lines, }; use gpui::{ - AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable, - Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, - Subscription, Task, WeakEntity, Window, actions, div, + AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, FocusOutEvent, + Focusable, Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, + Styled, Subscription, Task, WeakEntity, Window, actions, div, }; +use itertools::Itertools as _; use language::{ Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, DiagnosticEntryRef, Point, ToTreeSitterPoint, @@ -32,7 +33,7 @@ use project::{ use settings::Settings; use std::{ any::{Any, TypeId}, - cmp::{self, Ordering}, + cmp, ops::{Range, RangeInclusive}, sync::Arc, time::Duration, @@ -89,8 +90,8 @@ pub(crate) struct ProjectDiagnosticsEditor { impl EventEmitter for ProjectDiagnosticsEditor {} -const DIAGNOSTICS_UPDATE_DELAY: Duration = Duration::from_millis(50); -const DIAGNOSTICS_SUMMARY_UPDATE_DELAY: Duration = Duration::from_millis(30); +const DIAGNOSTICS_UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); +const DIAGNOSTICS_SUMMARY_UPDATE_DEBOUNCE: Duration = Duration::from_millis(30); impl Render for ProjectDiagnosticsEditor { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { @@ -149,6 +150,12 @@ impl Render for ProjectDiagnosticsEditor { } } +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +enum RetainExcerpts { + Yes, + No, +} + impl ProjectDiagnosticsEditor { pub fn register( workspace: &mut Workspace, @@ -165,14 +172,21 @@ impl ProjectDiagnosticsEditor { window: &mut Window, cx: &mut Context, ) -> Self { - let project_event_subscription = - cx.subscribe_in(&project_handle, window, |this, _project, event, window, cx| match event { + let project_event_subscription = cx.subscribe_in( + &project_handle, + window, + |this, _project, event, window, cx| match event { project::Event::DiskBasedDiagnosticsStarted { .. } => { cx.notify(); } project::Event::DiskBasedDiagnosticsFinished { language_server_id } => { log::debug!("disk based diagnostics finished for server {language_server_id}"); - this.update_stale_excerpts(window, cx); + this.close_diagnosticless_buffers( + window, + cx, + this.editor.focus_handle(cx).contains_focused(window, cx) + || this.focus_handle.contains_focused(window, cx), + ); } project::Event::DiagnosticsUpdated { language_server_id, @@ -181,34 +195,39 @@ impl ProjectDiagnosticsEditor { this.paths_to_update.extend(paths.clone()); this.diagnostic_summary_update = cx.spawn(async move |this, cx| { cx.background_executor() - .timer(DIAGNOSTICS_SUMMARY_UPDATE_DELAY) + .timer(DIAGNOSTICS_SUMMARY_UPDATE_DEBOUNCE) .await; this.update(cx, |this, cx| { this.update_diagnostic_summary(cx); }) .log_err(); }); - cx.emit(EditorEvent::TitleChanged); - if this.editor.focus_handle(cx).contains_focused(window, cx) || this.focus_handle.contains_focused(window, cx) { - log::debug!("diagnostics updated for server {language_server_id}, paths {paths:?}. recording change"); - } else { - log::debug!("diagnostics updated for server {language_server_id}, paths {paths:?}. updating excerpts"); - this.update_stale_excerpts(window, cx); - } + log::debug!( + "diagnostics updated for server {language_server_id}, \ + paths {paths:?}. updating excerpts" + ); + let focused = this.editor.focus_handle(cx).contains_focused(window, cx) + || this.focus_handle.contains_focused(window, cx); + this.update_stale_excerpts( + if focused { + RetainExcerpts::Yes + } else { + RetainExcerpts::No + }, + window, + cx, + ); } _ => {} - }); + }, + ); let focus_handle = cx.focus_handle(); - cx.on_focus_in(&focus_handle, window, |this, window, cx| { - this.focus_in(window, cx) - }) - .detach(); - cx.on_focus_out(&focus_handle, window, |this, _event, window, cx| { - this.focus_out(window, cx) - }) - .detach(); + cx.on_focus_in(&focus_handle, window, Self::focus_in) + .detach(); + cx.on_focus_out(&focus_handle, window, Self::focus_out) + .detach(); let excerpts = cx.new(|cx| MultiBuffer::new(project_handle.read(cx).capability())); let editor = cx.new(|cx| { @@ -238,8 +257,11 @@ impl ProjectDiagnosticsEditor { window.focus(&this.focus_handle); } } - EditorEvent::Blurred => this.update_stale_excerpts(window, cx), - EditorEvent::Saved => this.update_stale_excerpts(window, cx), + EditorEvent::Blurred => this.close_diagnosticless_buffers(window, cx, false), + EditorEvent::Saved => this.close_diagnosticless_buffers(window, cx, true), + EditorEvent::SelectionsChanged { .. } => { + this.close_diagnosticless_buffers(window, cx, true) + } _ => {} } }, @@ -283,15 +305,67 @@ impl ProjectDiagnosticsEditor { this } - fn update_stale_excerpts(&mut self, window: &mut Window, cx: &mut Context) { - if self.update_excerpts_task.is_some() || self.multibuffer.read(cx).is_dirty(cx) { + /// Closes all excerpts of buffers that: + /// - have no diagnostics anymore + /// - are saved (not dirty) + /// - and, if `reatin_selections` is true, do not have selections within them + fn close_diagnosticless_buffers( + &mut self, + _window: &mut Window, + cx: &mut Context, + retain_selections: bool, + ) { + let buffer_ids = self.multibuffer.read(cx).all_buffer_ids(); + let selected_buffers = self.editor.update(cx, |editor, cx| { + editor + .selections + .all_anchors(cx) + .iter() + .filter_map(|anchor| anchor.start.buffer_id) + .collect::>() + }); + for buffer_id in buffer_ids { + if retain_selections && selected_buffers.contains(&buffer_id) { + continue; + } + let has_blocks = self + .blocks + .get(&buffer_id) + .is_none_or(|blocks| blocks.is_empty()); + if !has_blocks { + continue; + } + let is_dirty = self + .multibuffer + .read(cx) + .buffer(buffer_id) + .is_some_and(|buffer| buffer.read(cx).is_dirty()); + if !is_dirty { + continue; + } + self.multibuffer.update(cx, |b, cx| { + b.remove_excerpts_for_buffer(buffer_id, cx); + }); + } + } + + fn update_stale_excerpts( + &mut self, + mut retain_excerpts: RetainExcerpts, + window: &mut Window, + cx: &mut Context, + ) { + if self.update_excerpts_task.is_some() { return; } + if self.multibuffer.read(cx).is_dirty(cx) { + retain_excerpts = RetainExcerpts::Yes; + } let project_handle = self.project.clone(); self.update_excerpts_task = Some(cx.spawn_in(window, async move |this, cx| { cx.background_executor() - .timer(DIAGNOSTICS_UPDATE_DELAY) + .timer(DIAGNOSTICS_UPDATE_DEBOUNCE) .await; loop { let Some(path) = this.update(cx, |this, cx| { @@ -312,7 +386,7 @@ impl ProjectDiagnosticsEditor { .log_err() { this.update_in(cx, |this, window, cx| { - this.update_excerpts(buffer, window, cx) + this.update_excerpts(buffer, retain_excerpts, window, cx) })? .await?; } @@ -378,10 +452,10 @@ impl ProjectDiagnosticsEditor { } } - fn focus_out(&mut self, window: &mut Window, cx: &mut Context) { + fn focus_out(&mut self, _: FocusOutEvent, window: &mut Window, cx: &mut Context) { if !self.focus_handle.is_focused(window) && !self.editor.focus_handle(cx).is_focused(window) { - self.update_stale_excerpts(window, cx); + self.close_diagnosticless_buffers(window, cx, false); } } @@ -403,12 +477,13 @@ impl ProjectDiagnosticsEditor { }); } } + multibuffer.clear(cx); }); self.paths_to_update = project_paths; }); - self.update_stale_excerpts(window, cx); + self.update_stale_excerpts(RetainExcerpts::No, window, cx); } fn diagnostics_are_unchanged( @@ -431,6 +506,7 @@ impl ProjectDiagnosticsEditor { fn update_excerpts( &mut self, buffer: Entity, + retain_excerpts: RetainExcerpts, window: &mut Window, cx: &mut Context, ) -> Task> { @@ -497,24 +573,27 @@ impl ProjectDiagnosticsEditor { ) })?; - for item in more { - let i = blocks - .binary_search_by(|probe| { - probe - .initial_range - .start - .cmp(&item.initial_range.start) - .then(probe.initial_range.end.cmp(&item.initial_range.end)) - .then(Ordering::Greater) - }) - .unwrap_or_else(|i| i); - blocks.insert(i, item); - } + blocks.extend(more); } - let mut excerpt_ranges: Vec> = Vec::new(); + let mut excerpt_ranges: Vec> = match retain_excerpts { + RetainExcerpts::No => Vec::new(), + RetainExcerpts::Yes => this.update(cx, |this, cx| { + this.multibuffer.update(cx, |multi_buffer, cx| { + multi_buffer + .excerpts_for_buffer(buffer_id, cx) + .into_iter() + .map(|(_, range)| ExcerptRange { + context: range.context.to_point(&buffer_snapshot), + primary: range.primary.to_point(&buffer_snapshot), + }) + .collect() + }) + })?, + }; + let mut result_blocks = vec![None; excerpt_ranges.len()]; let context_lines = cx.update(|_, cx| multibuffer_context_lines(cx))?; - for b in blocks.iter() { + for b in blocks { let excerpt_range = context_range_for_entry( b.initial_range.clone(), context_lines, @@ -541,7 +620,8 @@ impl ProjectDiagnosticsEditor { context: excerpt_range, primary: b.initial_range.clone(), }, - ) + ); + result_blocks.insert(i, Some(b)); } this.update_in(cx, |this, window, cx| { @@ -562,7 +642,7 @@ impl ProjectDiagnosticsEditor { ) }); #[cfg(test)] - let cloned_blocks = blocks.clone(); + let cloned_blocks = result_blocks.clone(); if was_empty && let Some(anchor_range) = anchor_ranges.first() { let range_to_select = anchor_range.start..anchor_range.start; @@ -576,22 +656,20 @@ impl ProjectDiagnosticsEditor { } } - let editor_blocks = - anchor_ranges - .into_iter() - .zip(blocks.into_iter()) - .map(|(anchor, block)| { - let editor = this.editor.downgrade(); - BlockProperties { - placement: BlockPlacement::Near(anchor.start), - height: Some(1), - style: BlockStyle::Flex, - render: Arc::new(move |bcx| { - block.render_block(editor.clone(), bcx) - }), - priority: 1, - } - }); + let editor_blocks = anchor_ranges + .into_iter() + .zip_eq(result_blocks.into_iter()) + .filter_map(|(anchor, block)| { + let block = block?; + let editor = this.editor.downgrade(); + Some(BlockProperties { + placement: BlockPlacement::Near(anchor.start), + height: Some(1), + style: BlockStyle::Flex, + render: Arc::new(move |bcx| block.render_block(editor.clone(), bcx)), + priority: 1, + }) + }); let block_ids = this.editor.update(cx, |editor, cx| { editor.display_map.update(cx, |display_map, cx| { @@ -601,7 +679,9 @@ impl ProjectDiagnosticsEditor { #[cfg(test)] { - for (block_id, block) in block_ids.iter().zip(cloned_blocks.iter()) { + for (block_id, block) in + block_ids.iter().zip(cloned_blocks.into_iter().flatten()) + { let markdown = block.markdown.clone(); editor::test::set_block_content_for_tests( &this.editor, @@ -626,6 +706,7 @@ impl ProjectDiagnosticsEditor { fn update_diagnostic_summary(&mut self, cx: &mut Context) { self.summary = self.project.read(cx).diagnostic_summary(false, cx); + cx.emit(EditorEvent::TitleChanged); } } @@ -843,13 +924,6 @@ impl DiagnosticsToolbarEditor for WeakEntity { .unwrap_or(false) } - fn has_stale_excerpts(&self, cx: &App) -> bool { - self.read_with(cx, |project_diagnostics_editor, _cx| { - !project_diagnostics_editor.paths_to_update.is_empty() - }) - .unwrap_or(false) - } - fn is_updating(&self, cx: &App) -> bool { self.read_with(cx, |project_diagnostics_editor, cx| { project_diagnostics_editor.update_excerpts_task.is_some() @@ -1010,12 +1084,6 @@ async fn heuristic_syntactic_expand( return; } } - - log::info!( - "Expanding to ancestor started on {} node\ - exceeding row limit of {max_row_count}.", - node.grammar_name() - ); *ancestor_range = Some(None); } }) diff --git a/crates/diagnostics/src/diagnostics_tests.rs b/crates/diagnostics/src/diagnostics_tests.rs index 824d4db6a58c06db5df4c04ac79ee1e509d55d4d..6e85a38aae602774892b17e87859fbd6ed745b4b 100644 --- a/crates/diagnostics/src/diagnostics_tests.rs +++ b/crates/diagnostics/src/diagnostics_tests.rs @@ -119,7 +119,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) { let editor = diagnostics.update(cx, |diagnostics, _| diagnostics.editor.clone()); diagnostics - .next_notification(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10), cx) + .next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; pretty_assertions::assert_eq!( @@ -190,7 +190,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) { }); diagnostics - .next_notification(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10), cx) + .next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; pretty_assertions::assert_eq!( @@ -277,7 +277,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) { }); diagnostics - .next_notification(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10), cx) + .next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; pretty_assertions::assert_eq!( @@ -391,7 +391,7 @@ async fn test_diagnostics_with_folds(cx: &mut TestAppContext) { // Only the first language server's diagnostics are shown. cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.executor().run_until_parked(); editor.update_in(cx, |editor, window, cx| { editor.fold_ranges(vec![Point::new(0, 0)..Point::new(3, 0)], false, window, cx); @@ -490,7 +490,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { // Only the first language server's diagnostics are shown. cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.executor().run_until_parked(); pretty_assertions::assert_eq!( @@ -530,7 +530,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { // Both language server's diagnostics are shown. cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.executor().run_until_parked(); pretty_assertions::assert_eq!( @@ -587,7 +587,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { // Only the first language server's diagnostics are updated. cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.executor().run_until_parked(); pretty_assertions::assert_eq!( @@ -629,7 +629,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) { // Both language servers' diagnostics are updated. cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.executor().run_until_parked(); pretty_assertions::assert_eq!( @@ -760,7 +760,7 @@ async fn test_random_diagnostics_blocks(cx: &mut TestAppContext, mut rng: StdRng .unwrap() }); cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.run_until_parked(); } @@ -769,7 +769,7 @@ async fn test_random_diagnostics_blocks(cx: &mut TestAppContext, mut rng: StdRng log::info!("updating mutated diagnostics view"); mutated_diagnostics.update_in(cx, |diagnostics, window, cx| { - diagnostics.update_stale_excerpts(window, cx) + diagnostics.update_stale_excerpts(RetainExcerpts::No, window, cx) }); log::info!("constructing reference diagnostics view"); @@ -777,7 +777,7 @@ async fn test_random_diagnostics_blocks(cx: &mut TestAppContext, mut rng: StdRng ProjectDiagnosticsEditor::new(true, project.clone(), workspace.downgrade(), window, cx) }); cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.run_until_parked(); let mutated_excerpts = @@ -789,7 +789,12 @@ async fn test_random_diagnostics_blocks(cx: &mut TestAppContext, mut rng: StdRng // The mutated view may contain more than the reference view as // we don't currently shrink excerpts when diagnostics were removed. - let mut ref_iter = reference_excerpts.lines().filter(|line| *line != "§ -----"); + let mut ref_iter = reference_excerpts.lines().filter(|line| { + // ignore $ ---- and $ .rs + !line.starts_with('§') + || line.starts_with("§ diagnostic") + || line.starts_with("§ related info") + }); let mut next_ref_line = ref_iter.next(); let mut skipped_block = false; @@ -797,7 +802,12 @@ async fn test_random_diagnostics_blocks(cx: &mut TestAppContext, mut rng: StdRng if let Some(ref_line) = next_ref_line { if mut_line == ref_line { next_ref_line = ref_iter.next(); - } else if mut_line.contains('§') && mut_line != "§ -----" { + } else if mut_line.contains('§') + // ignore $ ---- and $ .rs + && (!mut_line.starts_with('§') + || mut_line.starts_with("§ diagnostic") + || mut_line.starts_with("§ related info")) + { skipped_block = true; } } @@ -949,7 +959,7 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S .unwrap() }); cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.run_until_parked(); } @@ -958,11 +968,11 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S log::info!("updating mutated diagnostics view"); mutated_diagnostics.update_in(cx, |diagnostics, window, cx| { - diagnostics.update_stale_excerpts(window, cx) + diagnostics.update_stale_excerpts(RetainExcerpts::No, window, cx) }); cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); cx.run_until_parked(); } @@ -1427,7 +1437,7 @@ async fn test_diagnostics_with_code(cx: &mut TestAppContext) { let editor = diagnostics.update(cx, |diagnostics, _| diagnostics.editor.clone()); diagnostics - .next_notification(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10), cx) + .next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx) .await; // Verify that the diagnostic codes are displayed correctly @@ -1704,7 +1714,7 @@ async fn test_buffer_diagnostics(cx: &mut TestAppContext) { // wait a little bit to ensure that the buffer diagnostic's editor content // is rendered. cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); pretty_assertions::assert_eq!( editor_content_with_blocks(&editor, cx), @@ -1837,7 +1847,7 @@ async fn test_buffer_diagnostics_without_warnings(cx: &mut TestAppContext) { // wait a little bit to ensure that the buffer diagnostic's editor content // is rendered. cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); pretty_assertions::assert_eq!( editor_content_with_blocks(&editor, cx), @@ -1971,7 +1981,7 @@ async fn test_buffer_diagnostics_multiple_servers(cx: &mut TestAppContext) { // wait a little bit to ensure that the buffer diagnostic's editor content // is rendered. cx.executor() - .advance_clock(DIAGNOSTICS_UPDATE_DELAY + Duration::from_millis(10)); + .advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10)); pretty_assertions::assert_eq!( editor_content_with_blocks(&editor, cx), diff --git a/crates/diagnostics/src/toolbar_controls.rs b/crates/diagnostics/src/toolbar_controls.rs index b55fa5783dc96965a7d1ce7f52c5e4336b674ed2..2ba64d39dfd63fc246bf3dedf5974909c0d67a6f 100644 --- a/crates/diagnostics/src/toolbar_controls.rs +++ b/crates/diagnostics/src/toolbar_controls.rs @@ -16,9 +16,6 @@ pub(crate) trait DiagnosticsToolbarEditor: Send + Sync { /// Toggles whether warning diagnostics should be displayed by the /// diagnostics editor. fn toggle_warnings(&self, window: &mut Window, cx: &mut App); - /// Indicates whether any of the excerpts displayed by the diagnostics - /// editor are stale. - fn has_stale_excerpts(&self, cx: &App) -> bool; /// Indicates whether the diagnostics editor is currently updating the /// diagnostics. fn is_updating(&self, cx: &App) -> bool; @@ -37,14 +34,12 @@ pub(crate) trait DiagnosticsToolbarEditor: Send + Sync { impl Render for ToolbarControls { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let mut has_stale_excerpts = false; let mut include_warnings = false; let mut is_updating = false; match &self.editor { Some(editor) => { include_warnings = editor.include_warnings(cx); - has_stale_excerpts = editor.has_stale_excerpts(cx); is_updating = editor.is_updating(cx); } None => {} @@ -86,7 +81,6 @@ impl Render for ToolbarControls { IconButton::new("refresh-diagnostics", IconName::ArrowCircle) .icon_color(Color::Info) .shape(IconButtonShape::Square) - .disabled(!has_stale_excerpts) .tooltip(Tooltip::for_action_title( "Refresh diagnostics", &ToggleDiagnosticsRefresh, diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index 568d1ac8671fc3e10fb7656dfdffa7211accd1cd..c750bb912f0c2767e4c56890d0ab75046c094e71 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -5,7 +5,7 @@ use gpui::{App, AppContext, Context, Entity}; use itertools::Itertools; use language::{Buffer, BufferSnapshot}; use rope::Point; -use text::{Bias, OffsetRangeExt, locator::Locator}; +use text::{Bias, BufferId, OffsetRangeExt, locator::Locator}; use util::{post_inc, rel_path::RelPath}; use crate::{ @@ -152,6 +152,15 @@ impl MultiBuffer { } } + pub fn remove_excerpts_for_buffer(&mut self, buffer: BufferId, cx: &mut Context) { + self.remove_excerpts( + self.excerpts_for_buffer(buffer, cx) + .into_iter() + .map(|(excerpt, _)| excerpt), + cx, + ); + } + pub(super) fn expand_excerpts_with_paths( &mut self, ids: impl IntoIterator,