Spawn cache updates in separate tasks

Kirill Bulatov created

Change summary

crates/editor/src/editor.rs           |  85 -
crates/editor/src/inlay_hint_cache.rs | 892 ++++++++++++++++++++--------
2 files changed, 651 insertions(+), 326 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -24,7 +24,7 @@ pub mod test;
 
 use ::git::diff::DiffHunk;
 use aho_corasick::AhoCorasick;
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Result};
 use blink_manager::BlinkManager;
 use client::{ClickhouseEvent, TelemetrySettings};
 use clock::ReplicaId;
@@ -54,7 +54,7 @@ use gpui::{
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
-use inlay_hint_cache::{InlayHintCache, InlayHintQuery, InlayRefreshReason, InlaySplice};
+use inlay_hint_cache::{InlayHintCache, InlayHintQuery};
 pub use items::MAX_TAB_TITLE_LEN;
 use itertools::Itertools;
 pub use language::{char_kind, CharKind};
@@ -1193,6 +1193,12 @@ enum GotoDefinitionKind {
     Type,
 }
 
+#[derive(Debug, Copy, Clone)]
+enum InlayRefreshReason {
+    SettingsChange(editor_settings::InlayHints),
+    VisibleExcerptsChange,
+}
+
 impl Editor {
     pub fn single_line(
         field_editor_style: Option<Arc<GetFieldEditorTheme>>,
@@ -1360,7 +1366,10 @@ impl Editor {
             hover_state: Default::default(),
             link_go_to_definition_state: Default::default(),
             copilot_state: Default::default(),
-            inlay_hint_cache: InlayHintCache::new(settings::get::<EditorSettings>(cx).inlay_hints),
+            inlay_hint_cache: InlayHintCache::new(
+                settings::get::<EditorSettings>(cx).inlay_hints,
+                cx,
+            ),
             gutter_hovered: false,
             _subscriptions: vec![
                 cx.observe(&buffer, Self::on_buffer_changed),
@@ -2605,40 +2614,16 @@ impl Editor {
 
         let multi_buffer_handle = self.buffer().clone();
         let multi_buffer_snapshot = multi_buffer_handle.read(cx).snapshot(cx);
-        let currently_shown_inlay_hints = self.display_map.read(cx).current_inlays().fold(
-            HashMap::<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>::default(),
-            |mut current_hints, inlay| {
-                if let Some(buffer_id) = inlay.position.buffer_id {
-                    let excerpt_hints = current_hints
-                        .entry(buffer_id)
-                        .or_default()
-                        .entry(inlay.position.excerpt_id)
-                        .or_default();
-                    match excerpt_hints.binary_search_by(|probe| {
-                        inlay.position.cmp(&probe.0, &multi_buffer_snapshot)
-                    }) {
-                        Ok(ix) | Err(ix) => {
-                            excerpt_hints.insert(ix, (inlay.position, inlay.id));
-                        }
-                    }
-                }
-                current_hints
-            },
-        );
+        let current_inlays = self
+            .display_map
+            .read(cx)
+            .current_inlays()
+            .cloned()
+            .collect();
         match reason {
-            InlayRefreshReason::SettingsChange(new_settings) => {
-                if let Some(InlaySplice {
-                    to_remove,
-                    to_insert,
-                }) = self.inlay_hint_cache.apply_settings(
-                    &multi_buffer_handle,
-                    new_settings,
-                    currently_shown_inlay_hints,
-                    cx,
-                ) {
-                    self.splice_inlay_hints(to_remove, to_insert, cx);
-                }
-            }
+            InlayRefreshReason::SettingsChange(new_settings) => self
+                .inlay_hint_cache
+                .spawn_settings_update(multi_buffer_handle, new_settings, current_inlays),
             InlayRefreshReason::VisibleExcerptsChange => {
                 let replacement_queries = self
                     .excerpt_visible_offsets(&multi_buffer_handle, cx)
@@ -2652,28 +2637,12 @@ impl Editor {
                         }
                     })
                     .collect::<Vec<_>>();
-                cx.spawn(|editor, mut cx| async move {
-                    let InlaySplice {
-                        to_remove,
-                        to_insert,
-                    } = editor
-                        .update(&mut cx, |editor, cx| {
-                            editor.inlay_hint_cache.update_hints(
-                                multi_buffer_handle,
-                                replacement_queries,
-                                currently_shown_inlay_hints,
-                                cx,
-                            )
-                        })?
-                        .await
-                        .context("inlay cache hint fetch")?;
-
-                    editor.update(&mut cx, |editor, cx| {
-                        editor.splice_inlay_hints(to_remove, to_insert, cx)
-                    })
-                })
-                // TODO kb needs cancellation for many excerpts cases like `project search "test"`
-                .detach_and_log_err(cx);
+                self.inlay_hint_cache.spawn_hints_update(
+                    multi_buffer_handle,
+                    replacement_queries,
+                    current_inlays,
+                    cx,
+                )
             }
         };
     }

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -1,26 +1,24 @@
 use std::cmp;
 
-use crate::{editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer};
+use crate::{
+    display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer,
+    MultiBufferSnapshot,
+};
 use anyhow::Context;
 use clock::Global;
 use gpui::{ModelHandle, Task, ViewContext};
 use log::error;
 use project::{InlayHint, InlayHintKind};
 
-use collections::{HashMap, HashSet};
+use collections::{hash_map, HashMap, HashSet};
 use util::post_inc;
 
-#[derive(Debug, Copy, Clone)]
-pub enum InlayRefreshReason {
-    SettingsChange(editor_settings::InlayHints),
-    VisibleExcerptsChange,
-}
-
-#[derive(Debug, Default)]
+#[derive(Debug)]
 pub struct InlayHintCache {
     inlay_hints: HashMap<InlayId, InlayHint>,
     hints_in_buffers: HashMap<u64, BufferHints<(Anchor, InlayId)>>,
     allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
+    hint_updates_tx: smol::channel::Sender<HintsUpdate>,
 }
 
 #[derive(Clone, Debug)]
@@ -29,6 +27,13 @@ struct BufferHints<H> {
     hints_per_excerpt: HashMap<ExcerptId, Vec<H>>,
 }
 
+#[derive(Debug)]
+pub struct InlayHintQuery {
+    pub buffer_id: u64,
+    pub buffer_version: Global,
+    pub excerpt_id: ExcerptId,
+}
+
 impl<H> BufferHints<H> {
     fn new(buffer_version: Global) -> Self {
         Self {
@@ -38,146 +43,67 @@ impl<H> BufferHints<H> {
     }
 }
 
-#[derive(Debug, Default)]
-pub struct InlaySplice {
-    pub to_remove: Vec<InlayId>,
-    pub to_insert: Vec<(InlayId, Anchor, InlayHint)>,
-}
-
-#[derive(Debug)]
-pub struct InlayHintQuery {
-    pub buffer_id: u64,
-    pub buffer_version: Global,
-    pub excerpt_id: ExcerptId,
-}
-
 impl InlayHintCache {
-    pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self {
+    pub fn new(
+        inlay_hint_settings: editor_settings::InlayHints,
+        cx: &mut ViewContext<Editor>,
+    ) -> Self {
+        let (hint_updates_tx, hint_updates_rx) = smol::channel::unbounded();
+        spawn_hints_update_loop(hint_updates_rx, cx);
         Self {
             allowed_hint_kinds: allowed_hint_types(inlay_hint_settings),
             hints_in_buffers: HashMap::default(),
             inlay_hints: HashMap::default(),
+            hint_updates_tx,
         }
     }
 
-    pub fn apply_settings(
+    pub fn spawn_settings_update(
         &mut self,
-        multi_buffer: &ModelHandle<MultiBuffer>,
+        multi_buffer: ModelHandle<MultiBuffer>,
         inlay_hint_settings: editor_settings::InlayHints,
-        currently_shown_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
-        cx: &mut ViewContext<Editor>,
-    ) -> Option<InlaySplice> {
+        current_inlays: Vec<Inlay>,
+    ) {
         if !inlay_hint_settings.enabled {
             self.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings);
             if self.inlay_hints.is_empty() {
-                return None;
+                return;
             } else {
-                let to_remove = self.inlay_hints.keys().copied().collect();
-                self.inlay_hints.clear();
-                self.hints_in_buffers.clear();
-                return Some(InlaySplice {
-                    to_remove,
-                    to_insert: Vec::new(),
-                });
+                self.hint_updates_tx
+                    .send_blocking(HintsUpdate {
+                        multi_buffer,
+                        current_inlays,
+                        kind: HintsUpdateKind::Clean,
+                    })
+                    .ok();
+                return;
             }
         }
 
         let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings);
         if new_allowed_hint_kinds == self.allowed_hint_kinds {
-            None
-        } else {
-            let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
-            let mut to_remove = Vec::new();
-            let mut to_insert = Vec::new();
-            let mut shown_hints_to_remove = currently_shown_hints;
-
-            // TODO kb move into a background task
-            for (buffer_id, cached_buffer_hints) in &self.hints_in_buffers {
-                let shown_buffer_hints_to_remove =
-                    shown_hints_to_remove.entry(*buffer_id).or_default();
-                for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt {
-                    let shown_excerpt_hints_to_remove =
-                        shown_buffer_hints_to_remove.entry(*excerpt_id).or_default();
-                    let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable();
-                    shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
-                        loop {
-                            match cached_hints.peek() {
-                                Some((cached_anchor, cached_hint_id)) => {
-                                    if cached_hint_id == shown_hint_id {
-                                        return !new_allowed_hint_kinds.contains(
-                                            &self.inlay_hints.get(&cached_hint_id).unwrap().kind,
-                                        );
-                                    }
-
-                                    match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) {
-                                        cmp::Ordering::Less | cmp::Ordering::Equal => {
-                                            let maybe_missed_cached_hint =
-                                                self.inlay_hints.get(&cached_hint_id).unwrap();
-                                            let cached_hint_kind = maybe_missed_cached_hint.kind;
-                                            if !self.allowed_hint_kinds.contains(&cached_hint_kind)
-                                                && new_allowed_hint_kinds
-                                                    .contains(&cached_hint_kind)
-                                            {
-                                                to_insert.push((
-                                                    *cached_hint_id,
-                                                    *cached_anchor,
-                                                    maybe_missed_cached_hint.clone(),
-                                                ));
-                                            }
-                                            cached_hints.next();
-                                        }
-                                        cmp::Ordering::Greater => break,
-                                    }
-                                }
-                                None => return true,
-                            }
-                        }
-
-                        match self.inlay_hints.get(&shown_hint_id) {
-                            Some(shown_hint) => !new_allowed_hint_kinds.contains(&shown_hint.kind),
-                            None => true,
-                        }
-                    });
-
-                    for (cached_anchor, cached_hint_id) in cached_hints {
-                        let maybe_missed_cached_hint =
-                            self.inlay_hints.get(&cached_hint_id).unwrap();
-                        let cached_hint_kind = maybe_missed_cached_hint.kind;
-                        if !self.allowed_hint_kinds.contains(&cached_hint_kind)
-                            && new_allowed_hint_kinds.contains(&cached_hint_kind)
-                        {
-                            to_insert.push((
-                                *cached_hint_id,
-                                *cached_anchor,
-                                maybe_missed_cached_hint.clone(),
-                            ));
-                        }
-                    }
-                }
-            }
+            return;
+        }
 
-            to_remove.extend(
-                shown_hints_to_remove
-                    .into_iter()
-                    .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt)
-                    .flat_map(|(_, excerpt_hints)| excerpt_hints)
-                    .map(|(_, hint_id)| hint_id),
-            );
-            self.allowed_hint_kinds = new_allowed_hint_kinds;
-            Some(InlaySplice {
-                to_remove,
-                to_insert,
+        self.hint_updates_tx
+            .send_blocking(HintsUpdate {
+                multi_buffer,
+                current_inlays,
+                kind: HintsUpdateKind::AllowedHintKindsChanged {
+                    old: self.allowed_hint_kinds.clone(),
+                    new: new_allowed_hint_kinds,
+                },
             })
-        }
+            .ok();
     }
 
-    pub fn update_hints(
+    pub fn spawn_hints_update(
         &mut self,
         multi_buffer: ModelHandle<MultiBuffer>,
         queries: Vec<InlayHintQuery>,
-        currently_shown_hints: HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>,
+        current_inlays: Vec<Inlay>,
         cx: &mut ViewContext<Editor>,
-    ) -> Task<anyhow::Result<InlaySplice>> {
+    ) {
         let conflicts_with_cache = queries.iter().any(|update_query| {
             let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id)
                 else { return false };
@@ -198,8 +124,7 @@ impl InlayHintCache {
             }
         });
 
-        // TODO kb remember queries that run and do not query for these ranges if the buffer version was not changed
-        let queries = queries
+        let queries_per_buffer = queries
             .into_iter()
             .filter_map(|query| {
                 let Some(cached_buffer_hints) = self.hints_in_buffers.get(&query.buffer_id)
@@ -220,167 +145,410 @@ impl InlayHintCache {
                     None
                 }
             })
-            .collect::<Vec<_>>();
-        let task_multi_buffer = multi_buffer.clone();
-        let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx);
-        let mut to_remove = Vec::new();
-        let mut to_insert = Vec::new();
-        let mut cache_hints_to_persist: HashMap<
-            u64,
-            (Global, HashMap<ExcerptId, HashSet<InlayId>>),
-        > = HashMap::default();
-        cx.spawn(|editor, mut cx| async move {
-            let new_hints = fetch_queries_task.await.context("inlay hints fetch")?;
-            editor.update(&mut cx, |editor, cx| {
-                let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx);
-                for (new_buffer_id, new_hints_per_buffer) in new_hints {
-                    let cached_buffer_hints = editor
-                        .inlay_hint_cache
-                        .hints_in_buffers
-                        .entry(new_buffer_id)
-                        .or_insert_with(|| {
-                            BufferHints::new(new_hints_per_buffer.buffer_version.clone())
-                        });
+            .fold(
+                HashMap::<u64, (Global, Vec<ExcerptId>)>::default(),
+                |mut queries_per_buffer, new_query| {
+                    let (current_verison, excerpts_to_query) =
+                        queries_per_buffer.entry(new_query.buffer_id).or_default();
 
-                    let buffer_cache_hints_to_persist =
-                        cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default()));
-                    if cached_buffer_hints
-                        .buffer_version
-                        .changed_since(&new_hints_per_buffer.buffer_version)
-                    {
-                        buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version;
-                        buffer_cache_hints_to_persist.1.extend(
-                            cached_buffer_hints.hints_per_excerpt.iter().map(
-                                |(excerpt_id, excerpt_hints)| {
-                                    (
-                                        *excerpt_id,
-                                        excerpt_hints.iter().map(|(_, id)| *id).collect(),
-                                    )
-                                },
-                            ),
-                        );
-                        continue;
+                    if new_query.buffer_version.changed_since(current_verison) {
+                        *current_verison = new_query.buffer_version;
+                        *excerpts_to_query = vec![new_query.excerpt_id];
+                    } else if !current_verison.changed_since(&new_query.buffer_version) {
+                        excerpts_to_query.push(new_query.excerpt_id);
                     }
 
-                    let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id);
-                    for (new_excerpt_id, new_hints_per_excerpt) in
-                        new_hints_per_buffer.hints_per_excerpt
-                    {
-                        let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1
-                            .entry(new_excerpt_id)
-                            .or_default();
-                        let cached_excerpt_hints = cached_buffer_hints
-                            .hints_per_excerpt
-                            .entry(new_excerpt_id)
-                            .or_default();
-                        let empty_shown_excerpt_hints = Vec::new();
-                        let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints);
-                        for new_hint in new_hints_per_excerpt {
-                            let new_hint_anchor = multi_buffer_snapshot
-                                .anchor_in_excerpt(new_excerpt_id, new_hint.position);
-                            let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| {
-                                new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)
-                            }) {
-                                Ok(ix) => {
-                                    let (_, cached_inlay_id) = cached_excerpt_hints[ix];
-                                    let cache_hit = editor
-                                        .inlay_hint_cache
-                                        .inlay_hints
-                                        .get(&cached_inlay_id)
-                                        .filter(|cached_hint| cached_hint == &&new_hint)
-                                        .is_some();
-                                    if cache_hit {
-                                        excerpt_cache_hints_to_persist
-                                            .insert(cached_inlay_id);
-                                        None
-                                    } else {
-                                        Some(ix)
-                                    }
-                                }
-                                Err(ix) => Some(ix),
-                            };
-
-                            let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| {
-                                probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot)
-                            }) {
-                                Ok(ix) => {{
-                                    let (_, shown_inlay_id) = shown_excerpt_hints[ix];
-                                    let shown_hint_found =  editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id)
-                                        .filter(|cached_hint| cached_hint == &&new_hint).is_some();
-                                    if shown_hint_found {
-                                        Some(shown_inlay_id)
-                                    } else {
-                                        None
-                                    }
-                                }},
-                                Err(_) => None,
-                            };
-
-                            if let Some(insert_ix) = cache_insert_ix {
-                                let hint_id = match shown_inlay_id {
-                                    Some(shown_inlay_id) => shown_inlay_id,
-                                    None => {
-                                        let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id));
-                                        if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind)
-                                        {
-                                            to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone()));
-                                        }
-                                        new_hint_id
-                                    }
-                                };
-                                excerpt_cache_hints_to_persist.insert(hint_id);
-                                cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id));
-                                editor
-                                    .inlay_hint_cache
-                                    .inlay_hints
-                                    .insert(hint_id, new_hint);
-                            }
+                    queries_per_buffer
+                },
+            );
+
+        for (queried_buffer, (buffer_version, excerpts)) in queries_per_buffer {
+            self.hint_updates_tx
+                .send_blocking(HintsUpdate {
+                    multi_buffer,
+                    current_inlays,
+                    kind: HintsUpdateKind::BufferUpdate {
+                        invalidate_cache: conflicts_with_cache,
+                        buffer_id: queried_buffer,
+                        buffer_version,
+                        excerpts,
+                    },
+                })
+                .ok();
+        }
+    }
+}
+
+#[derive(Debug, Default)]
+struct InlaySplice {
+    to_remove: Vec<InlayId>,
+    to_insert: Vec<(InlayId, Anchor, InlayHint)>,
+}
+
+struct HintsUpdate {
+    multi_buffer: ModelHandle<MultiBuffer>,
+    current_inlays: Vec<Inlay>,
+    kind: HintsUpdateKind,
+}
+
+enum HintsUpdateKind {
+    Clean,
+    AllowedHintKindsChanged {
+        old: HashSet<Option<InlayHintKind>>,
+        new: HashSet<Option<InlayHintKind>>,
+    },
+    BufferUpdate {
+        buffer_id: u64,
+        buffer_version: Global,
+        excerpts: Vec<ExcerptId>,
+        invalidate_cache: bool,
+    },
+}
+
+struct UpdateTaskHandle {
+    multi_buffer: ModelHandle<MultiBuffer>,
+    cancellation_tx: smol::channel::Sender<()>,
+    task_finish_rx: smol::channel::Receiver<UpdateTaskResult>,
+}
+
+struct UpdateTaskResult {
+    multi_buffer: ModelHandle<MultiBuffer>,
+    splice: InlaySplice,
+    new_allowed_hint_kinds: Option<HashSet<Option<InlayHintKind>>>,
+    remove_from_cache: HashSet<InlayId>,
+    add_to_cache: HashMap<u64, BufferHints<(Anchor, InlayHint, InlayId)>>,
+}
+
+impl HintsUpdate {
+    fn merge(&mut self, mut other: Self) -> Result<(), Self> {
+        match (&mut self.kind, &mut other.kind) {
+            (HintsUpdateKind::Clean, HintsUpdateKind::Clean) => return Ok(()),
+            (
+                HintsUpdateKind::AllowedHintKindsChanged { .. },
+                HintsUpdateKind::AllowedHintKindsChanged { .. },
+            ) => {
+                *self = other;
+                return Ok(());
+            }
+            (
+                HintsUpdateKind::BufferUpdate {
+                    buffer_id: old_buffer_id,
+                    buffer_version: old_buffer_version,
+                    excerpts: old_excerpts,
+                    invalidate_cache: old_invalidate_cache,
+                },
+                HintsUpdateKind::BufferUpdate {
+                    buffer_id: new_buffer_id,
+                    buffer_version: new_buffer_version,
+                    excerpts: new_excerpts,
+                    invalidate_cache: new_invalidate_cache,
+                },
+            ) => {
+                if old_buffer_id == new_buffer_id {
+                    if new_buffer_version.changed_since(old_buffer_version) {
+                        *self = other;
+                        return Ok(());
+                    } else if old_buffer_version.changed_since(new_buffer_version) {
+                        return Ok(());
+                    } else if *new_invalidate_cache {
+                        *self = other;
+                        return Ok(());
+                    } else {
+                        let old_inlays = self
+                            .current_inlays
+                            .iter()
+                            .map(|inlay| inlay.id)
+                            .collect::<Vec<_>>();
+                        let new_inlays = other
+                            .current_inlays
+                            .iter()
+                            .map(|inlay| inlay.id)
+                            .collect::<Vec<_>>();
+                        if old_inlays == new_inlays {
+                            old_excerpts.extend(new_excerpts.drain(..));
+                            old_excerpts.dedup();
+                            return Ok(());
                         }
                     }
                 }
+            }
+            _ => {}
+        }
+
+        Err(other)
+    }
+
+    fn spawn(self, cx: &mut ViewContext<'_, '_, Editor>) -> UpdateTaskHandle {
+        let (task_finish_tx, task_finish_rx) = smol::channel::unbounded();
+        let (cancellation_tx, cancellation_rx) = smol::channel::bounded(1);
+
+        match self.kind {
+            HintsUpdateKind::Clean => cx
+                .spawn(|editor, mut cx| async move {
+                    if let Some(splice) = editor.update(&mut cx, |editor, cx| {
+                        clean_cache(editor, self.current_inlays)
+                    })? {
+                        task_finish_tx
+                            .send(UpdateTaskResult {
+                                multi_buffer: self.multi_buffer.clone(),
+                                splice,
+                                new_allowed_hint_kinds: None,
+                                remove_from_cache: HashSet::default(),
+                                add_to_cache: HashMap::default(),
+                            })
+                            .await
+                            .ok();
+                    }
+                    anyhow::Ok(())
+                })
+                .detach_and_log_err(cx),
+            HintsUpdateKind::AllowedHintKindsChanged { old, new } => cx
+                .spawn(|editor, mut cx| async move {
+                    if let Some(splice) = editor.update(&mut cx, |editor, cx| {
+                        update_allowed_hint_kinds(
+                            &self.multi_buffer.read(cx).snapshot(cx),
+                            self.current_inlays,
+                            old,
+                            new,
+                            editor,
+                        )
+                    })? {
+                        task_finish_tx
+                            .send(UpdateTaskResult {
+                                multi_buffer: self.multi_buffer.clone(),
+                                splice,
+                                new_allowed_hint_kinds: None,
+                                remove_from_cache: HashSet::default(),
+                                add_to_cache: HashMap::default(),
+                            })
+                            .await
+                            .ok();
+                    }
+                    anyhow::Ok(())
+                })
+                .detach_and_log_err(cx),
+            HintsUpdateKind::BufferUpdate {
+                buffer_id,
+                buffer_version,
+                excerpts,
+                invalidate_cache,
+            } => todo!("TODO kb"),
+        }
 
-                if conflicts_with_cache {
-                    for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints {
-                        match cache_hints_to_persist.get(&shown_buffer_id) {
-                            Some(cached_buffer_hints) => {
-                                for (persisted_id, cached_hints) in &cached_buffer_hints.1 {
-                                    shown_hints_to_clean.entry(*persisted_id).or_default()
-                                        .retain(|(_, shown_id)| !cached_hints.contains(shown_id));
+        UpdateTaskHandle {
+            multi_buffer: self.multi_buffer.clone(),
+            cancellation_tx,
+            task_finish_rx,
+        }
+    }
+}
+
+fn spawn_hints_update_loop(
+    hint_updates_rx: smol::channel::Receiver<HintsUpdate>,
+    cx: &mut ViewContext<'_, '_, Editor>,
+) {
+    cx.spawn(|editor, mut cx| async move {
+        let mut update = None::<HintsUpdate>;
+        let mut next_update = None::<HintsUpdate>;
+        loop {
+            if update.is_none() {
+                match hint_updates_rx.recv().await {
+                    Ok(first_task) => update = Some(first_task),
+                    Err(smol::channel::RecvError) => return,
+                }
+            }
+
+            let mut updates_limit = 10;
+            'update_merge: loop {
+                match hint_updates_rx.try_recv() {
+                    Ok(new_update) => {
+                        match update.as_mut() {
+                            Some(update) => match update.merge(new_update) {
+                                Ok(()) => {}
+                                Err(new_update) => {
+                                    next_update = Some(new_update);
+                                    break 'update_merge;
                                 }
                             },
-                            None => {},
+                            None => update = Some(new_update),
+                        };
+
+                        if updates_limit == 0 {
+                            break 'update_merge;
                         }
-                        to_remove.extend(shown_hints_to_clean.into_iter()
-                            .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id)));
+                        updates_limit -= 1;
                     }
+                    Err(smol::channel::TryRecvError::Empty) => break 'update_merge,
+                    Err(smol::channel::TryRecvError::Closed) => return,
+                }
+            }
 
-                    editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| {
-                        let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; };
-                        buffer_hints.buffer_version = buffer_hints_to_persist.0;
-                        buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| {
-                            let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; };
-                            excerpt_hints.retain(|(_, hint_id)| {
-                                let retain = excerpt_hints_to_persist.contains(hint_id);
-                                if !retain {
-                                    editor
-                                        .inlay_hint_cache
-                                        .inlay_hints
-                                        .remove(hint_id);
-                                }
-                                retain
+            if let Some(update) = update.take() {
+                let Ok(task_handle) = editor.update(&mut cx, |_, cx| update.spawn(cx)) else { return; };
+                while let Ok(update_task_result) = task_handle.task_finish_rx.recv().await {
+                    let Ok(()) = editor.update(&mut cx, |editor, cx| {
+                        let multi_buffer_snapshot = update_task_result.multi_buffer.read(cx).snapshot(cx);
+                        let inlay_hint_cache = &mut editor.inlay_hint_cache;
+
+                        if let Some(new_allowed_hint_kinds) = update_task_result.new_allowed_hint_kinds {
+                            inlay_hint_cache.allowed_hint_kinds = new_allowed_hint_kinds;
+                        }
+
+                        inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| {
+                            buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| {
+                                excerpt_hints.retain(|(_, hint_id)| !update_task_result.remove_from_cache.contains(hint_id));
+                                !excerpt_hints.is_empty()
                             });
-                            !excerpt_hints.is_empty()
+                            !buffer_hints.hints_per_excerpt.is_empty()
                         });
-                        !buffer_hints.hints_per_excerpt.is_empty()
-                    });
+                        inlay_hint_cache.inlay_hints.retain(|hint_id, _| !update_task_result.remove_from_cache.contains(hint_id));
+
+                        for (new_buffer_id, new_buffer_inlays) in update_task_result.add_to_cache {
+                            let cached_buffer_hints = inlay_hint_cache.hints_in_buffers.entry(new_buffer_id).or_insert_with(|| BufferHints::new(new_buffer_inlays.buffer_version));
+                            if cached_buffer_hints.buffer_version.changed_since(&new_buffer_inlays.buffer_version) {
+                                continue;
+                            }
+                            for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays.hints_per_excerpt {
+                                let cached_excerpt_hints = cached_buffer_hints.hints_per_excerpt.entry(excerpt_id).or_default();
+                                for (new_hint_position, new_hint, new_inlay_id) in new_excerpt_inlays {
+                                    if let hash_map::Entry::Vacant(v) = inlay_hint_cache.inlay_hints.entry(new_inlay_id) {
+                                        v.insert(new_hint);
+                                        match cached_excerpt_hints.binary_search_by(|probe| {
+                                            new_hint_position.cmp(&probe.0, &multi_buffer_snapshot)
+                                        }) {
+                                            Ok(ix) | Err(ix) => cached_excerpt_hints.insert(ix, (new_hint_position, new_inlay_id)),
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        let InlaySplice {
+                            to_remove,
+                            to_insert,
+                        } = update_task_result.splice;
+                        editor.splice_inlay_hints(to_remove, to_insert, cx)
+                    }) else { return; };
                 }
+            }
+            update = next_update.take();
+        }
+    })
+    .detach()
+}
+
+fn update_allowed_hint_kinds(
+    multi_buffer_snapshot: &MultiBufferSnapshot,
+    current_inlays: Vec<Inlay>,
+    old_kinds: HashSet<Option<InlayHintKind>>,
+    new_kinds: HashSet<Option<InlayHintKind>>,
+    editor: &mut Editor,
+) -> Option<InlaySplice> {
+    if old_kinds == new_kinds {
+        return None;
+    }
 
-                InlaySplice {
-                    to_remove,
-                    to_insert,
+    let mut to_remove = Vec::new();
+    let mut to_insert = Vec::new();
+    let mut shown_hints_to_remove = group_inlays(&multi_buffer_snapshot, current_inlays);
+    let hints_cache = &editor.inlay_hint_cache;
+
+    for (buffer_id, cached_buffer_hints) in &hints_cache.hints_in_buffers {
+        let shown_buffer_hints_to_remove = shown_hints_to_remove.entry(*buffer_id).or_default();
+        for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt {
+            let shown_excerpt_hints_to_remove =
+                shown_buffer_hints_to_remove.entry(*excerpt_id).or_default();
+            let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable();
+            shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
+                loop {
+                    match cached_hints.peek() {
+                        Some((cached_anchor, cached_hint_id)) => {
+                            if cached_hint_id == shown_hint_id {
+                                return !new_kinds.contains(
+                                    &hints_cache.inlay_hints.get(&cached_hint_id).unwrap().kind,
+                                );
+                            }
+
+                            match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) {
+                                cmp::Ordering::Less | cmp::Ordering::Equal => {
+                                    let maybe_missed_cached_hint =
+                                        hints_cache.inlay_hints.get(&cached_hint_id).unwrap();
+                                    let cached_hint_kind = maybe_missed_cached_hint.kind;
+                                    if !old_kinds.contains(&cached_hint_kind)
+                                        && new_kinds.contains(&cached_hint_kind)
+                                    {
+                                        to_insert.push((
+                                            *cached_hint_id,
+                                            *cached_anchor,
+                                            maybe_missed_cached_hint.clone(),
+                                        ));
+                                    }
+                                    cached_hints.next();
+                                }
+                                cmp::Ordering::Greater => break,
+                            }
+                        }
+                        None => return true,
+                    }
                 }
-            })
-        })
+
+                match hints_cache.inlay_hints.get(&shown_hint_id) {
+                    Some(shown_hint) => !new_kinds.contains(&shown_hint.kind),
+                    None => true,
+                }
+            });
+
+            for (cached_anchor, cached_hint_id) in cached_hints {
+                let maybe_missed_cached_hint =
+                    hints_cache.inlay_hints.get(&cached_hint_id).unwrap();
+                let cached_hint_kind = maybe_missed_cached_hint.kind;
+                if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
+                    to_insert.push((
+                        *cached_hint_id,
+                        *cached_anchor,
+                        maybe_missed_cached_hint.clone(),
+                    ));
+                }
+            }
+        }
+    }
+
+    to_remove.extend(
+        shown_hints_to_remove
+            .into_iter()
+            .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt)
+            .flat_map(|(_, excerpt_hints)| excerpt_hints)
+            .map(|(_, hint_id)| hint_id),
+    );
+    Some(InlaySplice {
+        to_remove,
+        to_insert,
+    })
+}
+
+fn clean_cache(editor: &mut Editor, current_inlays: Vec<Inlay>) -> Option<InlaySplice> {
+    let hints_cache = &mut editor.inlay_hint_cache;
+    if hints_cache.inlay_hints.is_empty() {
+        None
+    } else {
+        let splice = InlaySplice {
+            to_remove: current_inlays
+                .iter()
+                .filter(|inlay| {
+                    editor
+                        .copilot_state
+                        .suggestion
+                        .as_ref()
+                        .map(|inlay| inlay.id)
+                        != Some(inlay.id)
+                })
+                .map(|inlay| inlay.id)
+                .collect(),
+            to_insert: Vec::new(),
+        };
+        hints_cache.inlay_hints.clear();
+        hints_cache.hints_in_buffers.clear();
+        Some(splice)
     }
 }
 
