use crate::{ DIAGNOSTICS_UPDATE_DEBOUNCE, IncludeWarnings, ToggleWarnings, context_range_for_entry, diagnostic_renderer::{DiagnosticBlock, DiagnosticRenderer}, toolbar_controls::DiagnosticsToolbarEditor,};use anyhow::Result;use collections::HashMap;use editor::{ Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId}, multibuffer_context_lines,};use gpui::{ AnyElement, App, AppContext, Context, Entity, EntityId, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, div,};use language::{Buffer, Capability, DiagnosticEntry, DiagnosticEntryRef, Point};use project::{ DiagnosticSummary, Event, Project, ProjectItem, ProjectPath, project_settings::{DiagnosticSeverity, ProjectSettings},};use settings::Settings;use std::{ any::{Any, TypeId}, cmp::{self, Ordering}, sync::Arc,};use text::{Anchor, BufferSnapshot, OffsetRangeExt};use ui::{Button, ButtonStyle, Icon, IconName, Label, Tooltip, h_flex, prelude::*};use workspace::{ ItemHandle, ItemNavHistory, Workspace, item::{Item, ItemEvent, TabContentParams},}; actions!( diagnostics, [ /// Opens the project diagnostics view for the currently focused file. DeployCurrentFile, ]); /// The `BufferDiagnosticsEditor` is meant to be used when dealing specifically/// with diagnostics for a single buffer, as only the excerpts of the buffer/// where diagnostics are available are displayed.pub(crate) struct BufferDiagnosticsEditor { pub project: Entity<Project>, focus_handle: FocusHandle, editor: Entity<Editor>, /// The current diagnostic entries in the `BufferDiagnosticsEditor`. Used to /// allow quick comparison of updated diagnostics, to confirm if anything /// has changed. pub(crate) diagnostics: Vec<DiagnosticEntry<Anchor>>, /// The blocks used to display the diagnostics' content in the editor, next /// to the excerpts where the diagnostic originated. blocks: Vec<CustomBlockId>, /// Multibuffer to contain all excerpts that contain diagnostics, which are /// to be rendered in the editor. multibuffer: Entity<MultiBuffer>, /// The buffer for which the editor is displaying diagnostics and excerpts /// for. buffer: Option<Entity<Buffer>>, /// The path for which the editor is displaying diagnostics for. project_path: ProjectPath, /// Summary of the number of warnings and errors for the path. Used to /// display the number of warnings and errors in the tab's content. summary: DiagnosticSummary, /// Whether to include warnings in the list of diagnostics shown in the /// editor. pub(crate) include_warnings: bool, /// Keeps track of whether there's a background task already running to /// update the excerpts, in order to avoid firing multiple tasks for this purpose. pub(crate) update_excerpts_task: Option<Task<Result<()>>>, /// The project's subscription, responsible for processing events related to /// diagnostics. _subscription: Subscription,} impl BufferDiagnosticsEditor { /// Creates new instance of the `BufferDiagnosticsEditor` which can then be /// displayed by adding it to a pane. pub fn new( project_path: ProjectPath, project_handle: Entity<Project>, buffer: Option<Entity<Buffer>>, include_warnings: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { // Subscribe to project events related to diagnostics so the // `BufferDiagnosticsEditor` can update its state accordingly. let project_event_subscription = cx.subscribe_in( &project_handle, window, |buffer_diagnostics_editor, _project, event, window, cx| match event { Event::DiskBasedDiagnosticsStarted { .. } => { cx.notify(); } Event::DiskBasedDiagnosticsFinished { .. } => { buffer_diagnostics_editor.update_all_excerpts(window, cx); } Event::DiagnosticsUpdated { paths, language_server_id, } => { // When diagnostics have been updated, the // `BufferDiagnosticsEditor` should update its state only if // one of the paths matches its `project_path`, otherwise // the event should be ignored. if paths.contains(&buffer_diagnostics_editor.project_path) { buffer_diagnostics_editor.update_diagnostic_summary(cx); if buffer_diagnostics_editor.editor.focus_handle(cx).contains_focused(window, cx) || buffer_diagnostics_editor.focus_handle.contains_focused(window, cx) { log::debug!("diagnostics updated for server {language_server_id}. recording change"); } else { log::debug!("diagnostics updated for server {language_server_id}. updating excerpts"); buffer_diagnostics_editor.update_all_excerpts(window, cx); } } } _ => {} }, ); let focus_handle = cx.focus_handle(); cx.on_focus_in( &focus_handle, window, |buffer_diagnostics_editor, window, cx| buffer_diagnostics_editor.focus_in(window, cx), ) .detach(); cx.on_focus_out( &focus_handle, window, |buffer_diagnostics_editor, _event, window, cx| { buffer_diagnostics_editor.focus_out(window, cx) }, ) .detach(); let summary = project_handle .read(cx) .diagnostic_summary_for_path(&project_path, cx); let multibuffer = cx.new(|cx| MultiBuffer::new(project_handle.read(cx).capability())); let max_severity = Self::max_diagnostics_severity(include_warnings); let editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer( multibuffer.clone(), Some(project_handle.clone()), window, cx, ); editor.set_vertical_scroll_margin(5, cx); editor.disable_inline_diagnostics(); editor.set_max_diagnostics_severity(max_severity, cx); editor.set_all_diagnostics_active(cx); editor }); // Subscribe to events triggered by the editor in order to correctly // update the buffer's excerpts. cx.subscribe_in( &editor, window, |buffer_diagnostics_editor, _editor, event: &EditorEvent, window, cx| { cx.emit(event.clone()); match event { // If the user tries to focus on the editor but there's actually // no excerpts for the buffer, focus back on the // `BufferDiagnosticsEditor` instance. EditorEvent::Focused => { if buffer_diagnostics_editor.multibuffer.read(cx).is_empty() { window.focus(&buffer_diagnostics_editor.focus_handle, cx); } } EditorEvent::Blurred => { buffer_diagnostics_editor.update_all_excerpts(window, cx) } _ => {} } }, ) .detach(); let diagnostics = vec![]; let update_excerpts_task = None; let mut buffer_diagnostics_editor = Self { project: project_handle, focus_handle, editor, diagnostics, blocks: Default::default(), multibuffer, buffer, project_path, summary, include_warnings, update_excerpts_task, _subscription: project_event_subscription, }; buffer_diagnostics_editor.update_all_diagnostics(window, cx); buffer_diagnostics_editor } fn deploy( workspace: &mut Workspace, _: &DeployCurrentFile, window: &mut Window, cx: &mut Context<Workspace>, ) { // Determine the currently opened path by finding the active editor and // finding the project path for the buffer. // If there's no active editor with a project path, avoiding deploying // the buffer diagnostics view. if let Some(editor) = workspace.active_item_as::<Editor>(cx) && let Some(project_path) = editor.project_path(cx) { // Check if there's already a `BufferDiagnosticsEditor` tab for this // same path, and if so, focus on that one instead of creating a new // one. let existing_editor = workspace .items_of_type::<BufferDiagnosticsEditor>(cx) .find(|editor| editor.read(cx).project_path == project_path); if let Some(editor) = existing_editor { workspace.activate_item(&editor, true, true, window, cx); } else { let include_warnings = match cx.try_global::<IncludeWarnings>() { Some(include_warnings) => include_warnings.0, None => ProjectSettings::get_global(cx).diagnostics.include_warnings, }; let item = cx.new(|cx| { Self::new( project_path, workspace.project().clone(), editor.read(cx).buffer().read(cx).as_singleton(), include_warnings, window, cx, ) }); workspace.add_item_to_active_pane(Box::new(item), None, true, window, cx); } } } pub fn register( workspace: &mut Workspace, _window: Option<&mut Window>, _: &mut Context<Workspace>, ) { workspace.register_action(Self::deploy); } fn update_all_diagnostics(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.update_all_excerpts(window, cx); } fn update_diagnostic_summary(&mut self, cx: &mut Context<Self>) { let project = self.project.read(cx); self.summary = project.diagnostic_summary_for_path(&self.project_path, cx); } /// Enqueue an update to the excerpts and diagnostic blocks being shown in /// the editor. pub(crate) fn update_all_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { // If there's already a task updating the excerpts, early return and let // the other task finish. if self.update_excerpts_task.is_some() { return; } let buffer = self.buffer.clone(); self.update_excerpts_task = Some(cx.spawn_in(window, async move |editor, cx| { cx.background_executor() .timer(DIAGNOSTICS_UPDATE_DEBOUNCE) .await; if let Some(buffer) = buffer { editor .update_in(cx, |editor, window, cx| { editor.update_excerpts(buffer, window, cx) })? .await?; }; let _ = editor.update(cx, |editor, cx| { editor.update_excerpts_task = None; cx.notify(); }); Ok(()) })); } /// Updates the excerpts in the `BufferDiagnosticsEditor` for a single /// buffer. fn update_excerpts( &mut self, buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<Result<()>> { let was_empty = self.multibuffer.read(cx).is_empty(); let multibuffer_context = multibuffer_context_lines(cx); let buffer_snapshot = buffer.read(cx).snapshot(); let buffer_snapshot_max = buffer_snapshot.max_point(); let max_severity = Self::max_diagnostics_severity(self.include_warnings) .into_lsp() .unwrap_or(lsp::DiagnosticSeverity::WARNING); cx.spawn_in(window, async move |buffer_diagnostics_editor, mut cx| { // Fetch the diagnostics for the whole of the buffer // (`Point::zero()..buffer_snapshot.max_point()`) so we can confirm // if the diagnostics changed, if it didn't, early return as there's // nothing to update. let diagnostics = buffer_snapshot .diagnostics_in_range::<_, Anchor>(Point::zero()..buffer_snapshot_max, false) .collect::<Vec<_>>(); let unchanged = buffer_diagnostics_editor.update(cx, |buffer_diagnostics_editor, _cx| { if buffer_diagnostics_editor .diagnostics_are_unchanged(&diagnostics, &buffer_snapshot) { return true; } buffer_diagnostics_editor.set_diagnostics(&diagnostics); return false; })?; if unchanged { return Ok(()); } // Mapping between the Group ID and a vector of DiagnosticEntry. let mut grouped: HashMap<usize, Vec<_>> = HashMap::default(); for entry in diagnostics { grouped .entry(entry.diagnostic.group_id) .or_default() .push(DiagnosticEntryRef { range: entry.range.to_point(&buffer_snapshot), diagnostic: entry.diagnostic, }) } let mut blocks: Vec<DiagnosticBlock> = Vec::new(); for (_, group) in grouped { // If the minimum severity of the group is higher than the // maximum severity, or it doesn't even have severity, skip this // group. if group .iter() .map(|d| d.diagnostic.severity) .min() .is_none_or(|severity| severity > max_severity) { continue; } let languages = buffer_diagnostics_editor .read_with(cx, |b, cx| b.project.read(cx).languages().clone()) .ok(); let diagnostic_blocks = cx.update(|_window, cx| { DiagnosticRenderer::diagnostic_blocks_for_group( group, buffer_snapshot.remote_id(), Some(Arc::new(buffer_diagnostics_editor.clone())), languages, cx, ) })?; // For each of the diagnostic blocks to be displayed in the // editor, figure out its index in the list of blocks. // // The following rules are used to determine the order: // 1. Blocks with a lower start position should come first. // 2. If two blocks have the same start position, the one with // the higher end position should come first. for diagnostic_block in diagnostic_blocks { let index = blocks.partition_point(|probe| { match probe .initial_range .start .cmp(&diagnostic_block.initial_range.start) { Ordering::Less => true, Ordering::Greater => false, Ordering::Equal => { probe.initial_range.end > diagnostic_block.initial_range.end } } }); blocks.insert(index, diagnostic_block); } } // Build the excerpt ranges for this specific buffer's diagnostics, // so those excerpts can later be used to update the excerpts shown // in the editor. // This is done by iterating over the list of diagnostic blocks and // determine what range does the diagnostic block span. let mut excerpt_ranges: Vec<ExcerptRange<_>> = Vec::new(); for diagnostic_block in blocks.iter() { let excerpt_range = context_range_for_entry( diagnostic_block.initial_range.clone(), multibuffer_context, buffer_snapshot.clone(), &mut cx, ) .await; let initial_range = buffer_snapshot .anchor_after(diagnostic_block.initial_range.start) ..buffer_snapshot.anchor_before(diagnostic_block.initial_range.end); let bin_search = |probe: &ExcerptRange<text::Anchor>| { let context_start = || { probe .context .start .cmp(&excerpt_range.start, &buffer_snapshot) }; let context_end = || probe.context.end.cmp(&excerpt_range.end, &buffer_snapshot); let primary_start = || { probe .primary .start .cmp(&initial_range.start, &buffer_snapshot) }; let primary_end = || probe.primary.end.cmp(&initial_range.end, &buffer_snapshot); context_start() .then_with(context_end) .then_with(primary_start) .then_with(primary_end) .then(cmp::Ordering::Greater) }; let index = excerpt_ranges .binary_search_by(bin_search) .unwrap_or_else(|i| i); excerpt_ranges.insert( index, ExcerptRange { context: excerpt_range, primary: initial_range, }, ) } // Finally, update the editor's content with the new excerpt ranges // for this editor, as well as the diagnostic blocks. buffer_diagnostics_editor.update_in(cx, |buffer_diagnostics_editor, window, cx| { // Remove the list of `CustomBlockId` from the editor's display // map, ensuring that if any diagnostics have been solved, the // associated block stops being shown. let block_ids = buffer_diagnostics_editor.blocks.clone(); buffer_diagnostics_editor.editor.update(cx, |editor, cx| { editor.display_map.update(cx, |display_map, cx| { display_map.remove_blocks(block_ids.into_iter().collect(), cx); }) }); let (anchor_ranges, _) = buffer_diagnostics_editor .multibuffer .update(cx, |multibuffer, cx| { let excerpt_ranges = excerpt_ranges .into_iter() .map(|range| ExcerptRange { context: range.context.to_point(&buffer_snapshot), primary: range.primary.to_point(&buffer_snapshot), }) .collect(); multibuffer.set_excerpt_ranges_for_path( PathKey::for_buffer(&buffer, cx), buffer.clone(), &buffer_snapshot, excerpt_ranges, cx, )Lines 1–500 of 1004. Load more
use crate::{ DIAGNOSTICS_UPDATE_DEBOUNCE, IncludeWarnings, ToggleWarnings, context_range_for_entry, diagnostic_renderer::{DiagnosticBlock, DiagnosticRenderer}, toolbar_controls::DiagnosticsToolbarEditor, }; use anyhow::Result; use collections::HashMap; use editor::{ Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId}, multibuffer_context_lines, }; use gpui::{ AnyElement, App, AppContext, Context, Entity, EntityId, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, div, }; use language::{Buffer, Capability, DiagnosticEntry, DiagnosticEntryRef, Point}; use project::{ DiagnosticSummary, Event, Project, ProjectItem, ProjectPath, project_settings::{DiagnosticSeverity, ProjectSettings}, }; use settings::Settings; use std::{ any::{Any, TypeId}, cmp::{self, Ordering}, sync::Arc, }; use text::{Anchor, BufferSnapshot, OffsetRangeExt}; use ui::{Button, ButtonStyle, Icon, IconName, Label, Tooltip, h_flex, prelude::*}; use workspace::{ ItemHandle, ItemNavHistory, Workspace, item::{Item, ItemEvent, TabContentParams}, }; actions!( diagnostics, [ /// Opens the project diagnostics view for the currently focused file. DeployCurrentFile, ] ); /// The `BufferDiagnosticsEditor` is meant to be used when dealing specifically /// with diagnostics for a single buffer, as only the excerpts of the buffer /// where diagnostics are available are displayed. pub(crate) struct BufferDiagnosticsEditor { pub project: Entity<Project>, focus_handle: FocusHandle, editor: Entity<Editor>, /// The current diagnostic entries in the `BufferDiagnosticsEditor`. Used to /// allow quick comparison of updated diagnostics, to confirm if anything /// has changed. pub(crate) diagnostics: Vec<DiagnosticEntry<Anchor>>, /// The blocks used to display the diagnostics' content in the editor, next /// to the excerpts where the diagnostic originated. blocks: Vec<CustomBlockId>, /// Multibuffer to contain all excerpts that contain diagnostics, which are /// to be rendered in the editor. multibuffer: Entity<MultiBuffer>, /// The buffer for which the editor is displaying diagnostics and excerpts /// for. buffer: Option<Entity<Buffer>>, /// The path for which the editor is displaying diagnostics for. project_path: ProjectPath, /// Summary of the number of warnings and errors for the path. Used to /// display the number of warnings and errors in the tab's content. summary: DiagnosticSummary, /// Whether to include warnings in the list of diagnostics shown in the /// editor. pub(crate) include_warnings: bool, /// Keeps track of whether there's a background task already running to /// update the excerpts, in order to avoid firing multiple tasks for this purpose. pub(crate) update_excerpts_task: Option<Task<Result<()>>>, /// The project's subscription, responsible for processing events related to /// diagnostics. _subscription: Subscription, } impl BufferDiagnosticsEditor { /// Creates new instance of the `BufferDiagnosticsEditor` which can then be /// displayed by adding it to a pane. pub fn new( project_path: ProjectPath, project_handle: Entity<Project>, buffer: Option<Entity<Buffer>>, include_warnings: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { // Subscribe to project events related to diagnostics so the // `BufferDiagnosticsEditor` can update its state accordingly. let project_event_subscription = cx.subscribe_in( &project_handle, window, |buffer_diagnostics_editor, _project, event, window, cx| match event { Event::DiskBasedDiagnosticsStarted { .. } => { cx.notify(); } Event::DiskBasedDiagnosticsFinished { .. } => { buffer_diagnostics_editor.update_all_excerpts(window, cx); } Event::DiagnosticsUpdated { paths, language_server_id, } => { // When diagnostics have been updated, the // `BufferDiagnosticsEditor` should update its state only if // one of the paths matches its `project_path`, otherwise // the event should be ignored. if paths.contains(&buffer_diagnostics_editor.project_path) { buffer_diagnostics_editor.update_diagnostic_summary(cx); if buffer_diagnostics_editor.editor.focus_handle(cx).contains_focused(window, cx) || buffer_diagnostics_editor.focus_handle.contains_focused(window, cx) { log::debug!("diagnostics updated for server {language_server_id}. recording change"); } else { log::debug!("diagnostics updated for server {language_server_id}. updating excerpts"); buffer_diagnostics_editor.update_all_excerpts(window, cx); } } } _ => {} }, ); let focus_handle = cx.focus_handle(); cx.on_focus_in( &focus_handle, window, |buffer_diagnostics_editor, window, cx| buffer_diagnostics_editor.focus_in(window, cx), ) .detach(); cx.on_focus_out( &focus_handle, window, |buffer_diagnostics_editor, _event, window, cx| { buffer_diagnostics_editor.focus_out(window, cx) }, ) .detach(); let summary = project_handle .read(cx) .diagnostic_summary_for_path(&project_path, cx); let multibuffer = cx.new(|cx| MultiBuffer::new(project_handle.read(cx).capability())); let max_severity = Self::max_diagnostics_severity(include_warnings); let editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer( multibuffer.clone(), Some(project_handle.clone()), window, cx, ); editor.set_vertical_scroll_margin(5, cx); editor.disable_inline_diagnostics(); editor.set_max_diagnostics_severity(max_severity, cx); editor.set_all_diagnostics_active(cx); editor }); // Subscribe to events triggered by the editor in order to correctly // update the buffer's excerpts. cx.subscribe_in( &editor, window, |buffer_diagnostics_editor, _editor, event: &EditorEvent, window, cx| { cx.emit(event.clone()); match event { // If the user tries to focus on the editor but there's actually // no excerpts for the buffer, focus back on the // `BufferDiagnosticsEditor` instance. EditorEvent::Focused => { if buffer_diagnostics_editor.multibuffer.read(cx).is_empty() { window.focus(&buffer_diagnostics_editor.focus_handle, cx); } } EditorEvent::Blurred => { buffer_diagnostics_editor.update_all_excerpts(window, cx) } _ => {} } }, ) .detach(); let diagnostics = vec![]; let update_excerpts_task = None; let mut buffer_diagnostics_editor = Self { project: project_handle, focus_handle, editor, diagnostics, blocks: Default::default(), multibuffer, buffer, project_path, summary, include_warnings, update_excerpts_task, _subscription: project_event_subscription, }; buffer_diagnostics_editor.update_all_diagnostics(window, cx); buffer_diagnostics_editor } fn deploy( workspace: &mut Workspace, _: &DeployCurrentFile, window: &mut Window, cx: &mut Context<Workspace>, ) { // Determine the currently opened path by finding the active editor and // finding the project path for the buffer. // If there's no active editor with a project path, avoiding deploying // the buffer diagnostics view. if let Some(editor) = workspace.active_item_as::<Editor>(cx) && let Some(project_path) = editor.project_path(cx) { // Check if there's already a `BufferDiagnosticsEditor` tab for this // same path, and if so, focus on that one instead of creating a new // one. let existing_editor = workspace .items_of_type::<BufferDiagnosticsEditor>(cx) .find(|editor| editor.read(cx).project_path == project_path); if let Some(editor) = existing_editor { workspace.activate_item(&editor, true, true, window, cx); } else { let include_warnings = match cx.try_global::<IncludeWarnings>() { Some(include_warnings) => include_warnings.0, None => ProjectSettings::get_global(cx).diagnostics.include_warnings, }; let item = cx.new(|cx| { Self::new( project_path, workspace.project().clone(), editor.read(cx).buffer().read(cx).as_singleton(), include_warnings, window, cx, ) }); workspace.add_item_to_active_pane(Box::new(item), None, true, window, cx); } } } pub fn register( workspace: &mut Workspace, _window: Option<&mut Window>, _: &mut Context<Workspace>, ) { workspace.register_action(Self::deploy); } fn update_all_diagnostics(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.update_all_excerpts(window, cx); } fn update_diagnostic_summary(&mut self, cx: &mut Context<Self>) { let project = self.project.read(cx); self.summary = project.diagnostic_summary_for_path(&self.project_path, cx); } /// Enqueue an update to the excerpts and diagnostic blocks being shown in /// the editor. pub(crate) fn update_all_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { // If there's already a task updating the excerpts, early return and let // the other task finish. if self.update_excerpts_task.is_some() { return; } let buffer = self.buffer.clone(); self.update_excerpts_task = Some(cx.spawn_in(window, async move |editor, cx| { cx.background_executor() .timer(DIAGNOSTICS_UPDATE_DEBOUNCE) .await; if let Some(buffer) = buffer { editor .update_in(cx, |editor, window, cx| { editor.update_excerpts(buffer, window, cx) })? .await?; }; let _ = editor.update(cx, |editor, cx| { editor.update_excerpts_task = None; cx.notify(); }); Ok(()) })); } /// Updates the excerpts in the `BufferDiagnosticsEditor` for a single /// buffer. fn update_excerpts( &mut self, buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Self>, ) -> Task<Result<()>> { let was_empty = self.multibuffer.read(cx).is_empty(); let multibuffer_context = multibuffer_context_lines(cx); let buffer_snapshot = buffer.read(cx).snapshot(); let buffer_snapshot_max = buffer_snapshot.max_point(); let max_severity = Self::max_diagnostics_severity(self.include_warnings) .into_lsp() .unwrap_or(lsp::DiagnosticSeverity::WARNING); cx.spawn_in(window, async move |buffer_diagnostics_editor, mut cx| { // Fetch the diagnostics for the whole of the buffer // (`Point::zero()..buffer_snapshot.max_point()`) so we can confirm // if the diagnostics changed, if it didn't, early return as there's // nothing to update. let diagnostics = buffer_snapshot .diagnostics_in_range::<_, Anchor>(Point::zero()..buffer_snapshot_max, false) .collect::<Vec<_>>(); let unchanged = buffer_diagnostics_editor.update(cx, |buffer_diagnostics_editor, _cx| { if buffer_diagnostics_editor .diagnostics_are_unchanged(&diagnostics, &buffer_snapshot) { return true; } buffer_diagnostics_editor.set_diagnostics(&diagnostics); return false; })?; if unchanged { return Ok(()); } // Mapping between the Group ID and a vector of DiagnosticEntry. let mut grouped: HashMap<usize, Vec<_>> = HashMap::default(); for entry in diagnostics { grouped .entry(entry.diagnostic.group_id) .or_default() .push(DiagnosticEntryRef { range: entry.range.to_point(&buffer_snapshot), diagnostic: entry.diagnostic, }) } let mut blocks: Vec<DiagnosticBlock> = Vec::new(); for (_, group) in grouped { // If the minimum severity of the group is higher than the // maximum severity, or it doesn't even have severity, skip this // group. if group .iter() .map(|d| d.diagnostic.severity) .min() .is_none_or(|severity| severity > max_severity) { continue; } let languages = buffer_diagnostics_editor .read_with(cx, |b, cx| b.project.read(cx).languages().clone()) .ok(); let diagnostic_blocks = cx.update(|_window, cx| { DiagnosticRenderer::diagnostic_blocks_for_group( group, buffer_snapshot.remote_id(), Some(Arc::new(buffer_diagnostics_editor.clone())), languages, cx, ) })?; // For each of the diagnostic blocks to be displayed in the // editor, figure out its index in the list of blocks. // // The following rules are used to determine the order: // 1. Blocks with a lower start position should come first. // 2. If two blocks have the same start position, the one with // the higher end position should come first. for diagnostic_block in diagnostic_blocks { let index = blocks.partition_point(|probe| { match probe .initial_range .start .cmp(&diagnostic_block.initial_range.start) { Ordering::Less => true, Ordering::Greater => false, Ordering::Equal => { probe.initial_range.end > diagnostic_block.initial_range.end } } }); blocks.insert(index, diagnostic_block); } } // Build the excerpt ranges for this specific buffer's diagnostics, // so those excerpts can later be used to update the excerpts shown // in the editor. // This is done by iterating over the list of diagnostic blocks and // determine what range does the diagnostic block span. let mut excerpt_ranges: Vec<ExcerptRange<_>> = Vec::new(); for diagnostic_block in blocks.iter() { let excerpt_range = context_range_for_entry( diagnostic_block.initial_range.clone(), multibuffer_context, buffer_snapshot.clone(), &mut cx, ) .await; let initial_range = buffer_snapshot .anchor_after(diagnostic_block.initial_range.start) ..buffer_snapshot.anchor_before(diagnostic_block.initial_range.end); let bin_search = |probe: &ExcerptRange<text::Anchor>| { let context_start = || { probe .context .start .cmp(&excerpt_range.start, &buffer_snapshot) }; let context_end = || probe.context.end.cmp(&excerpt_range.end, &buffer_snapshot); let primary_start = || { probe .primary .start .cmp(&initial_range.start, &buffer_snapshot) }; let primary_end = || probe.primary.end.cmp(&initial_range.end, &buffer_snapshot); context_start() .then_with(context_end) .then_with(primary_start) .then_with(primary_end) .then(cmp::Ordering::Greater) }; let index = excerpt_ranges .binary_search_by(bin_search) .unwrap_or_else(|i| i); excerpt_ranges.insert( index, ExcerptRange { context: excerpt_range, primary: initial_range, }, ) } // Finally, update the editor's content with the new excerpt ranges // for this editor, as well as the diagnostic blocks. buffer_diagnostics_editor.update_in(cx, |buffer_diagnostics_editor, window, cx| { // Remove the list of `CustomBlockId` from the editor's display // map, ensuring that if any diagnostics have been solved, the // associated block stops being shown. let block_ids = buffer_diagnostics_editor.blocks.clone(); buffer_diagnostics_editor.editor.update(cx, |editor, cx| { editor.display_map.update(cx, |display_map, cx| { display_map.remove_blocks(block_ids.into_iter().collect(), cx); }) }); let (anchor_ranges, _) = buffer_diagnostics_editor .multibuffer .update(cx, |multibuffer, cx| { let excerpt_ranges = excerpt_ranges .into_iter() .map(|range| ExcerptRange { context: range.context.to_point(&buffer_snapshot), primary: range.primary.to_point(&buffer_snapshot), }) .collect(); multibuffer.set_excerpt_ranges_for_path( PathKey::for_buffer(&buffer, cx), buffer.clone(), &buffer_snapshot, excerpt_ranges, cx, )