pub mod items;mod toolbar_controls; #[cfg(test)]mod diagnostics_tests; use anyhow::Result;use collections::{BTreeSet, HashSet};use editor::{ diagnostic_block_renderer, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock}, highlight_diagnostic_message, scroll::Autoscroll, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,};use gpui::{ actions, div, svg, AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, HighlightStyle, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, StyledText, Subscription, Task, WeakEntity, Window,};use language::{ Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, ToTreeSitterPoint,};use lsp::LanguageServerId;use project::{project_settings::ProjectSettings, DiagnosticSummary, Project, ProjectPath};use settings::Settings;use std::{ any::{Any, TypeId}, cmp, cmp::Ordering, mem, ops::{Range, RangeInclusive}, sync::Arc, time::Duration,};use theme::ActiveTheme;pub use toolbar_controls::ToolbarControls;use ui::{h_flex, prelude::*, Icon, IconName, Label};use util::ResultExt;use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams}, searchable::SearchableItemHandle, ItemNavHistory, ToolbarItemLocation, Workspace,}; actions!(diagnostics, [Deploy, ToggleWarnings]); struct IncludeWarnings(bool);impl Global for IncludeWarnings {} pub fn init(cx: &mut App) { cx.observe_new(ProjectDiagnosticsEditor::register).detach();} struct ProjectDiagnosticsEditor { project: Entity<Project>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, editor: Entity<Editor>, summary: DiagnosticSummary, excerpts: Entity<MultiBuffer>, path_states: Vec<PathState>, paths_to_update: BTreeSet<(ProjectPath, Option<LanguageServerId>)>, include_warnings: bool, context: u32, update_excerpts_task: Option<Task<Result<()>>>, _subscription: Subscription,} struct PathState { path: ProjectPath, diagnostic_groups: Vec<DiagnosticGroupState>,} struct DiagnosticGroupState { language_server_id: LanguageServerId, primary_diagnostic: DiagnosticEntry<language::Anchor>, primary_excerpt_ix: usize, excerpts: Vec<ExcerptId>, blocks: HashSet<CustomBlockId>, block_count: usize,} impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {} const DIAGNOSTICS_UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); impl Render for ProjectDiagnosticsEditor { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let warning_count = if self.include_warnings { self.summary.warning_count } else { 0 }; let child = if warning_count + self.summary.error_count == 0 { let label = if self.summary.warning_count == 0 { SharedString::new_static("No problems in workspace") } else { SharedString::new_static("No errors in workspace") }; v_flex() .key_context("EmptyPane") .size_full() .gap_1() .justify_center() .items_center() .text_center() .bg(cx.theme().colors().editor_background) .child(Label::new(label).color(Color::Muted)) .when(self.summary.warning_count > 0, |this| { let plural_suffix = if self.summary.warning_count > 1 { "s" } else { "" }; let label = format!( "Show {} warning{}", self.summary.warning_count, plural_suffix ); this.child( Button::new("diagnostics-show-warning-label", label).on_click(cx.listener( |this, _, window, cx| { this.toggle_warnings(&Default::default(), window, cx); cx.notify(); }, )), ) }) } else { div().size_full().child(self.editor.clone()) }; div() .key_context("Diagnostics") .track_focus(&self.focus_handle(cx)) .size_full() .on_action(cx.listener(Self::toggle_warnings)) .child(child) }} impl ProjectDiagnosticsEditor { fn register( workspace: &mut Workspace, _window: Option<&mut Window>, _: &mut Context<Workspace>, ) { workspace.register_action(Self::deploy); } fn new_with_context( context: u32, include_warnings: bool, project_handle: Entity<Project>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let project_event_subscription = cx.subscribe_in(&project_handle, window, |this, project, event, window, cx| match event { project::Event::DiskBasedDiagnosticsStarted { .. } => { cx.notify(); } project::Event::DiskBasedDiagnosticsFinished { language_server_id } => { log::debug!("disk based diagnostics finished for server {language_server_id}"); this.update_stale_excerpts(window, cx); } project::Event::DiagnosticsUpdated { language_server_id, path, } => { this.paths_to_update .insert((path.clone(), Some(*language_server_id))); this.summary = project.read(cx).diagnostic_summary(false, cx); cx.emit(EditorEvent::TitleChanged); if this.editor.focus_handle(cx).contains_focused(window, cx) || this.focus_handle.contains_focused(window, cx) { log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change"); } else { log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts"); this.update_stale_excerpts(window, cx); } } _ => {} }); let focus_handle = cx.focus_handle(); cx.on_focus_in(&focus_handle, window, |this, window, cx| { this.focus_in(window, cx) }) .detach(); cx.on_focus_out(&focus_handle, window, |this, _event, window, cx| { this.focus_out(window, cx) }) .detach(); let excerpts = cx.new(|cx| MultiBuffer::new(project_handle.read(cx).capability())); let editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer( excerpts.clone(), Some(project_handle.clone()), true, window, cx, ); editor.set_vertical_scroll_margin(5, cx); editor.disable_inline_diagnostics(); editor }); cx.subscribe_in( &editor, window, |this, _editor, event: &EditorEvent, window, cx| { cx.emit(event.clone()); match event { EditorEvent::Focused => { if this.path_states.is_empty() { window.focus(&this.focus_handle); } } EditorEvent::Blurred => this.update_stale_excerpts(window, cx), _ => {} } }, ) .detach(); cx.observe_global_in::<IncludeWarnings>(window, |this, window, cx| { this.include_warnings = cx.global::<IncludeWarnings>().0; this.update_all_excerpts(window, cx); }) .detach(); let project = project_handle.read(cx); let mut this = Self { project: project_handle.clone(), context, summary: project.diagnostic_summary(false, cx), include_warnings, workspace, excerpts, focus_handle, editor, path_states: Default::default(), paths_to_update: Default::default(), update_excerpts_task: None, _subscription: project_event_subscription, }; this.update_all_excerpts(window, cx); this } fn update_stale_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { if self.update_excerpts_task.is_some() { return; } let project_handle = self.project.clone(); self.update_excerpts_task = Some(cx.spawn_in(window, |this, mut cx| async move { cx.background_executor() .timer(DIAGNOSTICS_UPDATE_DEBOUNCE) .await; loop { let Some((path, language_server_id)) = this.update(&mut cx, |this, _| { let Some((path, language_server_id)) = this.paths_to_update.pop_first() else { this.update_excerpts_task.take(); return None; }; Some((path, language_server_id)) })? else { break; }; if let Some(buffer) = project_handle .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))? .await .log_err() { this.update_in(&mut cx, |this, window, cx| { this.update_excerpts(path, language_server_id, buffer, window, cx) })? .await?; } } Ok(()) })); } fn new( project_handle: Entity<Project>, include_warnings: bool, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { Self::new_with_context( editor::DEFAULT_MULTIBUFFER_CONTEXT, include_warnings, project_handle, workspace, window, cx, ) } fn deploy( workspace: &mut Workspace, _: &Deploy, window: &mut Window, cx: &mut Context<Workspace>, ) { if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) { workspace.activate_item(&existing, true, true, window, cx); } else { let workspace_handle = cx.entity().downgrade(); let include_warnings = match cx.try_global::<IncludeWarnings>() { Some(include_warnings) => include_warnings.0, None => ProjectSettings::get_global(cx).diagnostics.include_warnings, }; let diagnostics = cx.new(|cx| { ProjectDiagnosticsEditor::new( workspace.project().clone(), include_warnings, workspace_handle, window, cx, ) }); workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, window, cx); } } fn toggle_warnings(&mut self, _: &ToggleWarnings, window: &mut Window, cx: &mut Context<Self>) { self.include_warnings = !self.include_warnings; cx.set_global(IncludeWarnings(self.include_warnings)); self.update_all_excerpts(window, cx); cx.notify(); } fn focus_in(&mut self, window: &mut Window, cx: &mut Context<Self>) { if self.focus_handle.is_focused(window) && !self.path_states.is_empty() { self.editor.focus_handle(cx).focus(window) } } fn focus_out(&mut self, window: &mut Window, cx: &mut Context<Self>) { if !self.focus_handle.is_focused(window) && !self.editor.focus_handle(cx).is_focused(window) { self.update_stale_excerpts(window, cx); } } /// Enqueue an update of all excerpts. Updates all paths that either /// currently have diagnostics or are currently present in this view. fn update_all_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.project.update(cx, |project, cx| { let mut paths = project .diagnostic_summaries(false, cx) .map(|(path, _, _)| (path, None)) .collect::<BTreeSet<_>>(); paths.extend( self.path_states .iter() .map(|state| (state.path.clone(), None)), ); let paths_to_update = std::mem::take(&mut self.paths_to_update); paths.extend(paths_to_update.into_iter().map(|(path, _)| (path, None))); self.paths_to_update = paths; }); self.update_stale_excerpts(window, cx); } fn update_excerpts( &mut self, path_to_update: ProjectPath, server_to_update: Option<LanguageServerId>, buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<Result<()>> { let was_empty = self.path_states.is_empty(); let snapshot = buffer.read(cx).snapshot(); let path_ix = match self .path_states .binary_search_by_key(&&path_to_update, |e| &e.path) { Ok(ix) => ix, Err(ix) => { self.path_states.insert( ix, PathState { path: path_to_update.clone(), diagnostic_groups: Default::default(), }, ); ix } }; let mut prev_excerpt_id = if path_ix > 0 { let prev_path_last_group = &self.path_states[path_ix - 1] .diagnostic_groups .last() .unwrap(); *prev_path_last_group.excerpts.last().unwrap() } else { ExcerptId::min() }; let mut new_group_ixs = Vec::new(); let mut blocks_to_add = Vec::new(); let mut blocks_to_remove = HashSet::default(); let mut first_excerpt_id = None; let max_severity = if self.include_warnings { DiagnosticSeverity::WARNING } else { DiagnosticSeverity::ERROR }; let excerpts = self.excerpts.clone().downgrade(); let context = self.context; let editor = self.editor.clone().downgrade(); cx.spawn_in(window, move |this, mut cx| async move { let mut old_groups = this .update(&mut cx, |this, _| { mem::take(&mut this.path_states[path_ix].diagnostic_groups) })? .into_iter() .enumerate() .peekable(); let mut new_groups = snapshot .diagnostic_groups(server_to_update) .into_iter() .filter(|(_, group)| { group.entries[group.primary_ix].diagnostic.severity <= max_severity }) .peekable(); loop { let mut to_insert = None; let mut to_remove = None; let mut to_keep = None; match (old_groups.peek(), new_groups.peek()) { (None, None) => break, (None, Some(_)) => to_insert = new_groups.next(), (Some((_, old_group)), None) => { if server_to_update.map_or(true, |id| id == old_group.language_server_id) { to_remove = old_groups.next(); } else { to_keep = old_groups.next(); } } (Some((_, old_group)), Some((new_language_server_id, new_group))) => { let old_primary = &old_group.primary_diagnostic; let new_primary = &new_group.entries[new_group.primary_ix]; match compare_diagnostics(old_primary, new_primary, &snapshot) .then_with(|| old_group.language_server_id.cmp(new_language_server_id)) { Ordering::Less => { if server_to_update .map_or(true, |id| id == old_group.language_server_id) { to_remove = old_groups.next(); } else { to_keep = old_groups.next(); } } Ordering::Equal => { to_keep = old_groups.next(); new_groups.next(); } Ordering::Greater => to_insert = new_groups.next(), } } } if let Some((language_server_id, group)) = to_insert { let mut group_state = DiagnosticGroupState { language_server_id, primary_diagnostic: group.entries[group.primary_ix].clone(), primary_excerpt_ix: 0, excerpts: Default::default(), blocks: Default::default(), block_count: 0, }; let mut pending_range: Option<(Range<Point>, Range<Point>, usize)> = None; let mut is_first_excerpt_for_group = true; for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() { let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot)); let expanded_range = if let Some(entry) = &resolved_entry { Some( context_range_for_entry( entry.range.clone(), context, snapshot.clone(), (*cx).clone(), ) .await, ) } else {Lines 1–500 of 1232. Load more
pub mod items; mod toolbar_controls; #[cfg(test)] mod diagnostics_tests; use anyhow::Result; use collections::{BTreeSet, HashSet}; use editor::{ diagnostic_block_renderer, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock}, highlight_diagnostic_message, scroll::Autoscroll, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset, }; use gpui::{ actions, div, svg, AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, HighlightStyle, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, StyledText, Subscription, Task, WeakEntity, Window, }; use language::{ Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal, ToTreeSitterPoint, }; use lsp::LanguageServerId; use project::{project_settings::ProjectSettings, DiagnosticSummary, Project, ProjectPath}; use settings::Settings; use std::{ any::{Any, TypeId}, cmp, cmp::Ordering, mem, ops::{Range, RangeInclusive}, sync::Arc, time::Duration, }; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; use ui::{h_flex, prelude::*, Icon, IconName, Label}; use util::ResultExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams}, searchable::SearchableItemHandle, ItemNavHistory, ToolbarItemLocation, Workspace, }; actions!(diagnostics, [Deploy, ToggleWarnings]); struct IncludeWarnings(bool); impl Global for IncludeWarnings {} pub fn init(cx: &mut App) { cx.observe_new(ProjectDiagnosticsEditor::register).detach(); } struct ProjectDiagnosticsEditor { project: Entity<Project>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, editor: Entity<Editor>, summary: DiagnosticSummary, excerpts: Entity<MultiBuffer>, path_states: Vec<PathState>, paths_to_update: BTreeSet<(ProjectPath, Option<LanguageServerId>)>, include_warnings: bool, context: u32, update_excerpts_task: Option<Task<Result<()>>>, _subscription: Subscription, } struct PathState { path: ProjectPath, diagnostic_groups: Vec<DiagnosticGroupState>, } struct DiagnosticGroupState { language_server_id: LanguageServerId, primary_diagnostic: DiagnosticEntry<language::Anchor>, primary_excerpt_ix: usize, excerpts: Vec<ExcerptId>, blocks: HashSet<CustomBlockId>, block_count: usize, } impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {} const DIAGNOSTICS_UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); impl Render for ProjectDiagnosticsEditor { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let warning_count = if self.include_warnings { self.summary.warning_count } else { 0 }; let child = if warning_count + self.summary.error_count == 0 { let label = if self.summary.warning_count == 0 { SharedString::new_static("No problems in workspace") } else { SharedString::new_static("No errors in workspace") }; v_flex() .key_context("EmptyPane") .size_full() .gap_1() .justify_center() .items_center() .text_center() .bg(cx.theme().colors().editor_background) .child(Label::new(label).color(Color::Muted)) .when(self.summary.warning_count > 0, |this| { let plural_suffix = if self.summary.warning_count > 1 { "s" } else { "" }; let label = format!( "Show {} warning{}", self.summary.warning_count, plural_suffix ); this.child( Button::new("diagnostics-show-warning-label", label).on_click(cx.listener( |this, _, window, cx| { this.toggle_warnings(&Default::default(), window, cx); cx.notify(); }, )), ) }) } else { div().size_full().child(self.editor.clone()) }; div() .key_context("Diagnostics") .track_focus(&self.focus_handle(cx)) .size_full() .on_action(cx.listener(Self::toggle_warnings)) .child(child) } } impl ProjectDiagnosticsEditor { fn register( workspace: &mut Workspace, _window: Option<&mut Window>, _: &mut Context<Workspace>, ) { workspace.register_action(Self::deploy); } fn new_with_context( context: u32, include_warnings: bool, project_handle: Entity<Project>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let project_event_subscription = cx.subscribe_in(&project_handle, window, |this, project, event, window, cx| match event { project::Event::DiskBasedDiagnosticsStarted { .. } => { cx.notify(); } project::Event::DiskBasedDiagnosticsFinished { language_server_id } => { log::debug!("disk based diagnostics finished for server {language_server_id}"); this.update_stale_excerpts(window, cx); } project::Event::DiagnosticsUpdated { language_server_id, path, } => { this.paths_to_update .insert((path.clone(), Some(*language_server_id))); this.summary = project.read(cx).diagnostic_summary(false, cx); cx.emit(EditorEvent::TitleChanged); if this.editor.focus_handle(cx).contains_focused(window, cx) || this.focus_handle.contains_focused(window, cx) { log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change"); } else { log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts"); this.update_stale_excerpts(window, cx); } } _ => {} }); let focus_handle = cx.focus_handle(); cx.on_focus_in(&focus_handle, window, |this, window, cx| { this.focus_in(window, cx) }) .detach(); cx.on_focus_out(&focus_handle, window, |this, _event, window, cx| { this.focus_out(window, cx) }) .detach(); let excerpts = cx.new(|cx| MultiBuffer::new(project_handle.read(cx).capability())); let editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer( excerpts.clone(), Some(project_handle.clone()), true, window, cx, ); editor.set_vertical_scroll_margin(5, cx); editor.disable_inline_diagnostics(); editor }); cx.subscribe_in( &editor, window, |this, _editor, event: &EditorEvent, window, cx| { cx.emit(event.clone()); match event { EditorEvent::Focused => { if this.path_states.is_empty() { window.focus(&this.focus_handle); } } EditorEvent::Blurred => this.update_stale_excerpts(window, cx), _ => {} } }, ) .detach(); cx.observe_global_in::<IncludeWarnings>(window, |this, window, cx| { this.include_warnings = cx.global::<IncludeWarnings>().0; this.update_all_excerpts(window, cx); }) .detach(); let project = project_handle.read(cx); let mut this = Self { project: project_handle.clone(), context, summary: project.diagnostic_summary(false, cx), include_warnings, workspace, excerpts, focus_handle, editor, path_states: Default::default(), paths_to_update: Default::default(), update_excerpts_task: None, _subscription: project_event_subscription, }; this.update_all_excerpts(window, cx); this } fn update_stale_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { if self.update_excerpts_task.is_some() { return; } let project_handle = self.project.clone(); self.update_excerpts_task = Some(cx.spawn_in(window, |this, mut cx| async move { cx.background_executor() .timer(DIAGNOSTICS_UPDATE_DEBOUNCE) .await; loop { let Some((path, language_server_id)) = this.update(&mut cx, |this, _| { let Some((path, language_server_id)) = this.paths_to_update.pop_first() else { this.update_excerpts_task.take(); return None; }; Some((path, language_server_id)) })? else { break; }; if let Some(buffer) = project_handle .update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))? .await .log_err() { this.update_in(&mut cx, |this, window, cx| { this.update_excerpts(path, language_server_id, buffer, window, cx) })? .await?; } } Ok(()) })); } fn new( project_handle: Entity<Project>, include_warnings: bool, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { Self::new_with_context( editor::DEFAULT_MULTIBUFFER_CONTEXT, include_warnings, project_handle, workspace, window, cx, ) } fn deploy( workspace: &mut Workspace, _: &Deploy, window: &mut Window, cx: &mut Context<Workspace>, ) { if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) { workspace.activate_item(&existing, true, true, window, cx); } else { let workspace_handle = cx.entity().downgrade(); let include_warnings = match cx.try_global::<IncludeWarnings>() { Some(include_warnings) => include_warnings.0, None => ProjectSettings::get_global(cx).diagnostics.include_warnings, }; let diagnostics = cx.new(|cx| { ProjectDiagnosticsEditor::new( workspace.project().clone(), include_warnings, workspace_handle, window, cx, ) }); workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, window, cx); } } fn toggle_warnings(&mut self, _: &ToggleWarnings, window: &mut Window, cx: &mut Context<Self>) { self.include_warnings = !self.include_warnings; cx.set_global(IncludeWarnings(self.include_warnings)); self.update_all_excerpts(window, cx); cx.notify(); } fn focus_in(&mut self, window: &mut Window, cx: &mut Context<Self>) { if self.focus_handle.is_focused(window) && !self.path_states.is_empty() { self.editor.focus_handle(cx).focus(window) } } fn focus_out(&mut self, window: &mut Window, cx: &mut Context<Self>) { if !self.focus_handle.is_focused(window) && !self.editor.focus_handle(cx).is_focused(window) { self.update_stale_excerpts(window, cx); } } /// Enqueue an update of all excerpts. Updates all paths that either /// currently have diagnostics or are currently present in this view. fn update_all_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.project.update(cx, |project, cx| { let mut paths = project .diagnostic_summaries(false, cx) .map(|(path, _, _)| (path, None)) .collect::<BTreeSet<_>>(); paths.extend( self.path_states .iter() .map(|state| (state.path.clone(), None)), ); let paths_to_update = std::mem::take(&mut self.paths_to_update); paths.extend(paths_to_update.into_iter().map(|(path, _)| (path, None))); self.paths_to_update = paths; }); self.update_stale_excerpts(window, cx); } fn update_excerpts( &mut self, path_to_update: ProjectPath, server_to_update: Option<LanguageServerId>, buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<Result<()>> { let was_empty = self.path_states.is_empty(); let snapshot = buffer.read(cx).snapshot(); let path_ix = match self .path_states .binary_search_by_key(&&path_to_update, |e| &e.path) { Ok(ix) => ix, Err(ix) => { self.path_states.insert( ix, PathState { path: path_to_update.clone(), diagnostic_groups: Default::default(), }, ); ix } }; let mut prev_excerpt_id = if path_ix > 0 { let prev_path_last_group = &self.path_states[path_ix - 1] .diagnostic_groups .last() .unwrap(); *prev_path_last_group.excerpts.last().unwrap() } else { ExcerptId::min() }; let mut new_group_ixs = Vec::new(); let mut blocks_to_add = Vec::new(); let mut blocks_to_remove = HashSet::default(); let mut first_excerpt_id = None; let max_severity = if self.include_warnings { DiagnosticSeverity::WARNING } else { DiagnosticSeverity::ERROR }; let excerpts = self.excerpts.clone().downgrade(); let context = self.context; let editor = self.editor.clone().downgrade(); cx.spawn_in(window, move |this, mut cx| async move { let mut old_groups = this .update(&mut cx, |this, _| { mem::take(&mut this.path_states[path_ix].diagnostic_groups) })? .into_iter() .enumerate() .peekable(); let mut new_groups = snapshot .diagnostic_groups(server_to_update) .into_iter() .filter(|(_, group)| { group.entries[group.primary_ix].diagnostic.severity <= max_severity }) .peekable(); loop { let mut to_insert = None; let mut to_remove = None; let mut to_keep = None; match (old_groups.peek(), new_groups.peek()) { (None, None) => break, (None, Some(_)) => to_insert = new_groups.next(), (Some((_, old_group)), None) => { if server_to_update.map_or(true, |id| id == old_group.language_server_id) { to_remove = old_groups.next(); } else { to_keep = old_groups.next(); } } (Some((_, old_group)), Some((new_language_server_id, new_group))) => { let old_primary = &old_group.primary_diagnostic; let new_primary = &new_group.entries[new_group.primary_ix]; match compare_diagnostics(old_primary, new_primary, &snapshot) .then_with(|| old_group.language_server_id.cmp(new_language_server_id)) { Ordering::Less => { if server_to_update .map_or(true, |id| id == old_group.language_server_id) { to_remove = old_groups.next(); } else { to_keep = old_groups.next(); } } Ordering::Equal => { to_keep = old_groups.next(); new_groups.next(); } Ordering::Greater => to_insert = new_groups.next(), } } } if let Some((language_server_id, group)) = to_insert { let mut group_state = DiagnosticGroupState { language_server_id, primary_diagnostic: group.entries[group.primary_ix].clone(), primary_excerpt_ix: 0, excerpts: Default::default(), blocks: Default::default(), block_count: 0, }; let mut pending_range: Option<(Range<Point>, Range<Point>, usize)> = None; let mut is_first_excerpt_for_group = true; for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() { let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot)); let expanded_range = if let Some(entry) = &resolved_entry { Some( context_range_for_entry( entry.range.clone(), context, snapshot.clone(), (*cx).clone(), ) .await, ) } else {