inlay_hint_cache.rs

  1use std::cmp;
  2
  3use crate::{
  4    display_map::Inlay, editor_settings, Anchor, Editor, ExcerptId, InlayId, MultiBuffer,
  5    MultiBufferSnapshot,
  6};
  7use anyhow::Context;
  8use clock::Global;
  9use gpui::{ModelHandle, Task, ViewContext};
 10use log::error;
 11use project::{InlayHint, InlayHintKind};
 12
 13use collections::{hash_map, HashMap, HashSet};
 14use util::post_inc;
 15
 16#[derive(Debug)]
 17pub struct InlayHintCache {
 18    inlay_hints: HashMap<InlayId, InlayHint>,
 19    hints_in_buffers: HashMap<u64, BufferHints<(Anchor, InlayId)>>,
 20    allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
 21    hint_updates_tx: smol::channel::Sender<HintsUpdate>,
 22}
 23
 24#[derive(Clone, Debug)]
 25struct BufferHints<H> {
 26    buffer_version: Global,
 27    hints_per_excerpt: HashMap<ExcerptId, Vec<H>>,
 28}
 29
 30#[derive(Debug)]
 31pub struct InlayHintQuery {
 32    pub buffer_id: u64,
 33    pub buffer_version: Global,
 34    pub excerpt_id: ExcerptId,
 35}
 36
 37impl<H> BufferHints<H> {
 38    fn new(buffer_version: Global) -> Self {
 39        Self {
 40            buffer_version,
 41            hints_per_excerpt: HashMap::default(),
 42        }
 43    }
 44}
 45
 46impl InlayHintCache {
 47    pub fn new(
 48        inlay_hint_settings: editor_settings::InlayHints,
 49        cx: &mut ViewContext<Editor>,
 50    ) -> Self {
 51        let (hint_updates_tx, hint_updates_rx) = smol::channel::unbounded();
 52        spawn_hints_update_loop(hint_updates_rx, cx);
 53        Self {
 54            allowed_hint_kinds: allowed_hint_types(inlay_hint_settings),
 55            hints_in_buffers: HashMap::default(),
 56            inlay_hints: HashMap::default(),
 57            hint_updates_tx,
 58        }
 59    }
 60
 61    pub fn spawn_settings_update(
 62        &mut self,
 63        multi_buffer: ModelHandle<MultiBuffer>,
 64        inlay_hint_settings: editor_settings::InlayHints,
 65        current_inlays: Vec<Inlay>,
 66    ) {
 67        if !inlay_hint_settings.enabled {
 68            self.allowed_hint_kinds = allowed_hint_types(inlay_hint_settings);
 69            if self.inlay_hints.is_empty() {
 70                return;
 71            } else {
 72                self.hint_updates_tx
 73                    .send_blocking(HintsUpdate {
 74                        multi_buffer,
 75                        current_inlays,
 76                        kind: HintsUpdateKind::Clean,
 77                    })
 78                    .ok();
 79                return;
 80            }
 81        }
 82
 83        let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings);
 84        if new_allowed_hint_kinds == self.allowed_hint_kinds {
 85            return;
 86        }
 87
 88        self.hint_updates_tx
 89            .send_blocking(HintsUpdate {
 90                multi_buffer,
 91                current_inlays,
 92                kind: HintsUpdateKind::AllowedHintKindsChanged {
 93                    old: self.allowed_hint_kinds.clone(),
 94                    new: new_allowed_hint_kinds,
 95                },
 96            })
 97            .ok();
 98    }
 99
