From c98c3ae55f0896c59a6ac7a77bdf2a1556fa9cde Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 15 Oct 2025 21:13:13 +0200 Subject: [PATCH] diagnostics: Live update diagnostics view on edits --- Cargo.lock | 1 + crates/diagnostics/Cargo.toml | 1 + crates/diagnostics/src/buffer_diagnostics.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 217 ++++++++++++------- crates/diagnostics/src/diagnostics_tests.rs | 50 +++-- crates/multi_buffer/src/path_key.rs | 11 +- 6 files changed, 185 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ee239c3740bb0fba40f81b9cdae77a46d62d535..36b21382daa1ee2a9a9b786f1ae51dc9acc15f48 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..a5041cbc72618cbeb0a9d8058dd7ab8802bdaaed 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 { diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 5a43fd135391a5e3d97d5c65e6d3be826210f102..86a0f483234571c1393cf9c884c3d15e746260ae 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,58 @@ 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) { + fn close_diagnosticless_buffers( + &mut self, + _window: &mut Window, + cx: &mut Context, + retain_selections: bool, + ) { + // todo: Retain only the dirty buffers + if self.multibuffer.read(cx).is_dirty(cx) { return; } + 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; + } + if self + .blocks + .get(&buffer_id) + .is_none_or(|blocks| blocks.is_empty()) + { + 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 +377,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 +443,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); } } @@ -408,7 +473,7 @@ impl ProjectDiagnosticsEditor { 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 +496,7 @@ impl ProjectDiagnosticsEditor { fn update_excerpts( &mut self, buffer: Entity, + retain_excerpts: RetainExcerpts, window: &mut Window, cx: &mut Context, ) -> Task> { @@ -497,24 +563,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 +610,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 +632,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 +646,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 +669,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 +696,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); } } @@ -1010,12 +1081,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/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,