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