Deduplicate inlay hints queries with buffer versions

Kirill Bulatov created

Change summary

crates/editor/src/display_map.rs      |  1 
crates/editor/src/editor.rs           | 10 ++++--
crates/editor/src/inlay_hint_cache.rs | 42 +++++++++++++++++++++++++---
3 files changed, 43 insertions(+), 10 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -255,7 +255,6 @@ impl DisplayMap {
         if to_remove.is_empty() && to_insert.is_empty() {
             return;
         }
-
         let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
         let edits = self.buffer_subscription.consume().into_inner();
         let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);

crates/editor/src/editor.rs 🔗

@@ -54,7 +54,7 @@ use gpui::{
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
-use inlay_hint_cache::{visible_inlay_hints, InlayHintCache, InlaySplice};
+use inlay_hint_cache::{visible_inlay_hints, InlayHintCache, InlaySplice, InvalidationStrategy};
 pub use items::MAX_TAB_TITLE_LEN;
 use itertools::Itertools;
 pub use language::{char_kind, CharKind};
@@ -1198,6 +1198,7 @@ enum InlayRefreshReason {
     SettingsChange(editor_settings::InlayHints),
     NewLinesShown,
     ExcerptEdited,
+    RefreshRequested,
 }
 
 impl Editor {
@@ -1311,7 +1312,7 @@ impl Editor {
                 }
                 project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| {
                     if let project::Event::RefreshInlays = event {
-                        editor.refresh_inlays(InlayRefreshReason::ExcerptEdited, cx);
+                        editor.refresh_inlays(InlayRefreshReason::RefreshRequested, cx);
                     };
                 }));
             }
@@ -2629,8 +2630,9 @@ impl Editor {
                 }
                 return;
             }
-            InlayRefreshReason::NewLinesShown => false,
-            InlayRefreshReason::ExcerptEdited => true,
+            InlayRefreshReason::NewLinesShown => InvalidationStrategy::None,
+            InlayRefreshReason::ExcerptEdited => InvalidationStrategy::OnConflict,
+            InlayRefreshReason::RefreshRequested => InvalidationStrategy::All,
         };
 
         let excerpts_to_query = self

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -5,6 +5,7 @@ use crate::{
     MultiBufferSnapshot,
 };
 use anyhow::Context;
+use clock::Global;
 use gpui::{ModelHandle, Task, ViewContext};
 use language::{Buffer, BufferSnapshot};
 use log::error;
@@ -31,6 +32,7 @@ struct CacheSnapshot {
 
 struct CachedExcerptHints {
     version: usize,
+    buffer_version: Global,
     hints: Vec<(InlayId, InlayHint)>,
 }
 
@@ -41,7 +43,14 @@ struct ExcerptQuery {
     excerpt_range_start: language::Anchor,
     excerpt_range_end: language::Anchor,
     cache_version: usize,
-    invalidate_cache: bool,
+    invalidate: InvalidationStrategy,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum InvalidationStrategy {
+    All,
+    OnConflict,
+    None,
 }
 
 #[derive(Debug, Default)]
@@ -116,10 +125,14 @@ impl InlayHintCache {
     pub fn spawn_hints_update(
         &mut self,
         mut excerpts_to_query: HashMap<ExcerptId, ModelHandle<Buffer>>,
-        invalidate_cache: bool,
+        invalidate: InvalidationStrategy,
         cx: &mut ViewContext<Editor>,
     ) {
         let update_tasks = &mut self.update_tasks;
+        let invalidate_cache = matches!(
+            invalidate,
+            InvalidationStrategy::All | InvalidationStrategy::OnConflict
+        );
         if invalidate_cache {
             update_tasks
                 .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
@@ -179,7 +192,7 @@ impl InlayHintCache {
                                 excerpt_range_start: excerpt_range.start,
                                 excerpt_range_end: excerpt_range.end,
                                 cache_version,
-                                invalidate_cache,
+                                invalidate,
                             };
                             let cached_excxerpt_hints = editor
                                 .inlay_hint_cache
@@ -187,6 +200,20 @@ impl InlayHintCache {
                                 .hints
                                 .get(&excerpt_id)
                                 .cloned();
+
+                            if let Some(cached_excerpt_hints) = &cached_excxerpt_hints {
+                                let new_task_buffer_version = buffer_snapshot.version();
+                                let cached_buffer_version = &cached_excerpt_hints.buffer_version;
+                                if cached_buffer_version.changed_since(new_task_buffer_version) {
+                                    return;
+                                }
+                                if !new_task_buffer_version.changed_since(&cached_buffer_version)
+                                    && !matches!(invalidate, InvalidationStrategy::All)
+                                {
+                                    return;
+                                }
+                            }
+
                             editor.inlay_hint_cache.update_tasks.insert(
                                 excerpt_id,
                                 new_update_task(
@@ -252,6 +279,7 @@ fn new_update_task(
                                     .or_insert_with(|| {
                                         Arc::new(CachedExcerptHints {
                                             version: new_update.cache_version,
+                                            buffer_version: buffer_snapshot.version().clone(),
                                             hints: Vec::new(),
                                         })
                                     });
@@ -267,7 +295,8 @@ fn new_update_task(
                                 cached_excerpt_hints.hints.retain(|(hint_id, _)| {
                                     !new_update.remove_from_cache.contains(hint_id)
                                 });
-
+                                cached_excerpt_hints.buffer_version =
+                                    buffer_snapshot.version().clone();
                                 editor.inlay_hint_cache.snapshot.version += 1;
 
                                 let mut splice = InlaySplice {
@@ -457,7 +486,10 @@ fn new_excerpt_hints_update_result(
 
     let mut remove_from_visible = Vec::new();
     let mut remove_from_cache = HashSet::default();
-    if query.invalidate_cache {
+    if matches!(
+        query.invalidate,
+        InvalidationStrategy::All | InvalidationStrategy::OnConflict
+    ) {
         remove_from_visible.extend(
             visible_hints
                 .iter()