diagnostics: Reduce cloning of `DiagnosticEntry` (#39193)

Lukas Wirth created

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/assistant_slash_commands/src/diagnostics_command.rs |  4 
crates/diagnostics/src/buffer_diagnostics.rs               | 13 
crates/diagnostics/src/diagnostic_renderer.rs              | 22 -
crates/diagnostics/src/diagnostics.rs                      | 17 +
crates/diagnostics/src/items.rs                            | 14 
crates/diagnostics/src/toolbar_controls.rs                 | 25 -
crates/editor/src/editor.rs                                | 18 
crates/editor/src/hover_popover.rs                         |  2 
crates/language/src/buffer.rs                              | 20 
crates/language/src/diagnostic_set.rs                      | 96 ++++++-
crates/language/src/language.rs                            |  2 
crates/multi_buffer/src/multi_buffer.rs                    | 16 
crates/project/src/project_tests.rs                        | 15 
crates/workspace/src/status_bar.rs                         |  1 
14 files changed, 172 insertions(+), 93 deletions(-)

Detailed changes

crates/assistant_slash_commands/src/diagnostics_command.rs 🔗

@@ -6,7 +6,7 @@ use assistant_slash_command::{
 use fuzzy::{PathMatch, StringMatchCandidate};
 use gpui::{App, Entity, Task, WeakEntity};
 use language::{
-    Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
+    Anchor, BufferSnapshot, DiagnosticEntryRef, DiagnosticSeverity, LspAdapterDelegate,
     OffsetRangeExt, ToOffset,
 };
 use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
@@ -367,7 +367,7 @@ pub fn collect_buffer_diagnostics(
 
 fn collect_diagnostic(
     output: &mut SlashCommandOutput,
-    entry: &DiagnosticEntry<Anchor>,
+    entry: &DiagnosticEntryRef<'_, Anchor>,
     snapshot: &BufferSnapshot,
     include_warnings: bool,
 ) {

crates/diagnostics/src/buffer_diagnostics.rs 🔗

@@ -15,7 +15,7 @@ use gpui::{
     InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
     Task, WeakEntity, Window, actions, div,
 };
-use language::{Buffer, DiagnosticEntry, Point};
+use language::{Buffer, DiagnosticEntry, DiagnosticEntryRef, Point};
 use project::{
     DiagnosticSummary, Event, Project, ProjectItem, ProjectPath,
     project_settings::{DiagnosticSeverity, ProjectSettings},
@@ -350,7 +350,7 @@ impl BufferDiagnosticsEditor {
                 grouped
                     .entry(entry.diagnostic.group_id)
                     .or_default()
-                    .push(DiagnosticEntry {
+                    .push(DiagnosticEntryRef {
                         range: entry.range.to_point(&buffer_snapshot),
                         diagnostic: entry.diagnostic,
                     })
@@ -560,13 +560,16 @@ impl BufferDiagnosticsEditor {
         })
     }
 
-    fn set_diagnostics(&mut self, diagnostics: &Vec<DiagnosticEntry<Anchor>>) {
-        self.diagnostics = diagnostics.clone();
+    fn set_diagnostics(&mut self, diagnostics: &[DiagnosticEntryRef<'_, Anchor>]) {
+        self.diagnostics = diagnostics
+            .iter()
+            .map(DiagnosticEntryRef::to_owned)
+            .collect();
     }
 
     fn diagnostics_are_unchanged(
         &self,
-        diagnostics: &Vec<DiagnosticEntry<Anchor>>,
+        diagnostics: &Vec<DiagnosticEntryRef<'_, Anchor>>,
         snapshot: &BufferSnapshot,
     ) -> bool {
         if self.diagnostics.len() != diagnostics.len() {

crates/diagnostics/src/diagnostic_renderer.rs 🔗

@@ -6,7 +6,7 @@ use editor::{
     hover_popover::diagnostics_markdown_style,
 };
 use gpui::{AppContext, Entity, Focusable, WeakEntity};
-use language::{BufferId, Diagnostic, DiagnosticEntry};
+use language::{BufferId, Diagnostic, DiagnosticEntryRef};
 use lsp::DiagnosticSeverity;
 use markdown::{Markdown, MarkdownElement};
 use settings::Settings;
@@ -24,7 +24,7 @@ pub struct DiagnosticRenderer;
 
 impl DiagnosticRenderer {
     pub fn diagnostic_blocks_for_group(
-        diagnostic_group: Vec<DiagnosticEntry<Point>>,
+        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
         buffer_id: BufferId,
         diagnostics_editor: Option<Arc<dyn DiagnosticsToolbarEditor>>,
         cx: &mut App,
@@ -35,7 +35,7 @@ impl DiagnosticRenderer {
         else {
             return Vec::new();
         };
-        let primary = diagnostic_group[primary_ix].clone();
+        let primary = &diagnostic_group[primary_ix];
         let group_id = primary.diagnostic.group_id;
         let mut results = vec![];
         for entry in diagnostic_group.iter() {
@@ -123,7 +123,7 @@ impl DiagnosticRenderer {
 impl editor::DiagnosticRenderer for DiagnosticRenderer {
     fn render_group(
         &self,
-        diagnostic_group: Vec<DiagnosticEntry<Point>>,
+        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
         buffer_id: BufferId,
         snapshot: EditorSnapshot,
         editor: WeakEntity<Editor>,
@@ -152,19 +152,15 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
 
     fn render_hover(
         &self,
-        diagnostic_group: Vec<DiagnosticEntry<Point>>,
+        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
         range: Range<Point>,
         buffer_id: BufferId,
         cx: &mut App,
     ) -> Option<Entity<Markdown>> {
         let blocks = Self::diagnostic_blocks_for_group(diagnostic_group, buffer_id, None, cx);
-        blocks.into_iter().find_map(|block| {
-            if block.initial_range == range {
-                Some(block.markdown)
-            } else {
-                None
-            }
-        })
+        blocks
+            .into_iter()
+            .find_map(|block| (block.initial_range == range).then(|| block.markdown))
     }
 
     fn open_link(
@@ -189,7 +185,7 @@ pub(crate) struct DiagnosticBlock {
 impl DiagnosticBlock {
     pub fn render_block(&self, editor: WeakEntity<Editor>, bcx: &BlockContext) -> AnyElement {
         let cx = &bcx.app;
-        let status_colors = bcx.app.theme().status();
+        let status_colors = cx.theme().status();
 
         let max_width = bcx.em_width * 120.;
 

crates/diagnostics/src/diagnostics.rs 🔗

@@ -22,7 +22,8 @@ use gpui::{
     Subscription, Task, WeakEntity, Window, actions, div,
 };
 use language::{
-    Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, Point, ToTreeSitterPoint,
+    Bias, Buffer, BufferRow, BufferSnapshot, DiagnosticEntry, DiagnosticEntryRef, Point,
+    ToTreeSitterPoint,
 };
 use project::{
     DiagnosticSummary, Project, ProjectPath,
@@ -412,8 +413,8 @@ impl ProjectDiagnosticsEditor {
 
     fn diagnostics_are_unchanged(
         &self,
-        existing: &Vec<DiagnosticEntry<text::Anchor>>,
-        new: &Vec<DiagnosticEntry<text::Anchor>>,
+        existing: &[DiagnosticEntry<text::Anchor>],
+        new: &[DiagnosticEntryRef<'_, text::Anchor>],
         snapshot: &BufferSnapshot,
     ) -> bool {
         if existing.len() != new.len() {
@@ -457,7 +458,13 @@ impl ProjectDiagnosticsEditor {
                 }) {
                     return true;
                 }
-                this.diagnostics.insert(buffer_id, diagnostics.clone());
+                this.diagnostics.insert(
+                    buffer_id,
+                    diagnostics
+                        .iter()
+                        .map(DiagnosticEntryRef::to_owned)
+                        .collect(),
+                );
                 false
             })?;
             if unchanged {
@@ -469,7 +476,7 @@ impl ProjectDiagnosticsEditor {
                 grouped
                     .entry(entry.diagnostic.group_id)
                     .or_default()
-                    .push(DiagnosticEntry {
+                    .push(DiagnosticEntryRef {
                         range: entry.range.to_point(&buffer_snapshot),
                         diagnostic: entry.diagnostic,
                     })

crates/diagnostics/src/items.rs 🔗

@@ -14,12 +14,14 @@ use workspace::{StatusItemView, ToolbarItemEvent, Workspace, item::ItemHandle};
 
 use crate::{Deploy, IncludeWarnings, ProjectDiagnosticsEditor};
 
+/// The status bar item that displays diagnostic counts.
 pub struct DiagnosticIndicator {
     summary: project::DiagnosticSummary,
-    active_editor: Option<WeakEntity<Editor>>,
     workspace: WeakEntity<Workspace>,
     current_diagnostic: Option<Diagnostic>,
+    active_editor: Option<WeakEntity<Editor>>,
     _observe_active_editor: Option<Subscription>,
+
     diagnostics_update: Task<()>,
     diagnostic_summary_update: Task<()>,
 }
@@ -73,10 +75,9 @@ impl Render for DiagnosticIndicator {
                             cx,
                         )
                     })
-                    .on_click(cx.listener(|this, _, window, cx| {
-                        this.go_to_next_diagnostic(window, cx);
-                    }))
-                    .into_any_element(),
+                    .on_click(
+                        cx.listener(|this, _, window, cx| this.go_to_next_diagnostic(window, cx)),
+                    ),
             )
         } else {
             None
@@ -177,7 +178,8 @@ impl DiagnosticIndicator {
             .filter(|entry| !entry.range.is_empty())
             .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
             .map(|entry| entry.diagnostic);
-        if new_diagnostic != self.current_diagnostic {
+        if new_diagnostic != self.current_diagnostic.as_ref() {
+            let new_diagnostic = new_diagnostic.cloned();
             self.diagnostics_update =
                 cx.spawn_in(window, async move |diagnostics_indicator, cx| {
                     cx.background_executor()

crates/diagnostics/src/toolbar_controls.rs 🔗

@@ -75,12 +75,9 @@ impl Render for ToolbarControls {
                                 &ToggleDiagnosticsRefresh,
                             ))
                             .on_click(cx.listener(move |toolbar_controls, _, _, cx| {
-                                match toolbar_controls.editor() {
-                                    Some(editor) => {
-                                        editor.stop_updating(cx);
-                                        cx.notify();
-                                    }
-                                    None => {}
+                                if let Some(editor) = toolbar_controls.editor() {
+                                    editor.stop_updating(cx);
+                                    cx.notify();
                                 }
                             })),
                     )
@@ -95,11 +92,10 @@ impl Render for ToolbarControls {
                                 &ToggleDiagnosticsRefresh,
                             ))
                             .on_click(cx.listener({
-                                move |toolbar_controls, _, window, cx| match toolbar_controls
-                                    .editor()
-                                {
-                                    Some(editor) => editor.refresh_diagnostics(window, cx),
-                                    None => {}
+                                move |toolbar_controls, _, window, cx| {
+                                    if let Some(editor) = toolbar_controls.editor() {
+                                        editor.refresh_diagnostics(window, cx)
+                                    }
                                 }
                             })),
                     )
@@ -110,9 +106,10 @@ impl Render for ToolbarControls {
                     .icon_color(warning_color)
                     .shape(IconButtonShape::Square)
                     .tooltip(Tooltip::text(warning_tooltip))
-                    .on_click(cx.listener(|this, _, window, cx| match &this.editor {
-                        Some(editor) => editor.toggle_warnings(window, cx),
-                        None => {}
+                    .on_click(cx.listener(|this, _, window, cx| {
+                        if let Some(editor) = &this.editor {
+                            editor.toggle_warnings(window, cx)
+                        }
                     })),
             )
     }

crates/editor/src/editor.rs 🔗

@@ -122,7 +122,7 @@ use itertools::{Either, Itertools};
 use language::{
     AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
     BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
-    DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
+    DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
     IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
     TextObject, TransactionId, TreeSitterOptions, WordsQuery,
     language_settings::{
@@ -404,7 +404,7 @@ pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App)
 pub trait DiagnosticRenderer {
     fn render_group(
         &self,
-        diagnostic_group: Vec<DiagnosticEntry<Point>>,
+        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
         buffer_id: BufferId,
         snapshot: EditorSnapshot,
         editor: WeakEntity<Editor>,
@@ -413,7 +413,7 @@ pub trait DiagnosticRenderer {
 
     fn render_hover(
         &self,
-        diagnostic_group: Vec<DiagnosticEntry<Point>>,
+        diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
         range: Range<Point>,
         buffer_id: BufferId,
         cx: &mut App,
@@ -15970,11 +15970,11 @@ impl Editor {
             active_group_id = Some(active_group.group_id);
         }
 
-        fn filtered(
+        fn filtered<'a>(
             snapshot: EditorSnapshot,
             severity: GoToDiagnosticSeverityFilter,
-            diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
-        ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
+            diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
+        ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
             diagnostics
                 .filter(move |entry| severity.matches(entry.diagnostic.severity))
                 .filter(|entry| entry.range.start != entry.range.end)
@@ -15998,7 +15998,7 @@ impl Editor {
                 .filter(|entry| entry.range.start >= selection.start),
         );
 
-        let mut found: Option<DiagnosticEntry<usize>> = None;
+        let mut found: Option<DiagnosticEntryRef<usize>> = None;
         if direction == Direction::Prev {
             'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
             {
@@ -17521,7 +17521,7 @@ impl Editor {
     fn activate_diagnostics(
         &mut self,
         buffer_id: BufferId,
-        diagnostic: DiagnosticEntry<usize>,
+        diagnostic: DiagnosticEntryRef<'_, usize>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -17710,7 +17710,7 @@ impl Editor {
                             .map(|(line, _)| line)
                             .map(SharedString::new)
                             .unwrap_or_else(|| {
-                                SharedString::from(diagnostic_entry.diagnostic.message)
+                                SharedString::new(&*diagnostic_entry.diagnostic.message)
                             });
                         let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
                         let (Ok(i) | Err(i)) = inline_diagnostics

crates/editor/src/hover_popover.rs 🔗

@@ -371,7 +371,7 @@ fn show_hover(
                     this.update(cx, |_, cx| cx.observe(&markdown, |_, _, cx| cx.notify()))?;
 
                 let local_diagnostic = DiagnosticEntry {
-                    diagnostic: local_diagnostic.diagnostic,
+                    diagnostic: local_diagnostic.diagnostic.to_owned(),
                     range: snapshot
                         .buffer_snapshot
                         .anchor_before(local_diagnostic.range.start)

crates/language/src/buffer.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     DebuggerTextObject, LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag,
     TextObject, TreeSitterOptions,
-    diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
+    diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup},
     language_settings::{LanguageSettings, language_settings},
     outline::OutlineItem,
     syntax_map::{
@@ -4519,7 +4519,7 @@ impl BufferSnapshot {
         &'a self,
         search_range: Range<T>,
         reversed: bool,
-    ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
+    ) -> impl 'a + Iterator<Item = DiagnosticEntryRef<'a, O>>
     where
         T: 'a + Clone + ToOffset,
         O: 'a + FromAnchor,
@@ -4552,11 +4552,13 @@ impl BufferSnapshot {
                 })?;
             iterators[next_ix]
                 .next()
-                .map(|DiagnosticEntry { range, diagnostic }| DiagnosticEntry {
-                    diagnostic,
-                    range: FromAnchor::from_anchor(&range.start, self)
-                        ..FromAnchor::from_anchor(&range.end, self),
-                })
+                .map(
+                    |DiagnosticEntryRef { range, diagnostic }| DiagnosticEntryRef {
+                        diagnostic,
+                        range: FromAnchor::from_anchor(&range.start, self)
+                            ..FromAnchor::from_anchor(&range.end, self),
+                    },
+                )
         })
     }
 
@@ -4572,7 +4574,7 @@ impl BufferSnapshot {
     pub fn diagnostic_groups(
         &self,
         language_server_id: Option<LanguageServerId>,
-    ) -> Vec<(LanguageServerId, DiagnosticGroup<Anchor>)> {
+    ) -> Vec<(LanguageServerId, DiagnosticGroup<'_, Anchor>)> {
         let mut groups = Vec::new();
 
         if let Some(language_server_id) = language_server_id {
@@ -4603,7 +4605,7 @@ impl BufferSnapshot {
     pub fn diagnostic_group<O>(
         &self,
         group_id: usize,
-    ) -> impl Iterator<Item = DiagnosticEntry<O>> + '_
+    ) -> impl Iterator<Item = DiagnosticEntryRef<'_, O>> + use<'_, O>
     where
         O: FromAnchor + 'static,
     {

crates/language/src/diagnostic_set.rs 🔗

@@ -34,19 +34,66 @@ pub struct DiagnosticEntry<T> {
     pub diagnostic: Diagnostic,
 }
 
+/// A single diagnostic in a set. Generic over its range type, because
+/// the diagnostics are stored internally as [`Anchor`]s, but can be
+/// resolved to different coordinates types like [`usize`] byte offsets or
+/// [`Point`](gpui::Point)s.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
+pub struct DiagnosticEntryRef<'a, T> {
+    /// The range of the buffer where the diagnostic applies.
+    pub range: Range<T>,
+    /// The information about the diagnostic.
+    pub diagnostic: &'a Diagnostic,
+}
+
+impl<T: PartialEq> PartialEq<DiagnosticEntry<T>> for DiagnosticEntryRef<'_, T> {
+    fn eq(&self, other: &DiagnosticEntry<T>) -> bool {
+        self.range == other.range && *self.diagnostic == other.diagnostic
+    }
+}
+
+impl<T: PartialEq> PartialEq<DiagnosticEntryRef<'_, T>> for DiagnosticEntry<T> {
+    fn eq(&self, other: &DiagnosticEntryRef<'_, T>) -> bool {
+        self.range == other.range && self.diagnostic == *other.diagnostic
+    }
+}
+
+impl<T: Clone> DiagnosticEntryRef<'_, T> {
+    pub fn to_owned(&self) -> DiagnosticEntry<T> {
+        DiagnosticEntry {
+            range: self.range.clone(),
+            diagnostic: self.diagnostic.clone(),
+        }
+    }
+}
+
+impl<'a> DiagnosticEntryRef<'a, Anchor> {
+    /// Converts the [DiagnosticEntry] to a different buffer coordinate type.
+    pub fn resolve<O: FromAnchor>(
+        &self,
+        buffer: &text::BufferSnapshot,
+    ) -> DiagnosticEntryRef<'a, O> {
+        DiagnosticEntryRef {
+            range: O::from_anchor(&self.range.start, buffer)
+                ..O::from_anchor(&self.range.end, buffer),
+            diagnostic: &self.diagnostic,
+        }
+    }
+}
+
 /// A group of related diagnostics, ordered by their start position
 /// in the buffer.
 #[derive(Debug, Serialize)]
-pub struct DiagnosticGroup<T> {
+pub struct DiagnosticGroup<'a, T> {
     /// The diagnostics.
-    pub entries: Vec<DiagnosticEntry<T>>,
+    pub entries: Vec<DiagnosticEntryRef<'a, T>>,
     /// The index into `entries` where the primary diagnostic is stored.
     pub primary_ix: usize,
 }
 
-impl DiagnosticGroup<Anchor> {
+impl<'a> DiagnosticGroup<'a, Anchor> {
     /// Converts the entries in this [`DiagnosticGroup`] to a different buffer coordinate type.
-    pub fn resolve<O: FromAnchor>(&self, buffer: &text::BufferSnapshot) -> DiagnosticGroup<O> {
+    pub fn resolve<O: FromAnchor>(&self, buffer: &text::BufferSnapshot) -> DiagnosticGroup<'a, O> {
         DiagnosticGroup {
             entries: self
                 .entries
@@ -84,6 +131,23 @@ impl DiagnosticEntry<PointUtf16> {
         })
     }
 }
+impl DiagnosticEntryRef<'_, PointUtf16> {
+    /// Returns a raw LSP diagnostic used to provide diagnostic context to LSP
+    /// codeAction request
+    pub fn to_lsp_diagnostic_stub(&self) -> Result<lsp::Diagnostic> {
+        let range = range_to_lsp(self.range.clone())?;
+
+        Ok(lsp::Diagnostic {
+            range,
+            code: self.diagnostic.code.clone(),
+            severity: Some(self.diagnostic.severity),
+            source: self.diagnostic.source.clone(),
+            message: self.diagnostic.message.clone(),
+            data: self.diagnostic.data.clone(),
+            ..Default::default()
+        })
+    }
+}
 
 impl DiagnosticSet {
     /// Constructs a [DiagnosticSet] from a sequence of entries, ordered by
@@ -138,7 +202,7 @@ impl DiagnosticSet {
         buffer: &'a text::BufferSnapshot,
         inclusive: bool,
         reversed: bool,
-    ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
+    ) -> impl 'a + Iterator<Item = DiagnosticEntryRef<'a, O>>
     where
         T: 'a + ToOffset,
         O: FromAnchor,
@@ -179,10 +243,10 @@ impl DiagnosticSet {
     }
 
     /// Adds all of this set's diagnostic groups to the given output vector.
-    pub fn groups(
-        &self,
+    pub fn groups<'a>(
+        &'a self,
         language_server_id: LanguageServerId,
-        output: &mut Vec<(LanguageServerId, DiagnosticGroup<Anchor>)>,
+        output: &mut Vec<(LanguageServerId, DiagnosticGroup<'a, Anchor>)>,
         buffer: &text::BufferSnapshot,
     ) {
         let mut groups = HashMap::default();
@@ -190,7 +254,10 @@ impl DiagnosticSet {
             groups
                 .entry(entry.diagnostic.group_id)
                 .or_insert(Vec::new())
-                .push(entry.clone());
+                .push(DiagnosticEntryRef {
+                    range: entry.range.clone(),
+                    diagnostic: &entry.diagnostic,
+                });
         }
 
         let start_ix = output.len();
@@ -224,7 +291,7 @@ impl DiagnosticSet {
         &'a self,
         group_id: usize,
         buffer: &'a text::BufferSnapshot,
-    ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>> {
+    ) -> impl 'a + Iterator<Item = DiagnosticEntryRef<'a, O>> {
         self.iter()
             .filter(move |entry| entry.diagnostic.group_id == group_id)
             .map(|entry| entry.resolve(buffer))
@@ -247,11 +314,14 @@ impl sum_tree::Item for DiagnosticEntry<Anchor> {
 
 impl DiagnosticEntry<Anchor> {
     /// Converts the [DiagnosticEntry] to a different buffer coordinate type.
-    pub fn resolve<O: FromAnchor>(&self, buffer: &text::BufferSnapshot) -> DiagnosticEntry<O> {
-        DiagnosticEntry {
+    pub fn resolve<'a, O: FromAnchor>(
+        &'a self,
+        buffer: &text::BufferSnapshot,
+    ) -> DiagnosticEntryRef<'a, O> {
+        DiagnosticEntryRef {
             range: O::from_anchor(&self.range.start, buffer)
                 ..O::from_anchor(&self.range.end, buffer),
-            diagnostic: self.diagnostic.clone(),
+            diagnostic: &self.diagnostic,
         }
     }
 }

crates/language/src/language.rs 🔗

@@ -75,7 +75,7 @@ use util::serde::default_true;
 
 pub use buffer::Operation;
 pub use buffer::*;
-pub use diagnostic_set::{DiagnosticEntry, DiagnosticGroup};
+pub use diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup};
 pub use language_registry::{
     AvailableLanguage, BinaryStatus, LanguageNotFound, LanguageQueries, LanguageRegistry,
     QUERY_FILENAME_PREFIXES,

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -17,7 +17,7 @@ use gpui::{App, AppContext as _, Context, Entity, EntityId, EventEmitter, Task};
 use itertools::Itertools;
 use language::{
     AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharClassifier,
-    CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntry, DiskState, File,
+    CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntryRef, DiskState, File,
     IndentGuideSettings, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline,
     OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject, ToOffset as _,
     ToPoint as _, TransactionId, TreeSitterOptions, Unclipped,
@@ -6007,7 +6007,7 @@ impl MultiBufferSnapshot {
         &self,
         buffer_id: BufferId,
         group_id: usize,
-    ) -> impl Iterator<Item = DiagnosticEntry<Point>> + '_ {
+    ) -> impl Iterator<Item = DiagnosticEntryRef<'_, Point>> + '_ {
         self.lift_buffer_metadata(Point::zero()..self.max_point(), move |buffer, range| {
             if buffer.remote_id() != buffer_id {
                 return None;
@@ -6016,16 +6016,16 @@ impl MultiBufferSnapshot {
                 buffer
                     .diagnostics_in_range(range, false)
                     .filter(move |diagnostic| diagnostic.diagnostic.group_id == group_id)
-                    .map(move |DiagnosticEntry { diagnostic, range }| (range, diagnostic)),
+                    .map(move |DiagnosticEntryRef { diagnostic, range }| (range, diagnostic)),
             )
         })
-        .map(|(range, diagnostic, _)| DiagnosticEntry { diagnostic, range })
+        .map(|(range, diagnostic, _)| DiagnosticEntryRef { diagnostic, range })
     }
 
     pub fn diagnostics_in_range<'a, T>(
         &'a self,
         range: Range<T>,
-    ) -> impl Iterator<Item = DiagnosticEntry<T>> + 'a
+    ) -> impl Iterator<Item = DiagnosticEntryRef<'a, T>> + 'a
     where
         T: 'a
             + text::ToOffset
@@ -6042,13 +6042,13 @@ impl MultiBufferSnapshot {
                     .map(|entry| (entry.range, entry.diagnostic)),
             )
         })
-        .map(|(range, diagnostic, _)| DiagnosticEntry { diagnostic, range })
+        .map(|(range, diagnostic, _)| DiagnosticEntryRef { diagnostic, range })
     }
 
     pub fn diagnostics_with_buffer_ids_in_range<'a, T>(
         &'a self,
         range: Range<T>,
-    ) -> impl Iterator<Item = (BufferId, DiagnosticEntry<T>)> + 'a
+    ) -> impl Iterator<Item = (BufferId, DiagnosticEntryRef<'a, T>)> + 'a
     where
         T: 'a
             + text::ToOffset
@@ -6065,7 +6065,7 @@ impl MultiBufferSnapshot {
                     .map(|entry| (entry.range, entry.diagnostic)),
             )
         })
-        .map(|(range, diagnostic, b)| (b.buffer_id, DiagnosticEntry { diagnostic, range }))
+        .map(|(range, diagnostic, b)| (b.buffer_id, DiagnosticEntryRef { diagnostic, range }))
     }
 
     pub fn syntax_ancestor<T: ToOffset>(

crates/project/src/project_tests.rs 🔗

@@ -20,9 +20,10 @@ use git2::RepositoryInitOptions;
 use gpui::{App, BackgroundExecutor, SemanticVersion, UpdateGlobal};
 use itertools::Itertools;
 use language::{
-    Diagnostic, DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, DiskState, FakeLspAdapter,
-    LanguageConfig, LanguageMatcher, LanguageName, LineEnding, ManifestName, ManifestProvider,
-    ManifestQuery, OffsetRangeExt, Point, ToPoint, ToolchainList, ToolchainLister,
+    Diagnostic, DiagnosticEntry, DiagnosticEntryRef, DiagnosticSet, DiagnosticSourceKind,
+    DiskState, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageName, LineEnding,
+    ManifestName, ManifestProvider, ManifestQuery, OffsetRangeExt, Point, ToPoint, ToolchainList,
+    ToolchainLister,
     language_settings::{LanguageSettingsContent, language_settings},
     tree_sitter_rust, tree_sitter_typescript,
 };
@@ -1847,9 +1848,9 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
             .collect::<Vec<_>>();
         assert_eq!(
             diagnostics,
-            &[DiagnosticEntry {
+            &[DiagnosticEntryRef {
                 range: Point::new(0, 9)..Point::new(0, 10),
-                diagnostic: Diagnostic {
+                diagnostic: &Diagnostic {
                     severity: lsp::DiagnosticSeverity::ERROR,
                     message: "undefined variable 'A'".to_string(),
                     group_id: 0,
@@ -2024,7 +2025,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
             buffer
                 .snapshot()
                 .diagnostics_in_range::<_, usize>(0..1, false)
-                .map(|entry| entry.diagnostic.message)
+                .map(|entry| entry.diagnostic.message.clone())
                 .collect::<Vec<_>>(),
             ["the message".to_string()]
         );
@@ -2050,7 +2051,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
             buffer
                 .snapshot()
                 .diagnostics_in_range::<_, usize>(0..1, false)
-                .map(|entry| entry.diagnostic.message)
+                .map(|entry| entry.diagnostic.message.clone())
                 .collect::<Vec<_>>(),
             Vec::<String>::new(),
         );

crates/workspace/src/status_bar.rs 🔗

@@ -9,6 +9,7 @@ use ui::{h_flex, prelude::*};
 use util::ResultExt;
 
 pub trait StatusItemView: Render {
+    /// Event callback that is triggered when the active pane item changes.
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,