pub mod items;mod toolbar_controls; mod diagnostic_renderer; #[cfg(test)]mod diagnostics_tests; use anyhow::Result;use collections::{BTreeSet, HashMap};use diagnostic_renderer::DiagnosticBlock;use editor::{ DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId}, scroll::Autoscroll,};use futures::future::join_all;use gpui::{ AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, div,};use language::{ Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, Point, ToTreeSitterPoint,};use project::{ DiagnosticSummary, Project, ProjectPath, lsp_store::rust_analyzer_ext::{cancel_flycheck, run_flycheck}, project_settings::{DiagnosticSeverity, ProjectSettings},};use settings::Settings;use std::{ any::{Any, TypeId}, cmp::{self, Ordering}, ops::{Range, RangeInclusive}, sync::Arc, time::Duration,};use text::{BufferId, OffsetRangeExt};use theme::ActiveTheme;pub use toolbar_controls::ToolbarControls;use ui::{Icon, IconName, Label, h_flex, prelude::*};use util::ResultExt;use workspace::{ ItemNavHistory, ToolbarItemLocation, Workspace, item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams}, searchable::SearchableItemHandle,}; actions!( diagnostics, [Deploy, ToggleWarnings, ToggleDiagnosticsRefresh]); #[derive(Default)]pub(crate) struct IncludeWarnings(bool);impl Global for IncludeWarnings {} pub fn init(cx: &mut App) { editor::set_diagnostic_renderer(diagnostic_renderer::DiagnosticRenderer {}, cx); cx.observe_new(ProjectDiagnosticsEditor::register).detach();} pub(crate) struct ProjectDiagnosticsEditor { project: Entity<Project>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, editor: Entity<Editor>, diagnostics: HashMap<BufferId, Vec<DiagnosticEntry<text::Anchor>>>, blocks: HashMap<BufferId, Vec<CustomBlockId>>, summary: DiagnosticSummary, multibuffer: Entity<MultiBuffer>, paths_to_update: BTreeSet<ProjectPath>, include_warnings: bool, update_excerpts_task: Option<Task<Result<()>>>, cargo_diagnostics_fetch: CargoDiagnosticsFetchState, _subscription: Subscription,} struct CargoDiagnosticsFetchState { fetch_task: Option<Task<()>>, cancel_task: Option<Task<()>>, diagnostic_sources: Arc<Vec<ProjectPath>>,} impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {} const DIAGNOSTICS_UPDATE_DELAY: 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)) .on_action(cx.listener(Self::toggle_diagnostics_refresh)) .child(child) }} impl ProjectDiagnosticsEditor { fn register( workspace: &mut Workspace, _window: Option<&mut Window>, _: &mut Context<Workspace>, ) { workspace.register_action(Self::deploy); } fn new( 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()); 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()), window, cx); editor.set_vertical_scroll_margin(5, cx); editor.disable_inline_diagnostics(); editor.set_max_diagnostics_severity( if include_warnings { DiagnosticSeverity::Warning } else { DiagnosticSeverity::Error }, cx, ); editor.set_all_diagnostics_active(cx); editor }); cx.subscribe_in( &editor, window, |this, _editor, event: &EditorEvent, window, cx| { cx.emit(event.clone()); match event { EditorEvent::Focused => { if this.multibuffer.read(cx).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| { let include_warnings = cx.global::<IncludeWarnings>().0; this.include_warnings = include_warnings; this.editor.update(cx, |editor, cx| { editor.set_max_diagnostics_severity( if include_warnings { DiagnosticSeverity::Warning } else { DiagnosticSeverity::Error }, cx, ) }); this.diagnostics.clear(); this.update_all_diagnostics(false, window, cx); }) .detach(); cx.observe_release(&cx.entity(), |editor, _, cx| { editor.stop_cargo_diagnostics_fetch(cx); }) .detach(); let project = project_handle.read(cx); let mut this = Self { project: project_handle.clone(), summary: project.diagnostic_summary(false, cx), diagnostics: Default::default(), blocks: Default::default(), include_warnings, workspace, multibuffer: excerpts, focus_handle, editor, paths_to_update: Default::default(), update_excerpts_task: None, cargo_diagnostics_fetch: CargoDiagnosticsFetchState { fetch_task: None, cancel_task: None, diagnostic_sources: Arc::new(Vec::new()), }, _subscription: project_event_subscription, }; this.update_all_diagnostics(true, 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, async move |this, cx| { cx.background_executor() .timer(DIAGNOSTICS_UPDATE_DELAY) .await; loop { let Some(path) = this.update(cx, |this, cx| { let Some(path) = this.paths_to_update.pop_first() else { this.update_excerpts_task = None; cx.notify(); return None; }; Some(path) })? else { break; }; if let Some(buffer) = project_handle .update(cx, |project, cx| project.open_buffer(path.clone(), cx))? .await .log_err() { this.update_in(cx, |this, window, cx| { this.update_excerpts(buffer, window, cx) })? .await?; } } Ok(()) })); } fn deploy( workspace: &mut Workspace, _: &Deploy, window: &mut Window, cx: &mut Context<Workspace>, ) { if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) { let is_active = workspace .active_item(cx) .is_some_and(|item| item.item_id() == existing.item_id()); workspace.activate_item(&existing, true, !is_active, 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( include_warnings, workspace.project().clone(), workspace_handle, window, cx, ) }); workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, window, cx); } } fn toggle_warnings(&mut self, _: &ToggleWarnings, _: &mut Window, cx: &mut Context<Self>) { cx.set_global(IncludeWarnings(!self.include_warnings)); } fn toggle_diagnostics_refresh( &mut self, _: &ToggleDiagnosticsRefresh, window: &mut Window, cx: &mut Context<Self>, ) { let fetch_cargo_diagnostics = ProjectSettings::get_global(cx) .diagnostics .fetch_cargo_diagnostics(); if fetch_cargo_diagnostics { if self.cargo_diagnostics_fetch.fetch_task.is_some() { self.stop_cargo_diagnostics_fetch(cx); } else { self.update_all_diagnostics(false, window, cx); } } else { if self.update_excerpts_task.is_some() { self.update_excerpts_task = None; } else { self.update_all_diagnostics(false, window, cx); } } cx.notify(); } fn focus_in(&mut self, window: &mut Window, cx: &mut Context<Self>) { if self.focus_handle.is_focused(window) && !self.multibuffer.read(cx).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); } } fn update_all_diagnostics( &mut self, first_launch: bool, window: &mut Window, cx: &mut Context<Self>, ) { let cargo_diagnostics_sources = self.cargo_diagnostics_sources(cx); if cargo_diagnostics_sources.is_empty() { self.update_all_excerpts(window, cx); } else if first_launch && !self.summary.is_empty() { self.update_all_excerpts(window, cx); } else { self.fetch_cargo_diagnostics(Arc::new(cargo_diagnostics_sources), cx); } } fn fetch_cargo_diagnostics( &mut self, diagnostics_sources: Arc<Vec<ProjectPath>>, cx: &mut Context<Self>, ) { let project = self.project.clone(); self.cargo_diagnostics_fetch.cancel_task = None; self.cargo_diagnostics_fetch.fetch_task = None; self.cargo_diagnostics_fetch.diagnostic_sources = diagnostics_sources.clone(); if self.cargo_diagnostics_fetch.diagnostic_sources.is_empty() { return; } self.cargo_diagnostics_fetch.fetch_task = Some(cx.spawn(async move |editor, cx| { let mut fetch_tasks = Vec::new(); for buffer_path in diagnostics_sources.iter().cloned() { if cx .update(|cx| { fetch_tasks.push(run_flycheck(project.clone(), buffer_path, cx)); }) .is_err() { break; } } let _ = join_all(fetch_tasks).await; editor .update(cx, |editor, _| { editor.cargo_diagnostics_fetch.fetch_task = None; }) .ok(); })); } fn stop_cargo_diagnostics_fetch(&mut self, cx: &mut App) { self.cargo_diagnostics_fetch.fetch_task = None; let mut cancel_gasks = Vec::new(); for buffer_path in std::mem::take(&mut self.cargo_diagnostics_fetch.diagnostic_sources) .iter() .cloned() { cancel_gasks.push(cancel_flycheck(self.project.clone(), buffer_path, cx)); } self.cargo_diagnostics_fetch.cancel_task = Some(cx.background_spawn(async move { let _ = join_all(cancel_gasks).await; log::info!("Finished fetching cargo diagnostics"); })); } /// 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) .collect::<BTreeSet<_>>(); self.multibuffer.update(cx, |multibuffer, cx| { for buffer in multibuffer.all_buffers() { if let Some(file) = buffer.read(cx).file() { paths.insert(ProjectPath { path: file.path().clone(), worktree_id: file.worktree_id(cx), }); } } }); self.paths_to_update = paths; }); self.update_stale_excerpts(window, cx); } fn diagnostics_are_unchanged( &self, existing: &Vec<DiagnosticEntry<text::Anchor>>, new: &Vec<DiagnosticEntry<text::Anchor>>, snapshot: &BufferSnapshot, ) -> bool { if existing.len() != new.len() { return false; } existing.iter().zip(new.iter()).all(|(existing, new)| { existing.diagnostic.message == new.diagnostic.message && existing.diagnostic.severity == new.diagnostic.severity && existing.diagnostic.is_primary == new.diagnostic.is_primary && existing.range.to_offset(snapshot) == new.range.to_offset(snapshot) }) } fn update_excerpts( &mut self,Lines 1–500 of 1098. Load more
pub mod items; mod toolbar_controls; mod diagnostic_renderer; #[cfg(test)] mod diagnostics_tests; use anyhow::Result; use collections::{BTreeSet, HashMap}; use diagnostic_renderer::DiagnosticBlock; use editor::{ DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId}, scroll::Autoscroll, }; use futures::future::join_all; use gpui::{ AnyElement, AnyView, App, AsyncApp, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, div, }; use language::{ Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, Point, ToTreeSitterPoint, }; use project::{ DiagnosticSummary, Project, ProjectPath, lsp_store::rust_analyzer_ext::{cancel_flycheck, run_flycheck}, project_settings::{DiagnosticSeverity, ProjectSettings}, }; use settings::Settings; use std::{ any::{Any, TypeId}, cmp::{self, Ordering}, ops::{Range, RangeInclusive}, sync::Arc, time::Duration, }; use text::{BufferId, OffsetRangeExt}; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; use ui::{Icon, IconName, Label, h_flex, prelude::*}; use util::ResultExt; use workspace::{ ItemNavHistory, ToolbarItemLocation, Workspace, item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams}, searchable::SearchableItemHandle, }; actions!( diagnostics, [Deploy, ToggleWarnings, ToggleDiagnosticsRefresh] ); #[derive(Default)] pub(crate) struct IncludeWarnings(bool); impl Global for IncludeWarnings {} pub fn init(cx: &mut App) { editor::set_diagnostic_renderer(diagnostic_renderer::DiagnosticRenderer {}, cx); cx.observe_new(ProjectDiagnosticsEditor::register).detach(); } pub(crate) struct ProjectDiagnosticsEditor { project: Entity<Project>, workspace: WeakEntity<Workspace>, focus_handle: FocusHandle, editor: Entity<Editor>, diagnostics: HashMap<BufferId, Vec<DiagnosticEntry<text::Anchor>>>, blocks: HashMap<BufferId, Vec<CustomBlockId>>, summary: DiagnosticSummary, multibuffer: Entity<MultiBuffer>, paths_to_update: BTreeSet<ProjectPath>, include_warnings: bool, update_excerpts_task: Option<Task<Result<()>>>, cargo_diagnostics_fetch: CargoDiagnosticsFetchState, _subscription: Subscription, } struct CargoDiagnosticsFetchState { fetch_task: Option<Task<()>>, cancel_task: Option<Task<()>>, diagnostic_sources: Arc<Vec<ProjectPath>>, } impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {} const DIAGNOSTICS_UPDATE_DELAY: 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)) .on_action(cx.listener(Self::toggle_diagnostics_refresh)) .child(child) } } impl ProjectDiagnosticsEditor { fn register( workspace: &mut Workspace, _window: Option<&mut Window>, _: &mut Context<Workspace>, ) { workspace.register_action(Self::deploy); } fn new( 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()); 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()), window, cx); editor.set_vertical_scroll_margin(5, cx); editor.disable_inline_diagnostics(); editor.set_max_diagnostics_severity( if include_warnings { DiagnosticSeverity::Warning } else { DiagnosticSeverity::Error }, cx, ); editor.set_all_diagnostics_active(cx); editor }); cx.subscribe_in( &editor, window, |this, _editor, event: &EditorEvent, window, cx| { cx.emit(event.clone()); match event { EditorEvent::Focused => { if this.multibuffer.read(cx).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| { let include_warnings = cx.global::<IncludeWarnings>().0; this.include_warnings = include_warnings; this.editor.update(cx, |editor, cx| { editor.set_max_diagnostics_severity( if include_warnings { DiagnosticSeverity::Warning } else { DiagnosticSeverity::Error }, cx, ) }); this.diagnostics.clear(); this.update_all_diagnostics(false, window, cx); }) .detach(); cx.observe_release(&cx.entity(), |editor, _, cx| { editor.stop_cargo_diagnostics_fetch(cx); }) .detach(); let project = project_handle.read(cx); let mut this = Self { project: project_handle.clone(), summary: project.diagnostic_summary(false, cx), diagnostics: Default::default(), blocks: Default::default(), include_warnings, workspace, multibuffer: excerpts, focus_handle, editor, paths_to_update: Default::default(), update_excerpts_task: None, cargo_diagnostics_fetch: CargoDiagnosticsFetchState { fetch_task: None, cancel_task: None, diagnostic_sources: Arc::new(Vec::new()), }, _subscription: project_event_subscription, }; this.update_all_diagnostics(true, 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, async move |this, cx| { cx.background_executor() .timer(DIAGNOSTICS_UPDATE_DELAY) .await; loop { let Some(path) = this.update(cx, |this, cx| { let Some(path) = this.paths_to_update.pop_first() else { this.update_excerpts_task = None; cx.notify(); return None; }; Some(path) })? else { break; }; if let Some(buffer) = project_handle .update(cx, |project, cx| project.open_buffer(path.clone(), cx))? .await .log_err() { this.update_in(cx, |this, window, cx| { this.update_excerpts(buffer, window, cx) })? .await?; } } Ok(()) })); } fn deploy( workspace: &mut Workspace, _: &Deploy, window: &mut Window, cx: &mut Context<Workspace>, ) { if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) { let is_active = workspace .active_item(cx) .is_some_and(|item| item.item_id() == existing.item_id()); workspace.activate_item(&existing, true, !is_active, 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( include_warnings, workspace.project().clone(), workspace_handle, window, cx, ) }); workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, window, cx); } } fn toggle_warnings(&mut self, _: &ToggleWarnings, _: &mut Window, cx: &mut Context<Self>) { cx.set_global(IncludeWarnings(!self.include_warnings)); } fn toggle_diagnostics_refresh( &mut self, _: &ToggleDiagnosticsRefresh, window: &mut Window, cx: &mut Context<Self>, ) { let fetch_cargo_diagnostics = ProjectSettings::get_global(cx) .diagnostics .fetch_cargo_diagnostics(); if fetch_cargo_diagnostics { if self.cargo_diagnostics_fetch.fetch_task.is_some() { self.stop_cargo_diagnostics_fetch(cx); } else { self.update_all_diagnostics(false, window, cx); } } else { if self.update_excerpts_task.is_some() { self.update_excerpts_task = None; } else { self.update_all_diagnostics(false, window, cx); } } cx.notify(); } fn focus_in(&mut self, window: &mut Window, cx: &mut Context<Self>) { if self.focus_handle.is_focused(window) && !self.multibuffer.read(cx).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); } } fn update_all_diagnostics( &mut self, first_launch: bool, window: &mut Window, cx: &mut Context<Self>, ) { let cargo_diagnostics_sources = self.cargo_diagnostics_sources(cx); if cargo_diagnostics_sources.is_empty() { self.update_all_excerpts(window, cx); } else if first_launch && !self.summary.is_empty() { self.update_all_excerpts(window, cx); } else { self.fetch_cargo_diagnostics(Arc::new(cargo_diagnostics_sources), cx); } } fn fetch_cargo_diagnostics( &mut self, diagnostics_sources: Arc<Vec<ProjectPath>>, cx: &mut Context<Self>, ) { let project = self.project.clone(); self.cargo_diagnostics_fetch.cancel_task = None; self.cargo_diagnostics_fetch.fetch_task = None; self.cargo_diagnostics_fetch.diagnostic_sources = diagnostics_sources.clone(); if self.cargo_diagnostics_fetch.diagnostic_sources.is_empty() { return; } self.cargo_diagnostics_fetch.fetch_task = Some(cx.spawn(async move |editor, cx| { let mut fetch_tasks = Vec::new(); for buffer_path in diagnostics_sources.iter().cloned() { if cx .update(|cx| { fetch_tasks.push(run_flycheck(project.clone(), buffer_path, cx)); }) .is_err() { break; } } let _ = join_all(fetch_tasks).await; editor .update(cx, |editor, _| { editor.cargo_diagnostics_fetch.fetch_task = None; }) .ok(); })); } fn stop_cargo_diagnostics_fetch(&mut self, cx: &mut App) { self.cargo_diagnostics_fetch.fetch_task = None; let mut cancel_gasks = Vec::new(); for buffer_path in std::mem::take(&mut self.cargo_diagnostics_fetch.diagnostic_sources) .iter() .cloned() { cancel_gasks.push(cancel_flycheck(self.project.clone(), buffer_path, cx)); } self.cargo_diagnostics_fetch.cancel_task = Some(cx.background_spawn(async move { let _ = join_all(cancel_gasks).await; log::info!("Finished fetching cargo diagnostics"); })); } /// 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) .collect::<BTreeSet<_>>(); self.multibuffer.update(cx, |multibuffer, cx| { for buffer in multibuffer.all_buffers() { if let Some(file) = buffer.read(cx).file() { paths.insert(ProjectPath { path: file.path().clone(), worktree_id: file.worktree_id(cx), }); } } }); self.paths_to_update = paths; }); self.update_stale_excerpts(window, cx); } fn diagnostics_are_unchanged( &self, existing: &Vec<DiagnosticEntry<text::Anchor>>, new: &Vec<DiagnosticEntry<text::Anchor>>, snapshot: &BufferSnapshot, ) -> bool { if existing.len() != new.len() { return false; } existing.iter().zip(new.iter()).all(|(existing, new)| { existing.diagnostic.message == new.diagnostic.message && existing.diagnostic.severity == new.diagnostic.severity && existing.diagnostic.is_primary == new.diagnostic.is_primary && existing.range.to_offset(snapshot) == new.range.to_offset(snapshot) }) } fn update_excerpts( &mut self,