@@ -400,6 +568,8 @@ fn allowed_hint_types(
     new_allowed_hint_types
 }
 
+// TODO kb wrong, query and update the editor separately
+// TODO kb need to react on react on scrolling too, for multibuffer excerpts
 fn fetch_queries(
     multi_buffer: ModelHandle<MultiBuffer>,
     queries: impl Iterator<Item = InlayHintQuery>,
@@ -477,3 +647,189 @@ fn fetch_queries(
         Ok(inlay_updates)
     })
 }
+
+fn group_inlays(
+    multi_buffer_snapshot: &MultiBufferSnapshot,
+    inlays: Vec<Inlay>,
+) -> HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>> {
+    inlays.into_iter().fold(
+        HashMap::<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>::default(),
+        |mut current_hints, inlay| {
+            if let Some(buffer_id) = inlay.position.buffer_id {
+                current_hints
+                    .entry(buffer_id)
+                    .or_default()
+                    .entry(inlay.position.excerpt_id)
+                    .or_default()
+                    .push((inlay.position, inlay.id));
+            }
+            current_hints
+        },
+    )
+}
+
+async fn update_hints(
+    multi_buffer: ModelHandle<MultiBuffer>,
+    queries: Vec<InlayHintQuery>,
+    current_inlays: Vec<Inlay>,
+    invalidate_cache: bool,
+    cx: &mut ViewContext<'_, '_, Editor>,
+) -> Option<InlaySplice> {
+    let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx);
+    let new_hints = fetch_queries_task.await.context("inlay hints fetch")?;
+
+    let mut to_remove = Vec::new();
+    let mut to_insert = Vec::new();
+    let mut cache_hints_to_persist: HashMap<u64, (Global, HashMap<ExcerptId, HashSet<InlayId>>)> =
+        HashMap::default();
+
+    editor.update(&mut cx, |editor, cx| {
+        let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx);
+        for (new_buffer_id, new_hints_per_buffer) in new_hints {
+            let cached_buffer_hints = editor
+                .inlay_hint_cache
+                .hints_in_buffers
+                .entry(new_buffer_id)
+                .or_insert_with(|| {
+                    BufferHints::new(new_hints_per_buffer.buffer_version.clone())
+                });
+
+            let buffer_cache_hints_to_persist =
+                cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default()));
+            if cached_buffer_hints
+                .buffer_version
+                .changed_since(&new_hints_per_buffer.buffer_version)
+            {
+                buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version;
+                buffer_cache_hints_to_persist.1.extend(
+                    cached_buffer_hints.hints_per_excerpt.iter().map(
+                        |(excerpt_id, excerpt_hints)| {
+                            (
+                                *excerpt_id,
+                                excerpt_hints.iter().map(|(_, id)| *id).collect(),
+                            )
+                        },
+                    ),
+                );
+                continue;
+            }
+
+            let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id);
+            for (new_excerpt_id, new_hints_per_excerpt) in
+                new_hints_per_buffer.hints_per_excerpt
+            {
+                let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1
+                    .entry(new_excerpt_id)
+                    .or_default();
+                let cached_excerpt_hints = cached_buffer_hints
+                    .hints_per_excerpt
+                    .entry(new_excerpt_id)
+                    .or_default();
+                let empty_shown_excerpt_hints = Vec::new();
+                let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints);
+                for new_hint in new_hints_per_excerpt {
+                    let new_hint_anchor = multi_buffer_snapshot
+                        .anchor_in_excerpt(new_excerpt_id, new_hint.position);
+                    let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| {
+                        new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)
+                    }) {
+                        Ok(ix) => {
+                            let (_, cached_inlay_id) = cached_excerpt_hints[ix];
+                            let cache_hit = editor
+                                .inlay_hint_cache
+                                .inlay_hints
+                                .get(&cached_inlay_id)
+                                .filter(|cached_hint| cached_hint == &&new_hint)
+                                .is_some();
+                            if cache_hit {
+                                excerpt_cache_hints_to_persist
+                                    .insert(cached_inlay_id);
+                                None
+                            } else {
+                                Some(ix)
+                            }
+                        }
+                        Err(ix) => Some(ix),
+                    };
+
+                    let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| {
+                        probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot)
+                    }) {
+                        Ok(ix) => {{
+                            let (_, shown_inlay_id) = shown_excerpt_hints[ix];
+                            let shown_hint_found =  editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id)
+                                .filter(|cached_hint| cached_hint == &&new_hint).is_some();
+                            if shown_hint_found {
+                                Some(shown_inlay_id)
+                            } else {
+                                None
+                            }
+                        }},
+                        Err(_) => None,
+                    };
+
+                    if let Some(insert_ix) = cache_insert_ix {
+                        let hint_id = match shown_inlay_id {
+                            Some(shown_inlay_id) => shown_inlay_id,
+                            None => {
+                                let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id));
+                                if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind)
+                                {
+                                    to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone()));
+                                }
+                                new_hint_id
+                            }
+                        };
+                        excerpt_cache_hints_to_persist.insert(hint_id);
+                        cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id));
+                        editor
+                            .inlay_hint_cache
+                            .inlay_hints
+                            .insert(hint_id, new_hint);
+                    }
+                }
+            }
+        }
+
+        if conflicts_with_cache {
+            for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints {
+                match cache_hints_to_persist.get(&shown_buffer_id) {
+                    Some(cached_buffer_hints) => {
+                        for (persisted_id, cached_hints) in &cached_buffer_hints.1 {
+                            shown_hints_to_clean.entry(*persisted_id).or_default()
+                                .retain(|(_, shown_id)| !cached_hints.contains(shown_id));
+                        }
+                    },
+                    None => {},
+                }
+                to_remove.extend(shown_hints_to_clean.into_iter()
+                    .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id)));
+            }
+
+            editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| {
+                let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; };
+                buffer_hints.buffer_version = buffer_hints_to_persist.0;
+                buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| {
+                    let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; };
+                    excerpt_hints.retain(|(_, hint_id)| {
+                        let retain = excerpt_hints_to_persist.contains(hint_id);
+                        if !retain {
+                            editor
+                                .inlay_hint_cache
+                                .inlay_hints
+                                .remove(hint_id);
+                        }
+                        retain
+                    });
+                    !excerpt_hints.is_empty()
+                });
+                !buffer_hints.hints_per_excerpt.is_empty()
+            });
+        }
+
+        Some(InlaySplice {
+            to_remove,
+            to_insert,
+        })
+    })
+}