grouped_diagnostics.rs

   1use anyhow::Result;
   2use collections::{BTreeMap, BTreeSet, HashMap, HashSet};
   3use editor::{
   4    diagnostic_block_renderer,
   5    display_map::{
   6        BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock,
   7        TransformBlockId,
   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
  54struct GroupedDiagnosticsEditor {
  55    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    paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
  63    include_warnings: bool,
  64    context: u32,
  65    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, BlockId)>,
  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, 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, cx);
 260        }
 261    }
 262
 263    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    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 kb change selections as in the old panel, to the next primary diagnostics
 323        // TODO kb 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        // TODO kb when warnings are turned off, there's a lot of refresh for many paths happening, why?
 344        let max_severity = if self.include_warnings {
 345            DiagnosticSeverity::WARNING
 346        } else {
 347            DiagnosticSeverity::ERROR
 348        };
 349
 350        let excerpt_borders = self.excerpt_borders_for_path(path_ix);
 351        let path_state = &mut self.path_states[path_ix];
 352        let buffer_snapshot = buffer.read(cx).snapshot();
 353
 354        let mut path_update = PathUpdate::new(
 355            excerpt_borders,
 356            &buffer_snapshot,
 357            server_to_update,
 358            max_severity,
 359            path_state,
 360        );
 361        path_update.prepare_excerpt_data(
 362            self.context,
 363            self.excerpts.read(cx).snapshot(cx),
 364            buffer.read(cx).snapshot(),
 365            path_state.diagnostics.iter(),
 366        );
 367        self.excerpts.update(cx, |multi_buffer, cx| {
 368            path_update.apply_excerpt_changes(
 369                path_state,
 370                self.context,
 371                buffer_snapshot,
 372                multi_buffer,
 373                buffer,
 374                cx,
 375            );
 376        });
 377
 378        let new_multi_buffer_snapshot = self.excerpts.read(cx).snapshot(cx);
 379        let blocks_to_insert =
 380            path_update.prepare_blocks_to_insert(self.editor.clone(), new_multi_buffer_snapshot);
 381
 382        let new_block_ids = self.editor.update(cx, |editor, cx| {
 383            editor.remove_blocks(std::mem::take(&mut path_update.blocks_to_remove), None, cx);
 384            editor.insert_blocks(blocks_to_insert, Some(Autoscroll::fit()), cx)
 385        });
 386        path_state.diagnostics = path_update.new_blocks(new_block_ids);
 387
 388        if self.path_states.is_empty() {
 389            if self.editor.focus_handle(cx).is_focused(cx) {
 390                cx.focus(&self.focus_handle);
 391            }
 392        } else if self.focus_handle.is_focused(cx) {
 393            let focus_handle = self.editor.focus_handle(cx);
 394            cx.focus(&focus_handle);
 395        }
 396
 397        #[cfg(test)]
 398        self.check_invariants(cx);
 399
 400        cx.notify();
 401    }
 402
 403    fn excerpt_borders_for_path(&self, path_ix: usize) -> (Option<ExcerptId>, Option<ExcerptId>) {
 404        let previous_path_state_ix =
 405            Some(path_ix.saturating_sub(1)).filter(|&previous_path_ix| previous_path_ix != path_ix);
 406        let next_path_state_ix = path_ix + 1;
 407        let start = previous_path_state_ix.and_then(|i| {
 408            self.path_states[..=i]
 409                .iter()
 410                .rev()
 411                .find_map(|state| state.last_excerpt_id)
 412        });
 413        let end = self.path_states[next_path_state_ix..]
 414            .iter()
 415            .find_map(|state| state.first_excerpt_id);
 416        (start, end)
 417    }
 418
 419    #[cfg(test)]
 420    fn check_invariants(&self, cx: &mut ViewContext<Self>) {
 421        let mut excerpts = Vec::new();
 422        for (id, buffer, _) in self.excerpts.read(cx).snapshot(cx).excerpts() {
 423            if let Some(file) = buffer.file() {
 424                excerpts.push((id, file.path().clone()));
 425            }
 426        }
 427
 428        let mut prev_path = None;
 429        for (_, path) in &excerpts {
 430            if let Some(prev_path) = prev_path {
 431                if path < prev_path {
 432                    panic!("excerpts are not sorted by path {:?}", excerpts);
 433                }
 434            }
 435            prev_path = Some(path);
 436        }
 437    }
 438}
 439
 440impl FocusableView for GroupedDiagnosticsEditor {
 441    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
 442        self.focus_handle.clone()
 443    }
 444}
 445
 446impl Item for GroupedDiagnosticsEditor {
 447    type Event = EditorEvent;
 448
 449    fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
 450        Editor::to_item_events(event, f)
 451    }
 452
 453    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
 454        self.editor.update(cx, |editor, cx| editor.deactivated(cx));
 455    }
 456
 457    fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
 458        self.editor
 459            .update(cx, |editor, cx| editor.navigate(data, cx))
 460    }
 461
 462    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
 463        Some("Project Diagnostics".into())
 464    }
 465
 466    fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
 467        if self.summary.error_count == 0 && self.summary.warning_count == 0 {
 468            Label::new("No problems")
 469                .color(if params.selected {
 470                    Color::Default
 471                } else {
 472                    Color::Muted
 473                })
 474                .into_any_element()
 475        } else {
 476            h_flex()
 477                .gap_1()
 478                .when(self.summary.error_count > 0, |then| {
 479                    then.child(
 480                        h_flex()
 481                            .gap_1()
 482                            .child(Icon::new(IconName::XCircle).color(Color::Error))
 483                            .child(Label::new(self.summary.error_count.to_string()).color(
 484                                if params.selected {
 485                                    Color::Default
 486                                } else {
 487                                    Color::Muted
 488                                },
 489                            )),
 490                    )
 491                })
 492                .when(self.summary.warning_count > 0, |then| {
 493                    then.child(
 494                        h_flex()
 495                            .gap_1()
 496                            .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
 497                            .child(Label::new(self.summary.warning_count.to_string()).color(
 498                                if params.selected {
 499                                    Color::Default
 500                                } else {
 501                                    Color::Muted
 502                                },
 503                            )),
 504                    )
 505                })
 506                .into_any_element()
 507        }
 508    }
 509
 510    fn telemetry_event_text(&self) -> Option<&'static str> {
 511        Some("project diagnostics")
 512    }
 513
 514    fn for_each_project_item(
 515        &self,
 516        cx: &AppContext,
 517        f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
 518    ) {
 519        self.editor.for_each_project_item(cx, f)
 520    }
 521
 522    fn is_singleton(&self, _: &AppContext) -> bool {
 523        false
 524    }
 525
 526    fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
 527        self.editor.update(cx, |editor, _| {
 528            editor.set_nav_history(Some(nav_history));
 529        });
 530    }
 531
 532    fn clone_on_split(
 533        &self,
 534        _workspace_id: Option<workspace::WorkspaceId>,
 535        cx: &mut ViewContext<Self>,
 536    ) -> Option<View<Self>>
 537    where
 538        Self: Sized,
 539    {
 540        Some(cx.new_view(|cx| {
 541            GroupedDiagnosticsEditor::new(self.project.clone(), self.workspace.clone(), cx)
 542        }))
 543    }
 544
 545    fn is_dirty(&self, cx: &AppContext) -> bool {
 546        self.excerpts.read(cx).is_dirty(cx)
 547    }
 548
 549    fn has_conflict(&self, cx: &AppContext) -> bool {
 550        self.excerpts.read(cx).has_conflict(cx)
 551    }
 552
 553    fn can_save(&self, _: &AppContext) -> bool {
 554        true
 555    }
 556
 557    fn save(
 558        &mut self,
 559        format: bool,
 560        project: Model<Project>,
 561        cx: &mut ViewContext<Self>,
 562    ) -> Task<Result<()>> {
 563        self.editor.save(format, project, cx)
 564    }
 565
 566    fn save_as(
 567        &mut self,
 568        _: Model<Project>,
 569        _: ProjectPath,
 570        _: &mut ViewContext<Self>,
 571    ) -> Task<Result<()>> {
 572        unreachable!()
 573    }
 574
 575    fn reload(&mut self, project: Model<Project>, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
 576        self.editor.reload(project, cx)
 577    }
 578
 579    fn act_as_type<'a>(
 580        &'a self,
 581        type_id: TypeId,
 582        self_handle: &'a View<Self>,
 583        _: &'a AppContext,
 584    ) -> Option<AnyView> {
 585        if type_id == TypeId::of::<Self>() {
 586            Some(self_handle.to_any())
 587        } else if type_id == TypeId::of::<Editor>() {
 588            Some(self.editor.to_any())
 589        } else {
 590            None
 591        }
 592    }
 593
 594    fn breadcrumb_location(&self) -> ToolbarItemLocation {
 595        ToolbarItemLocation::PrimaryLeft
 596    }
 597
 598    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
 599        self.editor.breadcrumbs(theme, cx)
 600    }
 601
 602    fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
 603        self.editor
 604            .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
 605    }
 606}
 607
 608fn compare_data_locations(
 609    old: &DiagnosticData,
 610    new: &DiagnosticData,
 611    snapshot: &BufferSnapshot,
 612) -> Ordering {
 613    compare_diagnostics(&old.entry, &new.entry, snapshot)
 614        .then_with(|| old.language_server_id.cmp(&new.language_server_id))
 615}
 616
 617fn compare_diagnostics(
 618    old: &DiagnosticEntry<language::Anchor>,
 619    new: &DiagnosticEntry<language::Anchor>,
 620    snapshot: &BufferSnapshot,
 621) -> Ordering {
 622    compare_diagnostic_ranges(&old.range, &new.range, snapshot)
 623        .then_with(|| old.diagnostic.message.cmp(&new.diagnostic.message))
 624}
 625
 626fn compare_diagnostic_ranges(
 627    old: &Range<language::Anchor>,
 628    new: &Range<language::Anchor>,
 629    snapshot: &BufferSnapshot,
 630) -> Ordering {
 631    // The diagnostics may point to a previously open Buffer for this file.
 632    if !old.start.is_valid(snapshot) || !new.start.is_valid(snapshot) {
 633        return Ordering::Greater;
 634    }
 635
 636    old.start
 637        .to_offset(snapshot)
 638        .cmp(&new.start.to_offset(snapshot))
 639        .then_with(|| {
 640            old.end
 641                .to_offset(snapshot)
 642                .cmp(&new.end.to_offset(snapshot))
 643        })
 644}
 645
 646// TODO kb wrong? What to do here instead?
 647fn compare_diagnostic_range_edges(
 648    old: &Range<language::Anchor>,
 649    new: &Range<language::Anchor>,
 650    snapshot: &BufferSnapshot,
 651) -> (Ordering, Ordering) {
 652    // The diagnostics may point to a previously open Buffer for this file.
 653    let start_cmp = match (old.start.is_valid(snapshot), new.start.is_valid(snapshot)) {
 654        (false, false) => old.start.offset.cmp(&new.start.offset),
 655        (false, true) => Ordering::Greater,
 656        (true, false) => Ordering::Less,
 657        (true, true) => old.start.cmp(&new.start, snapshot),
 658    };
 659
 660    let end_cmp = old
 661        .end
 662        .to_offset(snapshot)
 663        .cmp(&new.end.to_offset(snapshot));
 664    (start_cmp, end_cmp)
 665}
 666
 667#[derive(Debug)]
 668struct PathUpdate {
 669    path_excerpts_borders: (Option<ExcerptId>, Option<ExcerptId>),
 670    latest_excerpt_id: ExcerptId,
 671    new_diagnostics: Vec<(DiagnosticData, Option<BlockId>)>,
 672    diagnostics_by_row_label: BTreeMap<MultiBufferRow, (editor::Anchor, Vec<usize>)>,
 673    blocks_to_remove: HashSet<BlockId>,
 674    unchanged_blocks: HashMap<usize, BlockId>,
 675    excerpts_with_new_diagnostics: HashSet<ExcerptId>,
 676    excerpts_to_remove: Vec<ExcerptId>,
 677    excerpt_expands: HashMap<(ExpandExcerptDirection, u32), Vec<ExcerptId>>,
 678    excerpts_to_add: HashMap<ExcerptId, Vec<Range<language::Anchor>>>,
 679    first_excerpt_id: Option<ExcerptId>,
 680    last_excerpt_id: Option<ExcerptId>,
 681}
 682
 683impl PathUpdate {
 684    fn new(
 685        path_excerpts_borders: (Option<ExcerptId>, Option<ExcerptId>),
 686        buffer_snapshot: &BufferSnapshot,
 687        server_to_update: Option<LanguageServerId>,
 688        max_severity: DiagnosticSeverity,
 689        path_state: &PathState,
 690    ) -> Self {
 691        let mut blocks_to_remove = HashSet::default();
 692        let mut removed_groups = HashSet::default();
 693        let mut new_diagnostics = path_state
 694            .diagnostics
 695            .iter()
 696            .filter(|(diagnostic_data, _)| {
 697                server_to_update.map_or(true, |server_id| {
 698                    diagnostic_data.language_server_id != server_id
 699                })
 700            })
 701            .filter(|(diagnostic_data, block_id)| {
 702                let diagnostic = &diagnostic_data.entry.diagnostic;
 703                let retain = !diagnostic.is_primary || diagnostic.severity <= max_severity;
 704                if !retain {
 705                    removed_groups.insert(diagnostic.group_id);
 706                    blocks_to_remove.insert(*block_id);
 707                }
 708                retain
 709            })
 710            .map(|(diagnostic, block_id)| (diagnostic.clone(), Some(*block_id)))
 711            .collect::<Vec<_>>();
 712        new_diagnostics.retain(|(diagnostic_data, block_id)| {
 713            let retain = !removed_groups.contains(&diagnostic_data.entry.diagnostic.group_id);
 714            if !retain {
 715                if let Some(block_id) = block_id {
 716                    blocks_to_remove.insert(*block_id);
 717                }
 718            }
 719            retain
 720        });
 721        for (server_id, group) in buffer_snapshot
 722            .diagnostic_groups(server_to_update)
 723            .into_iter()
 724            .filter(|(_, group)| {
 725                group.entries[group.primary_ix].diagnostic.severity <= max_severity
 726            })
 727        {
 728            for (diagnostic_index, diagnostic) in group.entries.iter().enumerate() {
 729                let new_data = DiagnosticData {
 730                    language_server_id: server_id,
 731                    is_primary: diagnostic_index == group.primary_ix,
 732                    entry: diagnostic.clone(),
 733                };
 734                let (Ok(i) | Err(i)) = new_diagnostics.binary_search_by(|probe| {
 735                    compare_data_locations(&probe.0, &new_data, &buffer_snapshot)
 736                });
 737                new_diagnostics.insert(i, (new_data, None));
 738            }
 739        }
 740
 741        let latest_excerpt_id = path_excerpts_borders.0.unwrap_or_else(|| ExcerptId::min());
 742        Self {
 743            latest_excerpt_id,
 744            path_excerpts_borders,
 745            new_diagnostics,
 746            blocks_to_remove,
 747            diagnostics_by_row_label: BTreeMap::new(),
 748            excerpts_to_remove: Vec::new(),
 749            excerpts_with_new_diagnostics: HashSet::default(),
 750            unchanged_blocks: HashMap::default(),
 751            excerpts_to_add: HashMap::default(),
 752            excerpt_expands: HashMap::default(),
 753            first_excerpt_id: None,
 754            last_excerpt_id: None,
 755        }
 756    }
 757
 758    fn prepare_excerpt_data<'a>(
 759        &'a mut self,
 760        context: u32,
 761        multi_buffer_snapshot: MultiBufferSnapshot,
 762        buffer_snapshot: BufferSnapshot,
 763        current_diagnostics: impl Iterator<Item = &'a (DiagnosticData, BlockId)> + 'a,
 764    ) {
 765        let mut current_diagnostics = current_diagnostics.fuse().peekable();
 766        let mut excerpts_to_expand =
 767            HashMap::<ExcerptId, HashMap<ExpandExcerptDirection, u32>>::default();
 768        let mut current_excerpts = path_state_excerpts(
 769            self.path_excerpts_borders.0,
 770            self.path_excerpts_borders.1,
 771            &multi_buffer_snapshot,
 772        )
 773        .fuse()
 774        .peekable();
 775
 776        for (diagnostic_index, (new_diagnostic, existing_block)) in
 777            self.new_diagnostics.iter().enumerate()
 778        {
 779            if let Some(existing_block) = existing_block {
 780                self.unchanged_blocks
 781                    .insert(diagnostic_index, *existing_block);
 782            }
 783
 784            loop {
 785                match current_excerpts.peek() {
 786                    None => {
 787                        let excerpt_ranges = self
 788                            .excerpts_to_add
 789                            .entry(self.latest_excerpt_id)
 790                            .or_default();
 791                        let new_range = new_diagnostic.entry.range.clone();
 792                        let (Ok(i) | Err(i)) = excerpt_ranges.binary_search_by(|probe| {
 793                            compare_diagnostic_ranges(probe, &new_range, &buffer_snapshot)
 794                        });
 795                        excerpt_ranges.insert(i, new_range);
 796                        break;
 797                    }
 798                    Some((current_excerpt_id, _, current_excerpt_range)) => {
 799                        match compare_diagnostic_range_edges(
 800                            &current_excerpt_range.context,
 801                            &new_diagnostic.entry.range,
 802                            &buffer_snapshot,
 803                        ) {
 804                            /*
 805                                  new_s new_e
 806                            ----[---->><<----]--
 807                             cur_s         cur_e
 808                            */
 809                            (
 810                                Ordering::Less | Ordering::Equal,
 811                                Ordering::Greater | Ordering::Equal,
 812                            ) => {
 813                                self.excerpts_with_new_diagnostics
 814                                    .insert(*current_excerpt_id);
 815                                if self.first_excerpt_id.is_none() {
 816                                    self.first_excerpt_id = Some(*current_excerpt_id);
 817                                }
 818                                self.last_excerpt_id = Some(*current_excerpt_id);
 819                                break;
 820                            }
 821                            /*
 822                                  cur_s cur_e
 823                            ---->>>>>[--]<<<<<--
 824                             new_s         new_e
 825                            */
 826                            (
 827                                Ordering::Greater | Ordering::Equal,
 828                                Ordering::Less | Ordering::Equal,
 829                            ) => {
 830                                let expand_up = current_excerpt_range
 831                                    .context
 832                                    .start
 833                                    .to_point(&buffer_snapshot)
 834                                    .row
 835                                    .saturating_sub(
 836                                        new_diagnostic
 837                                            .entry
 838                                            .range
 839                                            .start
 840                                            .to_point(&buffer_snapshot)
 841                                            .row,
 842                                    );
 843                                let expand_down = new_diagnostic
 844                                    .entry
 845                                    .range
 846                                    .end
 847                                    .to_point(&buffer_snapshot)
 848                                    .row
 849                                    .saturating_sub(
 850                                        current_excerpt_range
 851                                            .context
 852                                            .end
 853                                            .to_point(&buffer_snapshot)
 854                                            .row,
 855                                    );
 856                                let expand_value = excerpts_to_expand
 857                                    .entry(*current_excerpt_id)
 858                                    .or_default()
 859                                    .entry(ExpandExcerptDirection::UpAndDown)
 860                                    .or_default();
 861                                *expand_value = (*expand_value).max(expand_up).max(expand_down);
 862                                self.excerpts_with_new_diagnostics
 863                                    .insert(*current_excerpt_id);
 864                                if self.first_excerpt_id.is_none() {
 865                                    self.first_excerpt_id = Some(*current_excerpt_id);
 866                                }
 867                                self.last_excerpt_id = Some(*current_excerpt_id);
 868                                break;
 869                            }
 870                            /*
 871                                    new_s   new_e
 872                                     >       <
 873                            ----[---->>>]<<<<<--
 874                             cur_s    cur_e
 875
 876                            or
 877                                      new_s new_e
 878                                        >    <
 879                            ----[----]-->>><<<--
 880                             cur_s cur_e
 881                            */
 882                            (Ordering::Less, Ordering::Less) => {
 883                                if current_excerpt_range
 884                                    .context
 885                                    .end
 886                                    .cmp(&new_diagnostic.entry.range.start, &buffer_snapshot)
 887                                    .is_ge()
 888                                {
 889                                    let expand_down = new_diagnostic
 890                                        .entry
 891                                        .range
 892                                        .end
 893                                        .to_point(&buffer_snapshot)
 894                                        .row
 895                                        .saturating_sub(
 896                                            current_excerpt_range
 897                                                .context
 898                                                .end
 899                                                .to_point(&buffer_snapshot)
 900                                                .row,
 901                                        );
 902                                    let expand_value = excerpts_to_expand
 903                                        .entry(*current_excerpt_id)
 904                                        .or_default()
 905                                        .entry(ExpandExcerptDirection::Down)
 906                                        .or_default();
 907                                    *expand_value = (*expand_value).max(expand_down);
 908                                    self.excerpts_with_new_diagnostics
 909                                        .insert(*current_excerpt_id);
 910                                    if self.first_excerpt_id.is_none() {
 911                                        self.first_excerpt_id = Some(*current_excerpt_id);
 912                                    }
 913                                    self.last_excerpt_id = Some(*current_excerpt_id);
 914                                    break;
 915                                } else if !self
 916                                    .excerpts_with_new_diagnostics
 917                                    .contains(current_excerpt_id)
 918                                {
 919                                    self.excerpts_to_remove.push(*current_excerpt_id);
 920                                }
 921                            }
 922                            /*
 923                                  cur_s      cur_e
 924                            ---->>>>>[<<<<----]--
 925                                >        <
 926                               new_s    new_e
 927
 928                            or
 929                                      cur_s cur_e
 930                            ---->>><<<--[----]--
 931                                >    <
 932                               new_s new_e
 933                            */
 934                            (Ordering::Greater, Ordering::Greater) => {
 935                                if current_excerpt_range
 936                                    .context
 937                                    .start
 938                                    .cmp(&new_diagnostic.entry.range.end, &buffer_snapshot)
 939                                    .is_le()
 940                                {
 941                                    let expand_up = current_excerpt_range
 942                                        .context
 943                                        .start
 944                                        .to_point(&buffer_snapshot)
 945                                        .row
 946                                        .saturating_sub(
 947                                            new_diagnostic
 948                                                .entry
 949                                                .range
 950                                                .start
 951                                                .to_point(&buffer_snapshot)
 952                                                .row,
 953                                        );
 954                                    let expand_value = excerpts_to_expand
 955                                        .entry(*current_excerpt_id)
 956                                        .or_default()
 957                                        .entry(ExpandExcerptDirection::Up)
 958                                        .or_default();
 959                                    *expand_value = (*expand_value).max(expand_up);
 960                                    self.excerpts_with_new_diagnostics
 961                                        .insert(*current_excerpt_id);
 962                                    if self.first_excerpt_id.is_none() {
 963                                        self.first_excerpt_id = Some(*current_excerpt_id);
 964                                    }
 965                                    self.last_excerpt_id = Some(*current_excerpt_id);
 966                                    break;
 967                                } else {
 968                                    let excerpt_ranges = self
 969                                        .excerpts_to_add
 970                                        .entry(self.latest_excerpt_id)
 971                                        .or_default();
 972                                    let new_range = new_diagnostic.entry.range.clone();
 973                                    let (Ok(i) | Err(i)) =
 974                                        excerpt_ranges.binary_search_by(|probe| {
 975                                            compare_diagnostic_ranges(
 976                                                probe,
 977                                                &new_range,
 978                                                &buffer_snapshot,
 979                                            )
 980                                        });
 981                                    excerpt_ranges.insert(i, new_range);
 982                                    break;
 983                                }
 984                            }
 985                        }
 986                        if let Some((next_id, ..)) = current_excerpts.next() {
 987                            self.latest_excerpt_id = next_id;
 988                        }
 989                    }
 990                }
 991            }
 992
 993            loop {
 994                match current_diagnostics.peek() {
 995                    None => break,
 996                    Some((current_diagnostic, current_block)) => {
 997                        match compare_data_locations(
 998                            current_diagnostic,
 999                            new_diagnostic,
1000                            &buffer_snapshot,
1001                        ) {
1002                            Ordering::Less => {
1003                                self.blocks_to_remove.insert(*current_block);
1004                            }
1005                            Ordering::Equal => {
1006                                if current_diagnostic.diagnostic_entries_equal(&new_diagnostic) {
1007                                    self.unchanged_blocks
1008                                        .insert(diagnostic_index, *current_block);
1009                                } else {
1010                                    self.blocks_to_remove.insert(*current_block);
1011                                }
1012                                let _ = current_diagnostics.next();
1013                                break;
1014                            }
1015                            Ordering::Greater => break,
1016                        }
1017                        let _ = current_diagnostics.next();
1018                    }
1019                }
1020            }
1021        }
1022
1023        self.excerpts_to_remove.retain(|excerpt_id| {
1024            !self.excerpts_with_new_diagnostics.contains(excerpt_id)
1025                && !excerpts_to_expand.contains_key(excerpt_id)
1026        });
1027        self.excerpts_to_remove.extend(
1028            current_excerpts
1029                .filter(|(excerpt_id, ..)| {
1030                    !self.excerpts_with_new_diagnostics.contains(excerpt_id)
1031                        && !excerpts_to_expand.contains_key(excerpt_id)
1032                })
1033                .map(|(excerpt_id, ..)| excerpt_id),
1034        );
1035        let mut excerpt_expands = HashMap::default();
1036        for (excerpt_id, directions) in excerpts_to_expand {
1037            let excerpt_expand = if directions.len() > 1 {
1038                Some((
1039                    ExpandExcerptDirection::UpAndDown,
1040                    directions
1041                        .values()
1042                        .max()
1043                        .copied()
1044                        .unwrap_or_default()
1045                        .max(context),
1046                ))
1047            } else {
1048                directions
1049                    .into_iter()
1050                    .next()
1051                    .map(|(direction, expand)| (direction, expand.max(context)))
1052            };
1053            if let Some(expand) = excerpt_expand {
1054                excerpt_expands
1055                    .entry(expand)
1056                    .or_insert_with(|| Vec::new())
1057                    .push(excerpt_id);
1058            }
1059        }
1060        self.blocks_to_remove
1061            .extend(current_diagnostics.map(|(_, block_id)| block_id));
1062    }
1063
1064    fn apply_excerpt_changes(
1065        &mut self,
1066        path_state: &mut PathState,
1067        context: u32,
1068        buffer_snapshot: BufferSnapshot,
1069        multi_buffer: &mut MultiBuffer,
1070        buffer: Model<Buffer>,
1071        cx: &mut gpui::ModelContext<MultiBuffer>,
1072    ) {
1073        let max_point = buffer_snapshot.max_point();
1074        for (after_excerpt_id, ranges) in std::mem::take(&mut self.excerpts_to_add) {
1075            let ranges = ranges
1076                .into_iter()
1077                .map(|range| {
1078                    let mut extended_point_range = range.to_point(&buffer_snapshot);
1079                    extended_point_range.start.row =
1080                        extended_point_range.start.row.saturating_sub(context);
1081                    extended_point_range.start.column = 0;
1082                    extended_point_range.end.row =
1083                        (extended_point_range.end.row + context).min(max_point.row);
1084                    extended_point_range.end.column = u32::MAX;
1085                    let extended_start =
1086                        buffer_snapshot.clip_point(extended_point_range.start, Bias::Left);
1087                    let extended_end =
1088                        buffer_snapshot.clip_point(extended_point_range.end, Bias::Right);
1089                    extended_start..extended_end
1090                })
1091                .collect::<Vec<_>>();
1092            let (joined_ranges, _) = build_excerpt_ranges(&buffer_snapshot, &ranges, context);
1093            let excerpts = multi_buffer.insert_excerpts_after(
1094                after_excerpt_id,
1095                buffer.clone(),
1096                joined_ranges,
1097                cx,
1098            );
1099            if self.first_excerpt_id.is_none() {
1100                self.first_excerpt_id = excerpts.first().copied();
1101            }
1102            self.last_excerpt_id = excerpts.last().copied();
1103        }
1104        for ((direction, line_count), excerpts) in std::mem::take(&mut self.excerpt_expands) {
1105            multi_buffer.expand_excerpts(excerpts, line_count, direction, cx);
1106        }
1107        multi_buffer.remove_excerpts(std::mem::take(&mut self.excerpts_to_remove), cx);
1108        path_state.first_excerpt_id = self.first_excerpt_id;
1109        path_state.last_excerpt_id = self.last_excerpt_id;
1110    }
1111
1112    fn prepare_blocks_to_insert(
1113        &mut self,
1114        editor: View<Editor>,
1115        multi_buffer_snapshot: MultiBufferSnapshot,
1116    ) -> Vec<BlockProperties<editor::Anchor>> {
1117        let mut updated_excerpts = path_state_excerpts(
1118            self.path_excerpts_borders.0,
1119            self.path_excerpts_borders.1,
1120            &multi_buffer_snapshot,
1121        )
1122        .fuse()
1123        .peekable();
1124        let mut used_labels = BTreeMap::new();
1125        self.diagnostics_by_row_label = self.new_diagnostics.iter().enumerate().fold(
1126            BTreeMap::new(),
1127            |mut diagnostics_by_row_label, (diagnostic_index, (diagnostic, existing_block))| {
1128                let new_diagnostic = &diagnostic.entry;
1129                let block_position = new_diagnostic.range.start;
1130                let excerpt_id = loop {
1131                    match updated_excerpts.peek() {
1132                        None => break None,
1133                        Some((excerpt_id, excerpt_buffer_snapshot, excerpt_range)) => {
1134                            let excerpt_range = &excerpt_range.context;
1135                            match block_position.cmp(&excerpt_range.start, excerpt_buffer_snapshot)
1136                            {
1137                                Ordering::Less => break None,
1138                                Ordering::Equal | Ordering::Greater => match block_position
1139                                    .cmp(&excerpt_range.end, excerpt_buffer_snapshot)
1140                                {
1141                                    Ordering::Equal | Ordering::Less => break Some(*excerpt_id),
1142                                    Ordering::Greater => {
1143                                        let _ = updated_excerpts.next();
1144                                    }
1145                                },
1146                            }
1147                        }
1148                    }
1149                };
1150
1151                let Some(position_in_multi_buffer) = excerpt_id.and_then(|excerpt_id| {
1152                    multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, block_position)
1153                }) else {
1154                    return diagnostics_by_row_label;
1155                };
1156
1157                let multi_buffer_row = MultiBufferRow(
1158                    position_in_multi_buffer
1159                        .to_point(&multi_buffer_snapshot)
1160                        .row,
1161                );
1162
1163                let grouped_diagnostics = &mut diagnostics_by_row_label
1164                    .entry(multi_buffer_row)
1165                    .or_insert_with(|| (position_in_multi_buffer, Vec::new()))
1166                    .1;
1167                let new_label = used_labels
1168                    .entry(multi_buffer_row)
1169                    .or_insert_with(|| HashSet::default())
1170                    .insert((
1171                        new_diagnostic.diagnostic.source.as_deref(),
1172                        new_diagnostic.diagnostic.message.as_str(),
1173                    ));
1174
1175                if !new_label || !grouped_diagnostics.is_empty() {
1176                    if let Some(existing_block) = existing_block {
1177                        self.blocks_to_remove.insert(*existing_block);
1178                    }
1179                    if let Some(block_id) = self.unchanged_blocks.remove(&diagnostic_index) {
1180                        self.blocks_to_remove.insert(block_id);
1181                    }
1182                }
1183                if new_label {
1184                    let (Ok(i) | Err(i)) = grouped_diagnostics.binary_search_by(|&probe| {
1185                        let a = &self.new_diagnostics[probe].0.entry.diagnostic;
1186                        let b = &self.new_diagnostics[diagnostic_index].0.entry.diagnostic;
1187                        a.group_id
1188                            .cmp(&b.group_id)
1189                            .then_with(|| a.is_primary.cmp(&b.is_primary).reverse())
1190                            .then_with(|| a.severity.cmp(&b.severity))
1191                    });
1192                    grouped_diagnostics.insert(i, diagnostic_index);
1193                }
1194
1195                diagnostics_by_row_label
1196            },
1197        );
1198
1199        self.diagnostics_by_row_label
1200            .values()
1201            .filter_map(|(earliest_in_row_position, diagnostics_at_line)| {
1202                let earliest_in_row_position = *earliest_in_row_position;
1203                match diagnostics_at_line.len() {
1204                    0 => None,
1205                    len => {
1206                        if len == 1 {
1207                            let i = diagnostics_at_line.first().copied()?;
1208                            if self.unchanged_blocks.contains_key(&i) {
1209                                return None;
1210                            }
1211                        }
1212                        let lines_in_first_message = diagnostic_text_lines(
1213                            &self
1214                                .new_diagnostics
1215                                .get(diagnostics_at_line.first().copied()?)?
1216                                .0
1217                                .entry
1218                                .diagnostic,
1219                        );
1220                        let folded_block_height = lines_in_first_message.clamp(1, 2);
1221                        let diagnostics_to_render = Arc::new(
1222                            diagnostics_at_line
1223                                .iter()
1224                                .filter_map(|&index| self.new_diagnostics.get(index))
1225                                .map(|(diagnostic_data, _)| {
1226                                    diagnostic_data.entry.diagnostic.clone()
1227                                })
1228                                .collect::<Vec<_>>(),
1229                        );
1230                        Some(BlockProperties {
1231                            position: earliest_in_row_position,
1232                            height: folded_block_height,
1233                            style: BlockStyle::Sticky,
1234                            render: render_same_line_diagnostics(
1235                                Arc::new(AtomicBool::new(false)),
1236                                diagnostics_to_render,
1237                                editor.clone(),
1238                                folded_block_height,
1239                            ),
1240                            disposition: BlockDisposition::Above,
1241                        })
1242                    }
1243                }
1244            })
1245            .collect()
1246    }
1247
1248    fn new_blocks(mut self, new_block_ids: Vec<BlockId>) -> Vec<(DiagnosticData, BlockId)> {
1249        let mut new_block_ids = new_block_ids.into_iter().fuse();
1250        for (_, (_, grouped_diagnostics)) in self.diagnostics_by_row_label {
1251            let mut created_block_id = None;
1252            match grouped_diagnostics.len() {
1253                0 => {
1254                    debug_panic!("Unexpected empty diagnostics group");
1255                    continue;
1256                }
1257                1 => {
1258                    let index = grouped_diagnostics[0];
1259                    if let Some(&block_id) = self.unchanged_blocks.get(&index) {
1260                        self.new_diagnostics[index].1 = Some(block_id);
1261                    } else {
1262                        let Some(block_id) =
1263                            created_block_id.get_or_insert_with(|| new_block_ids.next())
1264                        else {
1265                            debug_panic!("Expected a new block for each new diagnostic");
1266                            continue;
1267                        };
1268                        self.new_diagnostics[index].1 = Some(*block_id);
1269                    }
1270                }
1271                _ => {
1272                    let Some(block_id) =
1273                        created_block_id.get_or_insert_with(|| new_block_ids.next())
1274                    else {
1275                        debug_panic!("Expected a new block for each new diagnostic group");
1276                        continue;
1277                    };
1278                    for i in grouped_diagnostics {
1279                        self.new_diagnostics[i].1 = Some(*block_id);
1280                    }
1281                }
1282            }
1283        }
1284
1285        self.new_diagnostics
1286            .into_iter()
1287            .filter_map(|(diagnostic, block_id)| Some((diagnostic, block_id?)))
1288            .collect()
1289    }
1290}
1291
1292fn render_same_line_diagnostics(
1293    expanded: Arc<AtomicBool>,
1294    diagnostics: Arc<Vec<language::Diagnostic>>,
1295    editor_handle: View<Editor>,
1296    folded_block_height: u8,
1297) -> RenderBlock {
1298    Box::new(move |cx: &mut BlockContext| {
1299        let block_id = match cx.transform_block_id {
1300            TransformBlockId::Block(block_id) => block_id,
1301            _ => {
1302                debug_panic!("Expected a block id for the diagnostics block");
1303                return div().into_any_element();
1304            }
1305        };
1306        let Some(first_diagnostic) = diagnostics.first() else {
1307            debug_panic!("Expected at least one diagnostic");
1308            return div().into_any_element();
1309        };
1310        let button_expanded = expanded.clone();
1311        let expanded = expanded.load(atomic::Ordering::Acquire);
1312        let expand_label = if expanded { '-' } else { '+' };
1313        let first_diagnostics_height = diagnostic_text_lines(first_diagnostic);
1314        let extra_diagnostics = diagnostics.len() - 1;
1315        let toggle_expand_label =
1316            if folded_block_height == first_diagnostics_height && extra_diagnostics == 0 {
1317                None
1318            } else if extra_diagnostics > 0 {
1319                Some(format!("{expand_label}{extra_diagnostics}"))
1320            } else {
1321                Some(expand_label.to_string())
1322            };
1323
1324        let expanded_block_height = diagnostics
1325            .iter()
1326            .map(|diagnostic| diagnostic_text_lines(diagnostic))
1327            .sum::<u8>();
1328        let editor_handle = editor_handle.clone();
1329        let mut parent = v_flex();
1330        let mut diagnostics_iter = diagnostics.iter().fuse();
1331        if let Some(first_diagnostic) = diagnostics_iter.next() {
1332            let mut renderer = diagnostic_block_renderer(
1333                first_diagnostic.clone(),
1334                Some(folded_block_height),
1335                false,
1336                true,
1337            );
1338            parent = parent.child(
1339                h_flex()
1340                    .when_some(toggle_expand_label, |parent, label| {
1341                        parent.child(Button::new(cx.transform_block_id, label).on_click({
1342                            let diagnostics = Arc::clone(&diagnostics);
1343                            move |_, cx| {
1344                                let new_expanded = !expanded;
1345                                button_expanded.store(new_expanded, atomic::Ordering::Release);
1346                                let new_size = if new_expanded {
1347                                    expanded_block_height
1348                                } else {
1349                                    folded_block_height
1350                                };
1351                                editor_handle.update(cx, |editor, cx| {
1352                                    editor.replace_blocks(
1353                                        HashMap::from_iter(Some((
1354                                            block_id,
1355                                            (
1356                                                Some(new_size),
1357                                                render_same_line_diagnostics(
1358                                                    Arc::clone(&button_expanded),
1359                                                    Arc::clone(&diagnostics),
1360                                                    editor_handle.clone(),
1361                                                    folded_block_height,
1362                                                ),
1363                                            ),
1364                                        ))),
1365                                        None,
1366                                        cx,
1367                                    )
1368                                });
1369                            }
1370                        }))
1371                    })
1372                    .child(renderer(cx)),
1373            );
1374        }
1375        if expanded {
1376            for diagnostic in diagnostics_iter {
1377                let mut renderer = diagnostic_block_renderer(diagnostic.clone(), None, false, true);
1378                parent = parent.child(renderer(cx));
1379            }
1380        }
1381        parent.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}