100    pub fn spawn_hints_update(
101        &mut self,
102        multi_buffer: ModelHandle<MultiBuffer>,
103        queries: Vec<InlayHintQuery>,
104        current_inlays: Vec<Inlay>,
105        cx: &mut ViewContext<Editor>,
106    ) {
107        let conflicts_with_cache = queries.iter().any(|update_query| {
108            let Some(cached_buffer_hints) = self.hints_in_buffers.get(&update_query.buffer_id)
109                else { return false };
110            if cached_buffer_hints
111                .buffer_version
112                .changed_since(&update_query.buffer_version)
113            {
114                false
115            } else if update_query
116                .buffer_version
117                .changed_since(&cached_buffer_hints.buffer_version)
118            {
119                true
120            } else {
121                cached_buffer_hints
122                    .hints_per_excerpt
123                    .contains_key(&update_query.excerpt_id)
124            }
125        });
126
127        let queries_per_buffer = queries
128            .into_iter()
129            .filter_map(|query| {
130                let Some(cached_buffer_hints) = self.hints_in_buffers.get(&query.buffer_id)
131                    else { return Some(query) };
132                if cached_buffer_hints
133                    .buffer_version
134                    .changed_since(&query.buffer_version)
135                {
136                    return None;
137                }
138                if conflicts_with_cache
139                    || !cached_buffer_hints
140                        .hints_per_excerpt
141                        .contains_key(&query.excerpt_id)
142                {
143                    Some(query)
144                } else {
145                    None
146                }
147            })
148            .fold(
149                HashMap::<u64, (Global, Vec<ExcerptId>)>::default(),
150                |mut queries_per_buffer, new_query| {
151                    let (current_verison, excerpts_to_query) =
152                        queries_per_buffer.entry(new_query.buffer_id).or_default();
153
154                    if new_query.buffer_version.changed_since(current_verison) {
155                        *current_verison = new_query.buffer_version;
156                        *excerpts_to_query = vec![new_query.excerpt_id];
157                    } else if !current_verison.changed_since(&new_query.buffer_version) {
158                        excerpts_to_query.push(new_query.excerpt_id);
159                    }
160
161                    queries_per_buffer
162                },
163            );
164
165        for (queried_buffer, (buffer_version, excerpts)) in queries_per_buffer {
166            self.hint_updates_tx
167                .send_blocking(HintsUpdate {
168                    multi_buffer,
169                    current_inlays,
170                    kind: HintsUpdateKind::BufferUpdate {
171                        invalidate_cache: conflicts_with_cache,
172                        buffer_id: queried_buffer,
173                        buffer_version,
174                        excerpts,
175                    },
176                })
177                .ok();
178        }
179    }
180}
181
182#[derive(Debug, Default)]
183struct InlaySplice {
184    to_remove: Vec<InlayId>,
185    to_insert: Vec<(InlayId, Anchor, InlayHint)>,
186}
187
188struct HintsUpdate {
189    multi_buffer: ModelHandle<MultiBuffer>,
190    current_inlays: Vec<Inlay>,
191    kind: HintsUpdateKind,
192}
193
194enum HintsUpdateKind {
195    Clean,
196    AllowedHintKindsChanged {
197        old: HashSet<Option<InlayHintKind>>,
198        new: HashSet<Option<InlayHintKind>>,
199    },
200    BufferUpdate {
201        buffer_id: u64,
202        buffer_version: Global,
203        excerpts: Vec<ExcerptId>,
204        invalidate_cache: bool,
205    },
206}
207
208struct UpdateTaskHandle {
209    multi_buffer: ModelHandle<MultiBuffer>,
210    cancellation_tx: smol::channel::Sender<()>,
211    task_finish_rx: smol::channel::Receiver<UpdateTaskResult>,
212}
213
214struct UpdateTaskResult {
215    multi_buffer: ModelHandle<MultiBuffer>,
216    splice: InlaySplice,
217    new_allowed_hint_kinds: Option<HashSet<Option<InlayHintKind>>>,
218    remove_from_cache: HashSet<InlayId>,
219    add_to_cache: HashMap<u64, BufferHints<(Anchor, InlayHint, InlayId)>>,
220}
221
222impl HintsUpdate {
223    fn merge(&mut self, mut other: Self) -> Result<(), Self> {
224        match (&mut self.kind, &mut other.kind) {
225            (HintsUpdateKind::Clean, HintsUpdateKind::Clean) => return Ok(()),
226            (
227                HintsUpdateKind::AllowedHintKindsChanged { .. },
228                HintsUpdateKind::AllowedHintKindsChanged { .. },
229            ) => {
230                *self = other;
231                return Ok(());
232            }
233            (
234                HintsUpdateKind::BufferUpdate {
235                    buffer_id: old_buffer_id,
236                    buffer_version: old_buffer_version,
237                    excerpts: old_excerpts,
238                    invalidate_cache: old_invalidate_cache,
239                },
240                HintsUpdateKind::BufferUpdate {
241                    buffer_id: new_buffer_id,
242                    buffer_version: new_buffer_version,
243                    excerpts: new_excerpts,
244                    invalidate_cache: new_invalidate_cache,
245                },
246            ) => {
247                if old_buffer_id == new_buffer_id {
248                    if new_buffer_version.changed_since(old_buffer_version) {
249                        *self = other;
250                        return Ok(());
251                    } else if old_buffer_version.changed_since(new_buffer_version) {
252                        return Ok(());
253                    } else if *new_invalidate_cache {
254                        *self = other;
255                        return Ok(());
256                    } else {
257                        let old_inlays = self
258                            .current_inlays
259                            .iter()
260                            .map(|inlay| inlay.id)
261                            .collect::<Vec<_>>();
262                        let new_inlays = other
263                            .current_inlays
264                            .iter()
265                            .map(|inlay| inlay.id)
266                            .collect::<Vec<_>>();
267                        if old_inlays == new_inlays {
268                            old_excerpts.extend(new_excerpts.drain(..));
269                            old_excerpts.dedup();
270                            return Ok(());
271                        }
272                    }
273                }
274            }
275            _ => {}
276        }
277
278        Err(other)
279    }
280
281    fn spawn(self, cx: &mut ViewContext<'_, '_, Editor>) -> UpdateTaskHandle {
282        let (task_finish_tx, task_finish_rx) = smol::channel::unbounded();
283        let (cancellation_tx, cancellation_rx) = smol::channel::bounded(1);
284
285        match self.kind {
286            HintsUpdateKind::Clean => cx
287                .spawn(|editor, mut cx| async move {
288                    if let Some(splice) = editor.update(&mut cx, |editor, cx| {
289                        clean_cache(editor, self.current_inlays)
290                    })? {
291                        task_finish_tx
292                            .send(UpdateTaskResult {
293                                multi_buffer: self.multi_buffer.clone(),
294                                splice,
295                                new_allowed_hint_kinds: None,
296                                remove_from_cache: HashSet::default(),
297                                add_to_cache: HashMap::default(),
298                            })
299                            .await
300                            .ok();
301                    }
302                    anyhow::Ok(())
303                })
304                .detach_and_log_err(cx),
305            HintsUpdateKind::AllowedHintKindsChanged { old, new } => cx
306                .spawn(|editor, mut cx| async move {
307                    if let Some(splice) = editor.update(&mut cx, |editor, cx| {
308                        update_allowed_hint_kinds(
309                            &self.multi_buffer.read(cx).snapshot(cx),
310                            self.current_inlays,
311                            old,
312                            new,
313                            editor,
314                        )
315                    })? {
316                        task_finish_tx
317                            .send(UpdateTaskResult {
318                                multi_buffer: self.multi_buffer.clone(),
319                                splice,
320                                new_allowed_hint_kinds: None,
321                                remove_from_cache: HashSet::default(),
322                                add_to_cache: HashMap::default(),
323                            })
324                            .await
325                            .ok();
326                    }
327                    anyhow::Ok(())
328                })
329                .detach_and_log_err(cx),
330            HintsUpdateKind::BufferUpdate {
331                buffer_id,
332                buffer_version,
333                excerpts,
334                invalidate_cache,
335            } => todo!("TODO kb"),
336        }
337
338        UpdateTaskHandle {
339            multi_buffer: self.multi_buffer.clone(),
340            cancellation_tx,
341            task_finish_rx,
342        }
343    }
344}
345
346fn spawn_hints_update_loop(
347    hint_updates_rx: smol::channel::Receiver<HintsUpdate>,
348    cx: &mut ViewContext<'_, '_, Editor>,
349) {
350    cx.spawn(|editor, mut cx| async move {
351        let mut update = None::<HintsUpdate>;
352        let mut next_update = None::<HintsUpdate>;
353        loop {
354            if update.is_none() {
355                match hint_updates_rx.recv().await {
356                    Ok(first_task) => update = Some(first_task),
357                    Err(smol::channel::RecvError) => return,
358                }
359            }
360
361            let mut updates_limit = 10;
362            'update_merge: loop {
363                match hint_updates_rx.try_recv() {
364                    Ok(new_update) => {
365                        match update.as_mut() {
366                            Some(update) => match update.merge(new_update) {
367                                Ok(()) => {}
368                                Err(new_update) => {
369                                    next_update = Some(new_update);
370                                    break 'update_merge;
371                                }
372                            },
373                            None => update = Some(new_update),
374                        };
375
376                        if updates_limit == 0 {
377                            break 'update_merge;
378                        }
379                        updates_limit -= 1;
380                    }
381                    Err(smol::channel::TryRecvError::Empty) => break 'update_merge,
382                    Err(smol::channel::TryRecvError::Closed) => return,
383                }
384            }
385
386            if let Some(update) = update.take() {
387                let Ok(task_handle) = editor.update(&mut cx, |_, cx| update.spawn(cx)) else { return; };
388                while let Ok(update_task_result) = task_handle.task_finish_rx.recv().await {
389                    let Ok(()) = editor.update(&mut cx, |editor, cx| {
390                        let multi_buffer_snapshot = update_task_result.multi_buffer.read(cx).snapshot(cx);
391                        let inlay_hint_cache = &mut editor.inlay_hint_cache;
392
393                        if let Some(new_allowed_hint_kinds) = update_task_result.new_allowed_hint_kinds {
394                            inlay_hint_cache.allowed_hint_kinds = new_allowed_hint_kinds;
395                        }
396
397                        inlay_hint_cache.hints_in_buffers.retain(|_, buffer_hints| {
398                            buffer_hints.hints_per_excerpt.retain(|_, excerpt_hints| {
399                                excerpt_hints.retain(|(_, hint_id)| !update_task_result.remove_from_cache.contains(hint_id));
400                                !excerpt_hints.is_empty()
401                            });
402                            !buffer_hints.hints_per_excerpt.is_empty()
403                        });
404                        inlay_hint_cache.inlay_hints.retain(|hint_id, _| !update_task_result.remove_from_cache.contains(hint_id));
405
406                        for (new_buffer_id, new_buffer_inlays) in update_task_result.add_to_cache {
407                            let cached_buffer_hints = inlay_hint_cache.hints_in_buffers.entry(new_buffer_id).or_insert_with(|| BufferHints::new(new_buffer_inlays.buffer_version));
408                            if cached_buffer_hints.buffer_version.changed_since(&new_buffer_inlays.buffer_version) {
409                                continue;
410                            }
411                            for (excerpt_id, new_excerpt_inlays) in new_buffer_inlays.hints_per_excerpt {
412                                let cached_excerpt_hints = cached_buffer_hints.hints_per_excerpt.entry(excerpt_id).or_default();
413                                for (new_hint_position, new_hint, new_inlay_id) in new_excerpt_inlays {
414                                    if let hash_map::Entry::Vacant(v) = inlay_hint_cache.inlay_hints.entry(new_inlay_id) {
415                                        v.insert(new_hint);
416                                        match cached_excerpt_hints.binary_search_by(|probe| {
417                                            new_hint_position.cmp(&probe.0, &multi_buffer_snapshot)
418                                        }) {
419                                            Ok(ix) | Err(ix) => cached_excerpt_hints.insert(ix, (new_hint_position, new_inlay_id)),
420                                        }
421                                    }
422                                }
423                            }
424                        }
425
426                        let InlaySplice {
427                            to_remove,
428                            to_insert,
429                        } = update_task_result.splice;
430                        editor.splice_inlay_hints(to_remove, to_insert, cx)
431                    }) else { return; };
432                }
433            }
434            update = next_update.take();
435        }
436    })
437    .detach()
438}
439
440fn update_allowed_hint_kinds(
441    multi_buffer_snapshot: &MultiBufferSnapshot,
442    current_inlays: Vec<Inlay>,
443    old_kinds: HashSet<Option<InlayHintKind>>,
444    new_kinds: HashSet<Option<InlayHintKind>>,
445    editor: &mut Editor,
446) -> Option<InlaySplice> {
447    if old_kinds == new_kinds {
448        return None;
449    }
450
451    let mut to_remove = Vec::new();
452    let mut to_insert = Vec::new();
453    let mut shown_hints_to_remove = group_inlays(&multi_buffer_snapshot, current_inlays);
454    let hints_cache = &editor.inlay_hint_cache;
455
456    for (buffer_id, cached_buffer_hints) in &hints_cache.hints_in_buffers {
457        let shown_buffer_hints_to_remove = shown_hints_to_remove.entry(*buffer_id).or_default();
458        for (excerpt_id, cached_excerpt_hints) in &cached_buffer_hints.hints_per_excerpt {
459            let shown_excerpt_hints_to_remove =
460                shown_buffer_hints_to_remove.entry(*excerpt_id).or_default();
461            let mut cached_hints = cached_excerpt_hints.iter().fuse().peekable();
462            shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
463                loop {
464                    match cached_hints.peek() {
465                        Some((cached_anchor, cached_hint_id)) => {
466                            if cached_hint_id == shown_hint_id {
467                                return !new_kinds.contains(
468                                    &hints_cache.inlay_hints.get(&cached_hint_id).unwrap().kind,
469                                );
470                            }
471
472                            match cached_anchor.cmp(shown_anchor, &multi_buffer_snapshot) {
473                                cmp::Ordering::Less | cmp::Ordering::Equal => {
474                                    let maybe_missed_cached_hint =
475                                        hints_cache.inlay_hints.get(&cached_hint_id).unwrap();
476                                    let cached_hint_kind = maybe_missed_cached_hint.kind;
477                                    if !old_kinds.contains(&cached_hint_kind)
478                                        && new_kinds.contains(&cached_hint_kind)
479                                    {
480                                        to_insert.push((
481                                            *cached_hint_id,
482                                            *cached_anchor,
483                                            maybe_missed_cached_hint.clone(),
484                                        ));
485                                    }
486                                    cached_hints.next();
487                                }
488                                cmp::Ordering::Greater => break,
489                            }
490                        }
491                        None => return true,
492                    }
493                }
494
495                match hints_cache.inlay_hints.get(&shown_hint_id) {
496                    Some(shown_hint) => !new_kinds.contains(&shown_hint.kind),
497                    None => true,
498                }
499            });
500
501            for (cached_anchor, cached_hint_id) in cached_hints {
502                let maybe_missed_cached_hint =
503                    hints_cache.inlay_hints.get(&cached_hint_id).unwrap();
504                let cached_hint_kind = maybe_missed_cached_hint.kind;
505                if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
506                    to_insert.push((
507                        *cached_hint_id,
508                        *cached_anchor,
509                        maybe_missed_cached_hint.clone(),
510                    ));
511                }
512            }
513        }
514    }
515
516    to_remove.extend(
517        shown_hints_to_remove
518            .into_iter()
519            .flat_map(|(_, hints_by_excerpt)| hints_by_excerpt)
520            .flat_map(|(_, excerpt_hints)| excerpt_hints)
521            .map(|(_, hint_id)| hint_id),
522    );
523    Some(InlaySplice {
524        to_remove,
525        to_insert,
526    })
527}
528
529fn clean_cache(editor: &mut Editor, current_inlays: Vec<Inlay>) -> Option<InlaySplice> {
530    let hints_cache = &mut editor.inlay_hint_cache;
531    if hints_cache.inlay_hints.is_empty() {
532        None
533    } else {
534        let splice = InlaySplice {
535            to_remove: current_inlays
536                .iter()
537                .filter(|inlay| {
538                    editor
539                        .copilot_state
540                        .suggestion
541                        .as_ref()
542                        .map(|inlay| inlay.id)
543                        != Some(inlay.id)
544                })
545                .map(|inlay| inlay.id)
546                .collect(),
547            to_insert: Vec::new(),
548        };
549        hints_cache.inlay_hints.clear();
550        hints_cache.hints_in_buffers.clear();
551        Some(splice)
552    }
553}
554
555fn allowed_hint_types(
556    inlay_hint_settings: editor_settings::InlayHints,
557) -> HashSet<Option<InlayHintKind>> {
558    let mut new_allowed_hint_types = HashSet::default();
559    if inlay_hint_settings.show_type_hints {
560        new_allowed_hint_types.insert(Some(InlayHintKind::Type));
561    }
562    if inlay_hint_settings.show_parameter_hints {
563        new_allowed_hint_types.insert(Some(InlayHintKind::Parameter));
564    }
565    if inlay_hint_settings.show_other_hints {
566        new_allowed_hint_types.insert(None);
567    }
568    new_allowed_hint_types
569}
570
571// TODO kb wrong, query and update the editor separately
572// TODO kb need to react on react on scrolling too, for multibuffer excerpts
573fn fetch_queries(
574    multi_buffer: ModelHandle<MultiBuffer>,
575    queries: impl Iterator<Item = InlayHintQuery>,
576    cx: &mut ViewContext<'_, '_, Editor>,
577) -> Task<anyhow::Result<HashMap<u64, BufferHints<InlayHint>>>> {
578    let mut inlay_fetch_tasks = Vec::new();
579    for query in queries {
580        let task_multi_buffer = multi_buffer.clone();
581        let task = cx.spawn(|editor, mut cx| async move {
582            let Some(buffer_handle) = cx.read(|cx| task_multi_buffer.read(cx).buffer(query.buffer_id))
583                else { return anyhow::Ok((query, Some(Vec::new()))) };
584            let task = editor
585                .update(&mut cx, |editor, cx| {
586                    if let Some((_, excerpt_range)) = task_multi_buffer.read(cx)
587                        .excerpts_for_buffer(&buffer_handle, cx)
588                        .into_iter()
589                        .find(|(excerpt_id, _)| excerpt_id == &query.excerpt_id)
590                    {
591                        editor.project.as_ref().map(|project| {
592                            project.update(cx, |project, cx| {
593                                project.query_inlay_hints_for_buffer(
594                                    buffer_handle,
595                                    excerpt_range.context,
596                                    cx,
597                                )
598                            })
599                        })
600                    } else {
601                        None
602                    }
603                })
604                .context("inlays fetch task spawn")?;
605            Ok((
606                query,
607                match task {
608                    Some(task) => task.await.context("inlays for buffer task")?,
609                    None => Some(Vec::new()),
610                },
611            ))
612        });
613
614        inlay_fetch_tasks.push(task);
615    }
616
617    cx.spawn(|editor, cx| async move {
618        let mut inlay_updates: HashMap<u64, BufferHints<InlayHint>> = HashMap::default();
619        for task_result in futures::future::join_all(inlay_fetch_tasks).await {
620            match task_result {
621                Ok((query, Some(response_hints))) => {
622                    let Some(buffer_snapshot) = editor.read_with(&cx, |editor, cx| {
623                        editor.buffer().read(cx).buffer(query.buffer_id).map(|buffer| buffer.read(cx).snapshot())
624                    })? else { continue; };
625                    let buffer_hints = inlay_updates
626                        .entry(query.buffer_id)
627                        .or_insert_with(|| BufferHints::new(query.buffer_version.clone()));
628                    if buffer_snapshot.version().changed_since(&buffer_hints.buffer_version) {
629                        continue;
630                    }
631                    let cached_excerpt_hints = buffer_hints
632                        .hints_per_excerpt
633                        .entry(query.excerpt_id)
634                        .or_default();
635                    for inlay in response_hints {
636                        match cached_excerpt_hints.binary_search_by(|probe| {
637                            inlay.position.cmp(&probe.position, &buffer_snapshot)
638                        }) {
639                            Ok(ix) | Err(ix) => cached_excerpt_hints.insert(ix, inlay),
640                        }
641                    }
642                }
643                Ok((_, None)) => {}
644                Err(e) => error!("Failed to update inlays for buffer: {e:#}"),
645            }
646        }
647        Ok(inlay_updates)
648    })
649}
650
651fn group_inlays(
652    multi_buffer_snapshot: &MultiBufferSnapshot,
653    inlays: Vec<Inlay>,
654) -> HashMap<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>> {
655    inlays.into_iter().fold(
656        HashMap::<u64, HashMap<ExcerptId, Vec<(Anchor, InlayId)>>>::default(),
657        |mut current_hints, inlay| {
658            if let Some(buffer_id) = inlay.position.buffer_id {
659                current_hints
660                    .entry(buffer_id)
661                    .or_default()
662                    .entry(inlay.position.excerpt_id)
663                    .or_default()
664                    .push((inlay.position, inlay.id));
665            }
666            current_hints
667        },
668    )
669}
670
671async fn update_hints(
672    multi_buffer: ModelHandle<MultiBuffer>,
673    queries: Vec<InlayHintQuery>,
674    current_inlays: Vec<Inlay>,
675    invalidate_cache: bool,
676    cx: &mut ViewContext<'_, '_, Editor>,
677) -> Option<InlaySplice> {
678    let fetch_queries_task = fetch_queries(multi_buffer, queries.into_iter(), cx);
679    let new_hints = fetch_queries_task.await.context("inlay hints fetch")?;
680
681    let mut to_remove = Vec::new();
682    let mut to_insert = Vec::new();
683    let mut cache_hints_to_persist: HashMap<u64, (Global, HashMap<ExcerptId, HashSet<InlayId>>)> =
684        HashMap::default();
685
686    editor.update(&mut cx, |editor, cx| {
687        let multi_buffer_snapshot = task_multi_buffer.read(cx).snapshot(cx);
688        for (new_buffer_id, new_hints_per_buffer) in new_hints {
689            let cached_buffer_hints = editor
690                .inlay_hint_cache
691                .hints_in_buffers
692                .entry(new_buffer_id)
693                .or_insert_with(|| {
694                    BufferHints::new(new_hints_per_buffer.buffer_version.clone())
695                });
696
697            let buffer_cache_hints_to_persist =
698                cache_hints_to_persist.entry(new_buffer_id).or_insert_with(|| (new_hints_per_buffer.buffer_version.clone(), HashMap::default()));
699            if cached_buffer_hints
700                .buffer_version
701                .changed_since(&new_hints_per_buffer.buffer_version)
702            {
703                buffer_cache_hints_to_persist.0 = new_hints_per_buffer.buffer_version;
704                buffer_cache_hints_to_persist.1.extend(
705                    cached_buffer_hints.hints_per_excerpt.iter().map(
706                        |(excerpt_id, excerpt_hints)| {
707                            (
708                                *excerpt_id,
709                                excerpt_hints.iter().map(|(_, id)| *id).collect(),
710                            )
711                        },
712                    ),
713                );
714                continue;
715            }
716
717            let shown_buffer_hints = currently_shown_hints.get(&new_buffer_id);
718            for (new_excerpt_id, new_hints_per_excerpt) in
719                new_hints_per_buffer.hints_per_excerpt
720            {
721                let excerpt_cache_hints_to_persist = buffer_cache_hints_to_persist.1
722                    .entry(new_excerpt_id)
723                    .or_default();
724                let cached_excerpt_hints = cached_buffer_hints
725                    .hints_per_excerpt
726                    .entry(new_excerpt_id)
727                    .or_default();
728                let empty_shown_excerpt_hints = Vec::new();
729                let shown_excerpt_hints = shown_buffer_hints.and_then(|hints| hints.get(&new_excerpt_id)).unwrap_or(&empty_shown_excerpt_hints);
730                for new_hint in new_hints_per_excerpt {
731                    let new_hint_anchor = multi_buffer_snapshot
732                        .anchor_in_excerpt(new_excerpt_id, new_hint.position);
733                    let cache_insert_ix = match cached_excerpt_hints.binary_search_by(|probe| {
734                        new_hint_anchor.cmp(&probe.0, &multi_buffer_snapshot)
735                    }) {
736                        Ok(ix) => {
737                            let (_, cached_inlay_id) = cached_excerpt_hints[ix];
738                            let cache_hit = editor
739                                .inlay_hint_cache
740                                .inlay_hints
741                                .get(&cached_inlay_id)
742                                .filter(|cached_hint| cached_hint == &&new_hint)
743                                .is_some();
744                            if cache_hit {
745                                excerpt_cache_hints_to_persist
746                                    .insert(cached_inlay_id);
747                                None
748                            } else {
749                                Some(ix)
750                            }
751                        }
752                        Err(ix) => Some(ix),
753                    };
754
755                    let shown_inlay_id = match shown_excerpt_hints.binary_search_by(|probe| {
756                        probe.0.cmp(&new_hint_anchor, &multi_buffer_snapshot)
757                    }) {
758                        Ok(ix) => {{
759                            let (_, shown_inlay_id) = shown_excerpt_hints[ix];
760                            let shown_hint_found =  editor.inlay_hint_cache.inlay_hints.get(&shown_inlay_id)
761                                .filter(|cached_hint| cached_hint == &&new_hint).is_some();
762                            if shown_hint_found {
763                                Some(shown_inlay_id)
764                            } else {
765                                None
766                            }
767                        }},
768                        Err(_) => None,
769                    };
770
771                    if let Some(insert_ix) = cache_insert_ix {
772                        let hint_id = match shown_inlay_id {
773                            Some(shown_inlay_id) => shown_inlay_id,
774                            None => {
775                                let new_hint_id = InlayId(post_inc(&mut editor.next_inlay_id));
776                                if editor.inlay_hint_cache.allowed_hint_kinds.contains(&new_hint.kind)
777                                {
778                                    to_insert.push((new_hint_id, new_hint_anchor, new_hint.clone()));
779                                }
780                                new_hint_id
781                            }
782                        };
783                        excerpt_cache_hints_to_persist.insert(hint_id);
784                        cached_excerpt_hints.insert(insert_ix, (new_hint_anchor, hint_id));
785                        editor
786                            .inlay_hint_cache
787                            .inlay_hints
788                            .insert(hint_id, new_hint);
789                    }
790                }
791            }
792        }
793
794        if conflicts_with_cache {
795            for (shown_buffer_id, mut shown_hints_to_clean) in currently_shown_hints {
796                match cache_hints_to_persist.get(&shown_buffer_id) {
797                    Some(cached_buffer_hints) => {
798                        for (persisted_id, cached_hints) in &cached_buffer_hints.1 {
799                            shown_hints_to_clean.entry(*persisted_id).or_default()
800                                .retain(|(_, shown_id)| !cached_hints.contains(shown_id));
801                        }
802                    },
803                    None => {},
804                }
805                to_remove.extend(shown_hints_to_clean.into_iter()
806                    .flat_map(|(_, excerpt_hints)| excerpt_hints.into_iter().map(|(_, hint_id)| hint_id)));
807            }
808
809            editor.inlay_hint_cache.hints_in_buffers.retain(|buffer_id, buffer_hints| {
810                let Some(mut buffer_hints_to_persist) = cache_hints_to_persist.remove(buffer_id) else { return false; };
811                buffer_hints.buffer_version = buffer_hints_to_persist.0;
812                buffer_hints.hints_per_excerpt.retain(|excerpt_id, excerpt_hints| {
813                    let Some(excerpt_hints_to_persist) = buffer_hints_to_persist.1.remove(&excerpt_id) else { return false; };
814                    excerpt_hints.retain(|(_, hint_id)| {
815                        let retain = excerpt_hints_to_persist.contains(hint_id);
816                        if !retain {
817                            editor
818                                .inlay_hint_cache
819                                .inlay_hints
820                                .remove(hint_id);
821                        }
822                        retain
823                    });
824                    !excerpt_hints.is_empty()
825                });
826                !buffer_hints.hints_per_excerpt.is_empty()
827            });
828        }
829
830        Some(InlaySplice {
831            to_remove,
832            to_insert,
833        })
834    })
835}