1use anyhow::Result;
2use collections::{BTreeMap, BTreeSet, HashMap, HashSet};
3use editor::{
4 diagnostic_block_renderer,
5 display_map::{
6 BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, CustomBlockId,
7 RenderBlock,
8 },
9 scroll::Autoscroll,
10 Bias, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToPoint,
11};
12use futures::{
13 channel::mpsc::{self, UnboundedSender},
14 StreamExt as _,
15};
16use gpui::{
17 actions, div, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
18 FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, SharedString,
19 Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
20};
21use language::{
22 Buffer, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, OffsetRangeExt, ToOffset,
23 ToPoint as _,
24};
25use lsp::LanguageServerId;
26use multi_buffer::{build_excerpt_ranges, ExpandExcerptDirection, MultiBufferRow};
27use project::{DiagnosticSummary, Project, ProjectPath};
28use settings::Settings;
29use std::{
30 any::{Any, TypeId},
31 cmp::Ordering,
32 ops::Range,
33 sync::{
34 atomic::{self, AtomicBool},
35 Arc,
36 },
37};
38use theme::ActiveTheme;
39use ui::{h_flex, prelude::*, Icon, IconName, Label};
40use util::{debug_panic, ResultExt};
41use workspace::{
42 item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
43 ItemNavHistory, ToolbarItemLocation, Workspace,
44};
45
46use crate::project_diagnostics_settings::ProjectDiagnosticsSettings;
47actions!(grouped_diagnostics, [Deploy, ToggleWarnings]);
48
49pub fn init(cx: &mut AppContext) {
50 cx.observe_new_views(GroupedDiagnosticsEditor::register)
51 .detach();
52}
53
54pub struct GroupedDiagnosticsEditor {
55 pub project: Model<Project>,
56 workspace: WeakView<Workspace>,
57 focus_handle: FocusHandle,
58 editor: View<Editor>,
59 summary: DiagnosticSummary,
60 excerpts: Model<MultiBuffer>,
61 path_states: Vec<PathState>,
62 pub paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
63 pub include_warnings: bool,
64 context: u32,
65 pub update_paths_tx: UnboundedSender<(ProjectPath, Option<LanguageServerId>)>,
66 _update_excerpts_task: Task<Result<()>>,
67 _subscription: Subscription,
68}
69
70struct PathState {
71 path: ProjectPath,
72 first_excerpt_id: Option<ExcerptId>,
73 last_excerpt_id: Option<ExcerptId>,
74 diagnostics: Vec<(DiagnosticData, CustomBlockId)>,
75}
76
77#[derive(Debug, Clone)]
78struct DiagnosticData {
79 language_server_id: LanguageServerId,
80 is_primary: bool,
81 entry: DiagnosticEntry<language::Anchor>,
82}
83
84impl DiagnosticData {
85 fn diagnostic_entries_equal(&self, other: &DiagnosticData) -> bool {
86 self.language_server_id == other.language_server_id
87 && self.is_primary == other.is_primary
88 && self.entry.range == other.entry.range
89 && equal_without_group_ids(&self.entry.diagnostic, &other.entry.diagnostic)
90 }
91}
92
93// `group_id` can differ between LSP server diagnostics output,
94// hence ignore it when checking diagnostics for updates.
95fn equal_without_group_ids(a: &language::Diagnostic, b: &language::Diagnostic) -> bool {
96 a.source == b.source
97 && a.code == b.code
98 && a.severity == b.severity
99 && a.message == b.message
100 && a.is_primary == b.is_primary
101 && a.is_disk_based == b.is_disk_based
102 && a.is_unnecessary == b.is_unnecessary
103}
104
105impl EventEmitter<EditorEvent> for GroupedDiagnosticsEditor {}
106
107impl Render for GroupedDiagnosticsEditor {
108 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
109 let child = if self.path_states.is_empty() {
110 div()
111 .bg(cx.theme().colors().editor_background)
112 .flex()
113 .items_center()
114 .justify_center()
115 .size_full()
116 .child(Label::new("No problems in workspace"))
117 } else {
118 div().size_full().child(self.editor.clone())
119 };
120
121 div()
122 .track_focus(&self.focus_handle)
123 .when(self.path_states.is_empty(), |el| {
124 el.key_context("EmptyPane")
125 })
126 .size_full()
127 .on_action(cx.listener(Self::toggle_warnings))
128 .child(child)
129 }
130}
131
132impl GroupedDiagnosticsEditor {
133 fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
134 workspace.register_action(Self::deploy);
135 }
136
137 fn new_with_context(
138 context: u32,
139 project_handle: Model<Project>,
140 workspace: WeakView<Workspace>,
141 cx: &mut ViewContext<Self>,
142 ) -> Self {
143 let project_event_subscription =
144 cx.subscribe(&project_handle, |this, project, event, cx| match event {
145 project::Event::DiskBasedDiagnosticsStarted { .. } => {
146 cx.notify();
147 }
148 project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
149 log::debug!("disk based diagnostics finished for server {language_server_id}");
150 this.enqueue_update_stale_excerpts(Some(*language_server_id));
151 }
152 project::Event::DiagnosticsUpdated {
153 language_server_id,
154 path,
155 } => {
156 this.paths_to_update
157 .insert((path.clone(), *language_server_id));
158 this.summary = project.read(cx).diagnostic_summary(false, cx);
159 cx.emit(EditorEvent::TitleChanged);
160
161 if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
162 log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
163 } else {
164 log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
165 this.enqueue_update_stale_excerpts(Some(*language_server_id));
166 }
167 }
168 _ => {}
169 });
170
171 let focus_handle = cx.focus_handle();
172 cx.on_focus_in(&focus_handle, |this, cx| this.focus_in(cx))
173 .detach();
174 cx.on_focus_out(&focus_handle, |this, _event, cx| this.focus_out(cx))
175 .detach();
176
177 let excerpts = cx.new_model(|cx| {
178 MultiBuffer::new(
179 project_handle.read(cx).replica_id(),
180 project_handle.read(cx).capability(),
181 )
182 });
183 let editor = cx.new_view(|cx| {
184 let mut editor =
185 Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), false, cx);
186 editor.set_vertical_scroll_margin(5, cx);
187 editor
188 });
189 cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
190 cx.emit(event.clone());
191 match event {
192 EditorEvent::Focused => {
193 if this.path_states.is_empty() {
194 cx.focus(&this.focus_handle);
195 }
196 }
197 EditorEvent::Blurred => this.enqueue_update_stale_excerpts(None),
198 _ => {}
199 }
200 })
201 .detach();
202
203 let (update_excerpts_tx, mut update_excerpts_rx) = mpsc::unbounded();
204
205 let project = project_handle.read(cx);
206 let mut this = Self {
207 project: project_handle.clone(),
208 context,
209 summary: project.diagnostic_summary(false, cx),
210 workspace,
211 excerpts,
212 focus_handle,
213 editor,
214 path_states: Vec::new(),
215 paths_to_update: BTreeSet::new(),
216 include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
217 update_paths_tx: update_excerpts_tx,
218 _update_excerpts_task: cx.spawn(move |this, mut cx| async move {
219 while let Some((path, language_server_id)) = update_excerpts_rx.next().await {
220 if let Some(buffer) = project_handle
221 .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
222 .await
223 .log_err()
224 {
225 this.update(&mut cx, |this, cx| {
226 this.update_excerpts(path, language_server_id, buffer, cx);
227 })?;
228 }
229 }
230 anyhow::Ok(())
231 }),
232 _subscription: project_event_subscription,
233 };
234 this.enqueue_update_all_excerpts(cx);
235 this
236 }
237
238 fn new(
239 project_handle: Model<Project>,
240 workspace: WeakView<Workspace>,
241 cx: &mut ViewContext<Self>,
242 ) -> Self {
243 Self::new_with_context(
244 editor::DEFAULT_MULTIBUFFER_CONTEXT,
245 project_handle,
246 workspace,
247 cx,
248 )
249 }
250
251 fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
252 if let Some(existing) = workspace.item_of_type::<GroupedDiagnosticsEditor>(cx) {
253 workspace.activate_item(&existing, true, true, cx);
254 } else {
255 let workspace_handle = cx.view().downgrade();
256 let diagnostics = cx.new_view(|cx| {
257 GroupedDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
258 });
259 workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
260 }
261 }
262
263 pub fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
264 self.include_warnings = !self.include_warnings;
265 self.enqueue_update_all_excerpts(cx);
266 cx.notify();
267 }
268
269 fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
270 if self.focus_handle.is_focused(cx) && !self.path_states.is_empty() {
271 self.editor.focus_handle(cx).focus(cx)
272 }
273 }
274
275 fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
276 if !self.focus_handle.is_focused(cx) && !self.editor.focus_handle(cx).is_focused(cx) {
277 self.enqueue_update_stale_excerpts(None);
278 }
279 }
280
281 /// Enqueue an update of all excerpts. Updates all paths that either
282 /// currently have diagnostics or are currently present in this view.
283 fn enqueue_update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
284 self.project.update(cx, |project, cx| {
285 let mut paths = project
286 .diagnostic_summaries(false, cx)
287 .map(|(path, _, _)| path)
288 .collect::<BTreeSet<_>>();
289 paths.extend(self.path_states.iter().map(|state| state.path.clone()));
290 for path in paths {
291 self.update_paths_tx.unbounded_send((path, None)).unwrap();
292 }
293 });
294 }
295
296 /// Enqueue an update of the excerpts for any path whose diagnostics are known
297 /// to have changed. If a language server id is passed, then only the excerpts for
298 /// that language server's diagnostics will be updated. Otherwise, all stale excerpts
299 /// will be refreshed.
300 pub fn enqueue_update_stale_excerpts(&mut self, language_server_id: Option<LanguageServerId>) {
301 for (path, server_id) in &self.paths_to_update {
302 if language_server_id.map_or(true, |id| id == *server_id) {
303 self.update_paths_tx
304 .unbounded_send((path.clone(), Some(*server_id)))
305 .unwrap();
306 }
307 }
308 }
309
310 fn update_excerpts(
311 &mut self,
312 path_to_update: ProjectPath,
313 server_to_update: Option<LanguageServerId>,
314 buffer: Model<Buffer>,
315 cx: &mut ViewContext<Self>,
316 ) {
317 self.paths_to_update.retain(|(path, server_id)| {
318 *path != path_to_update
319 || server_to_update.map_or(false, |to_update| *server_id != to_update)
320 });
321
322 // TODO change selections as in the old panel, to the next primary diagnostics
323 // TODO make [shift-]f8 to work, jump to the next block group
324 let _was_empty = self.path_states.is_empty();
325 let path_ix = match self.path_states.binary_search_by(|probe| {
326 project::compare_paths((&probe.path.path, true), (&path_to_update.path, true))
327 }) {
328 Ok(ix) => ix,
329 Err(ix) => {
330 self.path_states.insert(
331 ix,
332 PathState {
333 path: path_to_update.clone(),
334 diagnostics: Vec::new(),
335 last_excerpt_id: None,
336 first_excerpt_id: None,
337 },
338 );
339 ix
340 }
341 };
342
343 let max_severity = if self.include_warnings {
344 DiagnosticSeverity::WARNING
345 } else {
346 DiagnosticSeverity::ERROR
347 };
348
349 let excerpt_borders = self.excerpt_borders_for_path(path_ix);
350 let path_state = &mut self.path_states[path_ix];
351 let buffer_snapshot = buffer.read(cx).snapshot();
352
353 let mut path_update = PathUpdate::new(
354 excerpt_borders,
355 &buffer_snapshot,
356 server_to_update,
357 max_severity,
358 path_state,
359 );
360 path_update.prepare_excerpt_data(
361 self.context,
362 self.excerpts.read(cx).snapshot(cx),
363 buffer.read(cx).snapshot(),
364 path_state.diagnostics.iter(),
365 );
366 self.excerpts.update(cx, |multi_buffer, cx| {
367 path_update.apply_excerpt_changes(
368 path_state,
369 self.context,
370 buffer_snapshot,
371 multi_buffer,
372 buffer,
373 cx,
374 );
375 });
376
377 let new_multi_buffer_snapshot = self.excerpts.read(cx).snapshot(cx);
378 let blocks_to_insert =
379 path_update.prepare_blocks_to_insert(self.editor.clone(), new_multi_buffer_snapshot);
380
381 let new_block_ids = self.editor.update(cx, |editor, cx| {
382 editor.remove_blocks(std::mem::take(&mut path_update.blocks_to_remove), None, cx);
383 editor.insert_blocks(blocks_to_insert, Some(Autoscroll::fit()), cx)
384 });
385 path_state.diagnostics = path_update.new_blocks(new_block_ids);
386
387 if self.path_states.is_empty() {
388 if self.editor.focus_handle(cx).is_focused(cx) {
389 cx.focus(&self.focus_handle);
390 }
391 } else if self.focus_handle.is_focused(cx) {
392 let focus_handle = self.editor.focus_handle(cx);
393 cx.focus(&focus_handle);
394 }
395
396 #[cfg(test)]
397 self.check_invariants(cx);
398
399 cx.notify();
400 }
401
402 fn excerpt_borders_for_path(&self, path_ix: usize) -> (Option<ExcerptId>, Option<ExcerptId>) {
403 let previous_path_state_ix =
404 Some(path_ix.saturating_sub(1)).filter(|&previous_path_ix| previous_path_ix != path_ix);
405 let next_path_state_ix = path_ix + 1;
406 let start = previous_path_state_ix.and_then(|i| {
407 self.path_states[..=i]
408 .iter()
409 .rev()
410 .find_map(|state| state.last_excerpt_id)
411 });
412 let end = self.path_states[next_path_state_ix..]
413 .iter()
414 .find_map(|state| state.first_excerpt_id);
415 (start, end)
416 }
417
418 #[cfg(test)]
419 fn check_invariants(&self, cx: &mut ViewContext<Self>) {
420 let mut excerpts = Vec::new();
421 for (id, buffer, _) in self.excerpts.read(cx).snapshot(cx).excerpts() {
422 if let Some(file) = buffer.file() {
423 excerpts.push((id, file.path().clone()));
424 }
425 }
426
427 let mut prev_path = None;
428 for (_, path) in &excerpts {
429 if let Some(prev_path) = prev_path {
430 if path < prev_path {
431 panic!("excerpts are not sorted by path {:?}", excerpts);
432 }
433 }
434 prev_path = Some(path);
435 }
436 }
437}
438
439impl FocusableView for GroupedDiagnosticsEditor {
440 fn focus_handle(&self, _: &AppContext) -> FocusHandle {
441 self.focus_handle.clone()
442 }
443}
444
445impl Item for GroupedDiagnosticsEditor {
446 type Event = EditorEvent;
447
448 fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
449 Editor::to_item_events(event, f)
450 }
451
452 fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
453 self.editor.update(cx, |editor, cx| editor.deactivated(cx));
454 }
455
456 fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
457 self.editor
458 .update(cx, |editor, cx| editor.navigate(data, cx))
459 }
460
461 fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
462 Some("Project Diagnostics".into())
463 }
464
465 fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
466 if self.summary.error_count == 0 && self.summary.warning_count == 0 {
467 Label::new("No problems")
468 .color(params.text_color())
469 .into_any_element()
470 } else {
471 h_flex()
472 .gap_1()
473 .when(self.summary.error_count > 0, |then| {
474 then.child(
475 h_flex()
476 .gap_1()
477 .child(Icon::new(IconName::XCircle).color(Color::Error))
478 .child(
479 Label::new(self.summary.error_count.to_string())
480 .color(params.text_color()),
481 ),
482 )
483 })
484 .when(self.summary.warning_count > 0, |then| {
485 then.child(
486 h_flex()
487 .gap_1()
488 .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
489 .child(
490 Label::new(self.summary.warning_count.to_string())
491 .color(params.text_color()),
492 ),
493 )
494 })
495 .into_any_element()
496 }
497 }
498
499 fn telemetry_event_text(&self) -> Option<&'static str> {
500 Some("project diagnostics")
501 }
502
503 fn for_each_project_item(
504 &self,
505 cx: &AppContext,
506 f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
507 ) {
508 self.editor.for_each_project_item(cx, f)
509 }
510
511 fn is_singleton(&self, _: &AppContext) -> bool {
512 false
513 }
514
515 fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
516 self.editor.update(cx, |editor, _| {
517 editor.set_nav_history(Some(nav_history));
518 });
519 }
520
521 fn clone_on_split(
522 &self,
523 _workspace_id: Option<workspace::WorkspaceId>,
524 cx: &mut ViewContext<Self>,
525 ) -> Option<View<Self>>
526 where
527 Self: Sized,
528 {
529 Some(cx.new_view(|cx| {
530 GroupedDiagnosticsEditor::new(self.project.clone(), self.workspace.clone(), cx)
531 }))
532 }
533
534 fn is_dirty(&self, cx: &AppContext) -> bool {
535 self.excerpts.read(cx).is_dirty(cx)
536 }
537
538 fn has_conflict(&self, cx: &AppContext) -> bool {
539 self.excerpts.read(cx).has_conflict(cx)
540 }
541
542 fn can_save(&self, _: &AppContext) -> bool {
543 true
544 }
545
546 fn save(
547 &mut self,
548 format: bool,
549 project: Model<Project>,
550 cx: &mut ViewContext<Self>,
551 ) -> Task<Result<()>> {
552 self.editor.save(format, project, cx)
553 }
554
555 fn save_as(
556 &mut self,
557 _: Model<Project>,
558 _: ProjectPath,
559 _: &mut ViewContext<Self>,
560 ) -> Task<Result<()>> {
561 unreachable!()
562 }
563
564 fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
565 self.editor.reload(project, cx)
566 }
567
568 fn act_as_type<'a>(
569 &'a self,
570 type_id: TypeId,
571 self_handle: &'a View<Self>,
572 _: &'a AppContext,
573 ) -> Option<AnyView> {
574 if type_id == TypeId::of::<Self>() {
575 Some(self_handle.to_any())
576 } else if type_id == TypeId::of::<Editor>() {
577 Some(self.editor.to_any())
578 } else {
579 None
580 }
581 }
582
583 fn breadcrumb_location(&self) -> ToolbarItemLocation {
584 ToolbarItemLocation::PrimaryLeft
585 }
586
587 fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
588 self.editor.breadcrumbs(theme, cx)
589 }
590
591 fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
592 self.editor
593 .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
594 }
595}
596
597fn compare_data_locations(
598 old: &DiagnosticData,
599 new: &DiagnosticData,
600 snapshot: &BufferSnapshot,
601) -> Ordering {
602 compare_diagnostics(&old.entry, &new.entry, snapshot)
603 .then_with(|| old.language_server_id.cmp(&new.language_server_id))
604}
605
606fn compare_diagnostics(
607 old: &DiagnosticEntry<language::Anchor>,
608 new: &DiagnosticEntry<language::Anchor>,
609 snapshot: &BufferSnapshot,
610) -> Ordering {
611 compare_diagnostic_ranges(&old.range, &new.range, snapshot)
612 .then_with(|| old.diagnostic.message.cmp(&new.diagnostic.message))
613}
614
615fn compare_diagnostic_ranges(
616 old: &Range<language::Anchor>,
617 new: &Range<language::Anchor>,
618 snapshot: &BufferSnapshot,
619) -> Ordering {
620 // The diagnostics may point to a previously open Buffer for this file.
621 if !old.start.is_valid(snapshot) || !new.start.is_valid(snapshot) {
622 return Ordering::Greater;
623 }
624
625 old.start
626 .to_offset(snapshot)
627 .cmp(&new.start.to_offset(snapshot))
628 .then_with(|| {
629 old.end
630 .to_offset(snapshot)
631 .cmp(&new.end.to_offset(snapshot))
632 })
633}
634
635// TODO wrong? What to do here instead?
636fn compare_diagnostic_range_edges(
637 old: &Range<language::Anchor>,
638 new: &Range<language::Anchor>,
639 snapshot: &BufferSnapshot,
640) -> (Ordering, Ordering) {
641 // The diagnostics may point to a previously open Buffer for this file.
642 let start_cmp = match (old.start.is_valid(snapshot), new.start.is_valid(snapshot)) {
643 (false, false) => old.start.offset.cmp(&new.start.offset),
644 (false, true) => Ordering::Greater,
645 (true, false) => Ordering::Less,
646 (true, true) => old.start.cmp(&new.start, snapshot),
647 };
648
649 let end_cmp = old
650 .end
651 .to_offset(snapshot)
652 .cmp(&new.end.to_offset(snapshot));
653 (start_cmp, end_cmp)
654}
655
656#[derive(Debug)]
657struct PathUpdate {
658 path_excerpts_borders: (Option<ExcerptId>, Option<ExcerptId>),
659 latest_excerpt_id: ExcerptId,
660 new_diagnostics: Vec<(DiagnosticData, Option<CustomBlockId>)>,
661 diagnostics_by_row_label: BTreeMap<MultiBufferRow, (editor::Anchor, Vec<usize>)>,
662 blocks_to_remove: HashSet<CustomBlockId>,
663 unchanged_blocks: HashMap<usize, CustomBlockId>,
664 excerpts_with_new_diagnostics: HashSet<ExcerptId>,
665 excerpts_to_remove: Vec<ExcerptId>,
666 excerpt_expands: HashMap<(ExpandExcerptDirection, u32), Vec<ExcerptId>>,
667 excerpts_to_add: HashMap<ExcerptId, Vec<Range<language::Anchor>>>,
668 first_excerpt_id: Option<ExcerptId>,
669 last_excerpt_id: Option<ExcerptId>,
670}
671
672impl PathUpdate {
673 fn new(
674 path_excerpts_borders: (Option<ExcerptId>, Option<ExcerptId>),
675 buffer_snapshot: &BufferSnapshot,
676 server_to_update: Option<LanguageServerId>,
677 max_severity: DiagnosticSeverity,
678 path_state: &PathState,
679 ) -> Self {
680 let mut blocks_to_remove = HashSet::default();
681 let mut removed_groups = HashSet::default();
682 let mut new_diagnostics = path_state
683 .diagnostics
684 .iter()
685 .filter(|(diagnostic_data, _)| {
686 server_to_update.map_or(true, |server_id| {
687 diagnostic_data.language_server_id != server_id
688 })
689 })
690 .filter(|(diagnostic_data, block_id)| {
691 let diagnostic = &diagnostic_data.entry.diagnostic;
692 let retain = !diagnostic.is_primary || diagnostic.severity <= max_severity;
693 if !retain {
694 removed_groups.insert(diagnostic.group_id);
695 blocks_to_remove.insert(*block_id);
696 }
697 retain
698 })
699 .map(|(diagnostic, block_id)| (diagnostic.clone(), Some(*block_id)))
700 .collect::<Vec<_>>();
701 new_diagnostics.retain(|(diagnostic_data, block_id)| {
702 let retain = !removed_groups.contains(&diagnostic_data.entry.diagnostic.group_id);
703 if !retain {
704 if let Some(block_id) = block_id {
705 blocks_to_remove.insert(*block_id);
706 }
707 }
708 retain
709 });
710 for (server_id, group) in buffer_snapshot
711 .diagnostic_groups(server_to_update)
712 .into_iter()
713 .filter(|(_, group)| {
714 group.entries[group.primary_ix].diagnostic.severity <= max_severity
715 })
716 {
717 for (diagnostic_index, diagnostic) in group.entries.iter().enumerate() {
718 let new_data = DiagnosticData {
719 language_server_id: server_id,
720 is_primary: diagnostic_index == group.primary_ix,
721 entry: diagnostic.clone(),
722 };
723 let (Ok(i) | Err(i)) = new_diagnostics.binary_search_by(|probe| {
724 compare_data_locations(&probe.0, &new_data, &buffer_snapshot)
725 });
726 new_diagnostics.insert(i, (new_data, None));
727 }
728 }
729
730 let latest_excerpt_id = path_excerpts_borders.0.unwrap_or_else(|| ExcerptId::min());
731 Self {
732 latest_excerpt_id,
733 path_excerpts_borders,
734 new_diagnostics,
735 blocks_to_remove,
736 diagnostics_by_row_label: BTreeMap::new(),
737 excerpts_to_remove: Vec::new(),
738 excerpts_with_new_diagnostics: HashSet::default(),
739 unchanged_blocks: HashMap::default(),
740 excerpts_to_add: HashMap::default(),
741 excerpt_expands: HashMap::default(),
742 first_excerpt_id: None,
743 last_excerpt_id: None,
744 }
745 }
746
747 fn prepare_excerpt_data<'a>(
748 &'a mut self,
749 context: u32,
750 multi_buffer_snapshot: MultiBufferSnapshot,
751 buffer_snapshot: BufferSnapshot,
752 current_diagnostics: impl Iterator<Item = &'a (DiagnosticData, CustomBlockId)> + 'a,
753 ) {
754 let mut current_diagnostics = current_diagnostics.fuse().peekable();
755 let mut excerpts_to_expand =
756 HashMap::<ExcerptId, HashMap<ExpandExcerptDirection, u32>>::default();
757 let mut current_excerpts = path_state_excerpts(
758 self.path_excerpts_borders.0,
759 self.path_excerpts_borders.1,
760 &multi_buffer_snapshot,
761 )
762 .fuse()
763 .peekable();
764
765 for (diagnostic_index, (new_diagnostic, existing_block)) in
766 self.new_diagnostics.iter().enumerate()
767 {
768 if let Some(existing_block) = existing_block {
769 self.unchanged_blocks
770 .insert(diagnostic_index, *existing_block);
771 }
772
773 loop {
774 match current_excerpts.peek() {
775 None => {
776 let excerpt_ranges = self
777 .excerpts_to_add
778 .entry(self.latest_excerpt_id)
779 .or_default();
780 let new_range = new_diagnostic.entry.range.clone();
781 let (Ok(i) | Err(i)) = excerpt_ranges.binary_search_by(|probe| {
782 compare_diagnostic_ranges(probe, &new_range, &buffer_snapshot)
783 });
784 excerpt_ranges.insert(i, new_range);
785 break;
786 }
787 Some((current_excerpt_id, _, current_excerpt_range)) => {
788 match compare_diagnostic_range_edges(
789 ¤t_excerpt_range.context,
790 &new_diagnostic.entry.range,
791 &buffer_snapshot,
792 ) {
793 /*
794 new_s new_e
795 ----[---->><<----]--
796 cur_s cur_e
797 */
798 (
799 Ordering::Less | Ordering::Equal,
800 Ordering::Greater | Ordering::Equal,
801 ) => {
802 self.excerpts_with_new_diagnostics
803 .insert(*current_excerpt_id);
804 if self.first_excerpt_id.is_none() {
805 self.first_excerpt_id = Some(*current_excerpt_id);
806 }
807 self.last_excerpt_id = Some(*current_excerpt_id);
808 break;
809 }
810 /*
811 cur_s cur_e
812 ---->>>>>[--]<<<<<--
813 new_s new_e
814 */
815 (
816 Ordering::Greater | Ordering::Equal,
817 Ordering::Less | Ordering::Equal,
818 ) => {
819 let expand_up = current_excerpt_range
820 .context
821 .start
822 .to_point(&buffer_snapshot)
823 .row
824 .saturating_sub(
825 new_diagnostic
826 .entry
827 .range
828 .start
829 .to_point(&buffer_snapshot)
830 .row,
831 );
832 let expand_down = new_diagnostic
833 .entry
834 .range
835 .end
836 .to_point(&buffer_snapshot)
837 .row
838 .saturating_sub(
839 current_excerpt_range
840 .context
841 .end
842 .to_point(&buffer_snapshot)
843 .row,
844 );
845 let expand_value = excerpts_to_expand
846 .entry(*current_excerpt_id)
847 .or_default()
848 .entry(ExpandExcerptDirection::UpAndDown)
849 .or_default();
850 *expand_value = (*expand_value).max(expand_up).max(expand_down);
851 self.excerpts_with_new_diagnostics
852 .insert(*current_excerpt_id);
853 if self.first_excerpt_id.is_none() {
854 self.first_excerpt_id = Some(*current_excerpt_id);
855 }
856 self.last_excerpt_id = Some(*current_excerpt_id);
857 break;
858 }
859 /*
860 new_s new_e
861 > <
862 ----[---->>>]<<<<<--
863 cur_s cur_e
864
865 or
866 new_s new_e
867 > <
868 ----[----]-->>><<<--
869 cur_s cur_e
870 */
871 (Ordering::Less, Ordering::Less) => {
872 if current_excerpt_range
873 .context
874 .end
875 .cmp(&new_diagnostic.entry.range.start, &buffer_snapshot)
876 .is_ge()
877 {
878 let expand_down = new_diagnostic
879 .entry
880 .range
881 .end
882 .to_point(&buffer_snapshot)
883 .row
884 .saturating_sub(
885 current_excerpt_range
886 .context
887 .end
888 .to_point(&buffer_snapshot)
889 .row,
890 );
891 let expand_value = excerpts_to_expand
892 .entry(*current_excerpt_id)
893 .or_default()
894 .entry(ExpandExcerptDirection::Down)
895 .or_default();
896 *expand_value = (*expand_value).max(expand_down);
897 self.excerpts_with_new_diagnostics
898 .insert(*current_excerpt_id);
899 if self.first_excerpt_id.is_none() {
900 self.first_excerpt_id = Some(*current_excerpt_id);
901 }
902 self.last_excerpt_id = Some(*current_excerpt_id);
903 break;
904 } else if !self
905 .excerpts_with_new_diagnostics
906 .contains(current_excerpt_id)
907 {
908 self.excerpts_to_remove.push(*current_excerpt_id);
909 }
910 }
911 /*
912 cur_s cur_e
913 ---->>>>>[<<<<----]--
914 > <
915 new_s new_e
916
917 or
918 cur_s cur_e
919 ---->>><<<--[----]--
920 > <
921 new_s new_e
922 */
923 (Ordering::Greater, Ordering::Greater) => {
924 if current_excerpt_range
925 .context
926 .start
927 .cmp(&new_diagnostic.entry.range.end, &buffer_snapshot)
928 .is_le()
929 {
930 let expand_up = current_excerpt_range
931 .context
932 .start
933 .to_point(&buffer_snapshot)
934 .row
935 .saturating_sub(
936 new_diagnostic
937 .entry
938 .range
939 .start
940 .to_point(&buffer_snapshot)
941 .row,
942 );
943 let expand_value = excerpts_to_expand
944 .entry(*current_excerpt_id)
945 .or_default()
946 .entry(ExpandExcerptDirection::Up)
947 .or_default();
948 *expand_value = (*expand_value).max(expand_up);
949 self.excerpts_with_new_diagnostics
950 .insert(*current_excerpt_id);
951 if self.first_excerpt_id.is_none() {
952 self.first_excerpt_id = Some(*current_excerpt_id);
953 }
954 self.last_excerpt_id = Some(*current_excerpt_id);
955 break;
956 } else {
957 let excerpt_ranges = self
958 .excerpts_to_add
959 .entry(self.latest_excerpt_id)
960 .or_default();
961 let new_range = new_diagnostic.entry.range.clone();
962 let (Ok(i) | Err(i)) =
963 excerpt_ranges.binary_search_by(|probe| {
964 compare_diagnostic_ranges(
965 probe,
966 &new_range,
967 &buffer_snapshot,
968 )
969 });
970 excerpt_ranges.insert(i, new_range);
971 break;
972 }
973 }
974 }
975 if let Some((next_id, ..)) = current_excerpts.next() {
976 self.latest_excerpt_id = next_id;
977 }
978 }
979 }
980 }
981
982 loop {
983 match current_diagnostics.peek() {
984 None => break,
985 Some((current_diagnostic, current_block)) => {
986 match compare_data_locations(
987 current_diagnostic,
988 new_diagnostic,
989 &buffer_snapshot,
990 ) {
991 Ordering::Less => {
992 self.blocks_to_remove.insert(*current_block);
993 }
994 Ordering::Equal => {
995 if current_diagnostic.diagnostic_entries_equal(&new_diagnostic) {
996 self.unchanged_blocks
997 .insert(diagnostic_index, *current_block);
998 } else {
999 self.blocks_to_remove.insert(*current_block);
1000 }
1001 let _ = current_diagnostics.next();
1002 break;
1003 }
1004 Ordering::Greater => break,
1005 }
1006 let _ = current_diagnostics.next();
1007 }
1008 }
1009 }
1010 }
1011
1012 self.excerpts_to_remove.retain(|excerpt_id| {
1013 !self.excerpts_with_new_diagnostics.contains(excerpt_id)
1014 && !excerpts_to_expand.contains_key(excerpt_id)
1015 });
1016 self.excerpts_to_remove.extend(
1017 current_excerpts
1018 .filter(|(excerpt_id, ..)| {
1019 !self.excerpts_with_new_diagnostics.contains(excerpt_id)
1020 && !excerpts_to_expand.contains_key(excerpt_id)
1021 })
1022 .map(|(excerpt_id, ..)| excerpt_id),
1023 );
1024 let mut excerpt_expands = HashMap::default();
1025 for (excerpt_id, directions) in excerpts_to_expand {
1026 let excerpt_expand = if directions.len() > 1 {
1027 Some((
1028 ExpandExcerptDirection::UpAndDown,
1029 directions
1030 .values()
1031 .max()
1032 .copied()
1033 .unwrap_or_default()
1034 .max(context),
1035 ))
1036 } else {
1037 directions
1038 .into_iter()
1039 .next()
1040 .map(|(direction, expand)| (direction, expand.max(context)))
1041 };
1042 if let Some(expand) = excerpt_expand {
1043 excerpt_expands
1044 .entry(expand)
1045 .or_insert_with(|| Vec::new())
1046 .push(excerpt_id);
1047 }
1048 }
1049 self.blocks_to_remove
1050 .extend(current_diagnostics.map(|(_, block_id)| block_id));
1051 }
1052
1053 fn apply_excerpt_changes(
1054 &mut self,
1055 path_state: &mut PathState,
1056 context: u32,
1057 buffer_snapshot: BufferSnapshot,
1058 multi_buffer: &mut MultiBuffer,
1059 buffer: Model<Buffer>,
1060 cx: &mut gpui::ModelContext<MultiBuffer>,
1061 ) {
1062 let max_point = buffer_snapshot.max_point();
1063 for (after_excerpt_id, ranges) in std::mem::take(&mut self.excerpts_to_add) {
1064 let ranges = ranges
1065 .into_iter()
1066 .map(|range| {
1067 let mut extended_point_range = range.to_point(&buffer_snapshot);
1068 extended_point_range.start.row =
1069 extended_point_range.start.row.saturating_sub(context);
1070 extended_point_range.start.column = 0;
1071 extended_point_range.end.row =
1072 (extended_point_range.end.row + context).min(max_point.row);
1073 extended_point_range.end.column = u32::MAX;
1074 let extended_start =
1075 buffer_snapshot.clip_point(extended_point_range.start, Bias::Left);
1076 let extended_end =
1077 buffer_snapshot.clip_point(extended_point_range.end, Bias::Right);
1078 extended_start..extended_end
1079 })
1080 .collect::<Vec<_>>();
1081 let (joined_ranges, _) = build_excerpt_ranges(&buffer_snapshot, &ranges, context);
1082 let excerpts = multi_buffer.insert_excerpts_after(
1083 after_excerpt_id,
1084 buffer.clone(),
1085 joined_ranges,
1086 cx,
1087 );
1088 if self.first_excerpt_id.is_none() {
1089 self.first_excerpt_id = excerpts.first().copied();
1090 }
1091 self.last_excerpt_id = excerpts.last().copied();
1092 }
1093 for ((direction, line_count), excerpts) in std::mem::take(&mut self.excerpt_expands) {
1094 multi_buffer.expand_excerpts(excerpts, line_count, direction, cx);
1095 }
1096 multi_buffer.remove_excerpts(std::mem::take(&mut self.excerpts_to_remove), cx);
1097 path_state.first_excerpt_id = self.first_excerpt_id;
1098 path_state.last_excerpt_id = self.last_excerpt_id;
1099 }
1100
1101 fn prepare_blocks_to_insert(
1102 &mut self,
1103 editor: View<Editor>,
1104 multi_buffer_snapshot: MultiBufferSnapshot,
1105 ) -> Vec<BlockProperties<editor::Anchor>> {
1106 let mut updated_excerpts = path_state_excerpts(
1107 self.path_excerpts_borders.0,
1108 self.path_excerpts_borders.1,
1109 &multi_buffer_snapshot,
1110 )
1111 .fuse()
1112 .peekable();
1113 let mut used_labels = BTreeMap::new();
1114 self.diagnostics_by_row_label = self.new_diagnostics.iter().enumerate().fold(
1115 BTreeMap::new(),
1116 |mut diagnostics_by_row_label, (diagnostic_index, (diagnostic, existing_block))| {
1117 let new_diagnostic = &diagnostic.entry;
1118 let block_position = new_diagnostic.range.start;
1119 let excerpt_id = loop {
1120 match updated_excerpts.peek() {
1121 None => break None,
1122 Some((excerpt_id, excerpt_buffer_snapshot, excerpt_range)) => {
1123 let excerpt_range = &excerpt_range.context;
1124 match block_position.cmp(&excerpt_range.start, excerpt_buffer_snapshot)
1125 {
1126 Ordering::Less => break None,
1127 Ordering::Equal | Ordering::Greater => match block_position
1128 .cmp(&excerpt_range.end, excerpt_buffer_snapshot)
1129 {
1130 Ordering::Equal | Ordering::Less => break Some(*excerpt_id),
1131 Ordering::Greater => {
1132 let _ = updated_excerpts.next();
1133 }
1134 },
1135 }
1136 }
1137 }
1138 };
1139
1140 let Some(position_in_multi_buffer) = excerpt_id.and_then(|excerpt_id| {
1141 multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, block_position)
1142 }) else {
1143 return diagnostics_by_row_label;
1144 };
1145
1146 let multi_buffer_row = MultiBufferRow(
1147 position_in_multi_buffer
1148 .to_point(&multi_buffer_snapshot)
1149 .row,
1150 );
1151
1152 let grouped_diagnostics = &mut diagnostics_by_row_label
1153 .entry(multi_buffer_row)
1154 .or_insert_with(|| (position_in_multi_buffer, Vec::new()))
1155 .1;
1156 let new_label = used_labels
1157 .entry(multi_buffer_row)
1158 .or_insert_with(|| HashSet::default())
1159 .insert((
1160 new_diagnostic.diagnostic.source.as_deref(),
1161 new_diagnostic.diagnostic.message.as_str(),
1162 ));
1163
1164 if !new_label || !grouped_diagnostics.is_empty() {
1165 if let Some(existing_block) = existing_block {
1166 self.blocks_to_remove.insert(*existing_block);
1167 }
1168 if let Some(block_id) = self.unchanged_blocks.remove(&diagnostic_index) {
1169 self.blocks_to_remove.insert(block_id);
1170 }
1171 }
1172 if new_label {
1173 let (Ok(i) | Err(i)) = grouped_diagnostics.binary_search_by(|&probe| {
1174 let a = &self.new_diagnostics[probe].0.entry.diagnostic;
1175 let b = &self.new_diagnostics[diagnostic_index].0.entry.diagnostic;
1176 a.group_id
1177 .cmp(&b.group_id)
1178 .then_with(|| a.is_primary.cmp(&b.is_primary).reverse())
1179 .then_with(|| a.severity.cmp(&b.severity))
1180 });
1181 grouped_diagnostics.insert(i, diagnostic_index);
1182 }
1183
1184 diagnostics_by_row_label
1185 },
1186 );
1187
1188 self.diagnostics_by_row_label
1189 .values()
1190 .filter_map(|(earliest_in_row_position, diagnostics_at_line)| {
1191 let earliest_in_row_position = *earliest_in_row_position;
1192 match diagnostics_at_line.len() {
1193 0 => None,
1194 len => {
1195 if len == 1 {
1196 let i = diagnostics_at_line.first().copied()?;
1197 if self.unchanged_blocks.contains_key(&i) {
1198 return None;
1199 }
1200 }
1201 let lines_in_first_message = diagnostic_text_lines(
1202 &self
1203 .new_diagnostics
1204 .get(diagnostics_at_line.first().copied()?)?
1205 .0
1206 .entry
1207 .diagnostic,
1208 );
1209 let folded_block_height = lines_in_first_message.clamp(1, 2);
1210 let diagnostics_to_render = Arc::new(
1211 diagnostics_at_line
1212 .iter()
1213 .filter_map(|&index| self.new_diagnostics.get(index))
1214 .map(|(diagnostic_data, _)| {
1215 diagnostic_data.entry.diagnostic.clone()
1216 })
1217 .collect::<Vec<_>>(),
1218 );
1219 Some(BlockProperties {
1220 position: earliest_in_row_position,
1221 height: folded_block_height,
1222 style: BlockStyle::Sticky,
1223 render: render_same_line_diagnostics(
1224 Arc::new(AtomicBool::new(false)),
1225 diagnostics_to_render,
1226 editor.clone(),
1227 folded_block_height,
1228 ),
1229 disposition: BlockDisposition::Above,
1230 })
1231 }
1232 }
1233 })
1234 .collect()
1235 }
1236
1237 fn new_blocks(
1238 mut self,
1239 new_block_ids: Vec<CustomBlockId>,
1240 ) -> Vec<(DiagnosticData, CustomBlockId)> {
1241 let mut new_block_ids = new_block_ids.into_iter().fuse();
1242 for (_, (_, grouped_diagnostics)) in self.diagnostics_by_row_label {
1243 let mut created_block_id = None;
1244 match grouped_diagnostics.len() {
1245 0 => {
1246 debug_panic!("Unexpected empty diagnostics group");
1247 continue;
1248 }
1249 1 => {
1250 let index = grouped_diagnostics[0];
1251 if let Some(&block_id) = self.unchanged_blocks.get(&index) {
1252 self.new_diagnostics[index].1 = Some(block_id);
1253 } else {
1254 let Some(block_id) =
1255 created_block_id.get_or_insert_with(|| new_block_ids.next())
1256 else {
1257 debug_panic!("Expected a new block for each new diagnostic");
1258 continue;
1259 };
1260 self.new_diagnostics[index].1 = Some(*block_id);
1261 }
1262 }
1263 _ => {
1264 let Some(block_id) =
1265 created_block_id.get_or_insert_with(|| new_block_ids.next())
1266 else {
1267 debug_panic!("Expected a new block for each new diagnostic group");
1268 continue;
1269 };
1270 for i in grouped_diagnostics {
1271 self.new_diagnostics[i].1 = Some(*block_id);
1272 }
1273 }
1274 }
1275 }
1276
1277 self.new_diagnostics
1278 .into_iter()
1279 .filter_map(|(diagnostic, block_id)| Some((diagnostic, block_id?)))
1280 .collect()
1281 }
1282}
1283
1284fn render_same_line_diagnostics(
1285 expanded: Arc<AtomicBool>,
1286 diagnostics: Arc<Vec<language::Diagnostic>>,
1287 editor_handle: View<Editor>,
1288 folded_block_height: u8,
1289) -> RenderBlock {
1290 Box::new(move |cx: &mut BlockContext| {
1291 let block_id = match cx.block_id {
1292 BlockId::Custom(block_id) => block_id,
1293 _ => {
1294 debug_panic!("Expected a block id for the diagnostics block");
1295 return div().into_any_element();
1296 }
1297 };
1298 let Some(first_diagnostic) = diagnostics.first() else {
1299 debug_panic!("Expected at least one diagnostic");
1300 return div().into_any_element();
1301 };
1302 let button_expanded = expanded.clone();
1303 let expanded = expanded.load(atomic::Ordering::Acquire);
1304 let expand_label = if expanded { '-' } else { '+' };
1305 let first_diagnostics_height = diagnostic_text_lines(first_diagnostic);
1306 let extra_diagnostics = diagnostics.len() - 1;
1307 let toggle_expand_label =
1308 if folded_block_height == first_diagnostics_height && extra_diagnostics == 0 {
1309 None
1310 } else if extra_diagnostics > 0 {
1311 Some(format!("{expand_label}{extra_diagnostics}"))
1312 } else {
1313 Some(expand_label.to_string())
1314 };
1315
1316 let expanded_block_height = diagnostics
1317 .iter()
1318 .map(|diagnostic| diagnostic_text_lines(diagnostic))
1319 .sum::<u8>();
1320 let editor_handle = editor_handle.clone();
1321 let parent = h_flex()
1322 .items_start()
1323 .child(v_flex().size_full().when_some_else(
1324 toggle_expand_label,
1325 |parent, label| {
1326 parent.child(Button::new(cx.block_id, label).on_click({
1327 let diagnostics = Arc::clone(&diagnostics);
1328 move |_, cx| {
1329 let new_expanded = !expanded;
1330 button_expanded.store(new_expanded, atomic::Ordering::Release);
1331 let new_size = if new_expanded {
1332 expanded_block_height
1333 } else {
1334 folded_block_height
1335 };
1336 editor_handle.update(cx, |editor, cx| {
1337 editor.replace_blocks(
1338 HashMap::from_iter(Some((
1339 block_id,
1340 (
1341 Some(new_size),
1342 render_same_line_diagnostics(
1343 Arc::clone(&button_expanded),
1344 Arc::clone(&diagnostics),
1345 editor_handle.clone(),
1346 folded_block_height,
1347 ),
1348 ),
1349 ))),
1350 None,
1351 cx,
1352 )
1353 });
1354 }
1355 }))
1356 },
1357 |parent| {
1358 parent.child(
1359 h_flex()
1360 .size(IconSize::default().rems())
1361 .invisible()
1362 .flex_none(),
1363 )
1364 },
1365 ));
1366 let max_message_rows = if expanded {
1367 None
1368 } else {
1369 Some(folded_block_height)
1370 };
1371 let mut renderer =
1372 diagnostic_block_renderer(first_diagnostic.clone(), max_message_rows, false, true);
1373 let mut diagnostics_element = v_flex();
1374 diagnostics_element = diagnostics_element.child(renderer(cx));
1375 if expanded {
1376 for diagnostic in diagnostics.iter().skip(1) {
1377 let mut renderer = diagnostic_block_renderer(diagnostic.clone(), None, false, true);
1378 diagnostics_element = diagnostics_element.child(renderer(cx));
1379 }
1380 }
1381 parent.child(diagnostics_element).into_any_element()
1382 })
1383}
1384
1385fn diagnostic_text_lines(diagnostic: &language::Diagnostic) -> u8 {
1386 diagnostic.message.matches('\n').count() as u8 + 1
1387}
1388
1389fn path_state_excerpts(
1390 after_excerpt_id: Option<ExcerptId>,
1391 before_excerpt_id: Option<ExcerptId>,
1392 multi_buffer_snapshot: &editor::MultiBufferSnapshot,
1393) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, ExcerptRange<language::Anchor>)> {
1394 multi_buffer_snapshot
1395 .excerpts()
1396 .skip_while(move |&(excerpt_id, ..)| match after_excerpt_id {
1397 Some(after_excerpt_id) => after_excerpt_id != excerpt_id,
1398 None => false,
1399 })
1400 .filter(move |&(excerpt_id, ..)| after_excerpt_id != Some(excerpt_id))
1401 .take_while(move |&(excerpt_id, ..)| match before_excerpt_id {
1402 Some(before_excerpt_id) => before_excerpt_id != excerpt_id,
1403 None => true,
1404 })
1405}