inlay_hint_cache.rs

  1use std::{cmp, ops::Range, sync::Arc};
  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 language::{Buffer, BufferSnapshot};
 11use log::error;
 12use parking_lot::RwLock;
 13use project::{InlayHint, InlayHintKind};
 14
 15use collections::{hash_map, HashMap, HashSet};
 16use util::post_inc;
 17
 18pub struct InlayHintCache {
 19    pub hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
 20    pub allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
 21    pub version: usize,
 22    update_tasks: HashMap<ExcerptId, UpdateTask>,
 23}
 24
 25struct UpdateTask {
 26    current: (InvalidationStrategy, SpawnedTask),
 27    pending_refresh: Option<SpawnedTask>,
 28}
 29
 30struct SpawnedTask {
 31    version: usize,
 32    is_running_rx: smol::channel::Receiver<()>,
 33    _task: Task<()>,
 34}
 35
 36#[derive(Debug)]
 37pub struct CachedExcerptHints {
 38    version: usize,
 39    buffer_version: Global,
 40    pub hints: Vec<(InlayId, InlayHint)>,
 41}
 42
 43#[derive(Debug, Clone, Copy)]
 44struct ExcerptQuery {
 45    buffer_id: u64,
 46    excerpt_id: ExcerptId,
 47    dimensions: ExcerptDimensions,
 48    cache_version: usize,
 49    invalidate: InvalidationStrategy,
 50}
 51
 52#[derive(Debug, Clone, Copy)]
 53struct ExcerptDimensions {
 54    excerpt_range_start: language::Anchor,
 55    excerpt_range_end: language::Anchor,
 56    excerpt_visible_range_start: language::Anchor,
 57    excerpt_visible_range_end: language::Anchor,
 58}
 59
 60impl UpdateTask {
 61    fn new(invalidation_strategy: InvalidationStrategy, spawned_task: SpawnedTask) -> Self {
 62        Self {
 63            current: (invalidation_strategy, spawned_task),
 64            pending_refresh: None,
 65        }
 66    }
 67
 68    fn is_running(&self) -> bool {
 69        !self.current.1.is_running_rx.is_closed()
 70            || self
 71                .pending_refresh
 72                .as_ref()
 73                .map_or(false, |task| !task.is_running_rx.is_closed())
 74    }
 75
 76    fn cache_version(&self) -> usize {
 77        self.current.1.version
 78    }
 79
 80    fn invalidation_strategy(&self) -> InvalidationStrategy {
 81        self.current.0
 82    }
 83}
 84
 85impl ExcerptDimensions {
 86    fn contains_position(
 87        &self,
 88        position: language::Anchor,
 89        buffer_snapshot: &BufferSnapshot,
 90        visible_only: bool,
 91    ) -> bool {
 92        if visible_only {
 93            self.excerpt_visible_range_start
 94                .cmp(&position, buffer_snapshot)
 95                .is_le()
 96                && self
 97                    .excerpt_visible_range_end
 98                    .cmp(&position, buffer_snapshot)
 99                    .is_ge()
100        } else {
101            self.excerpt_range_start
102                .cmp(&position, buffer_snapshot)
103                .is_le()
104                && self
105                    .excerpt_range_end
106                    .cmp(&position, buffer_snapshot)
107                    .is_ge()
108        }
109    }
110}
111
112#[derive(Debug, Clone, Copy)]
113pub enum InvalidationStrategy {
114    Forced,
115    OnConflict,
116    None,
117}
118
119#[derive(Debug, Default)]
120pub struct InlaySplice {
121    pub to_remove: Vec<InlayId>,
122    pub to_insert: Vec<(Anchor, InlayId, InlayHint)>,
123}
124
125#[derive(Debug)]
126struct ExcerptHintsUpdate {
127    excerpt_id: ExcerptId,
128    cache_version: usize,
129    remove_from_visible: Vec<InlayId>,
130    remove_from_cache: HashSet<InlayId>,
131    add_to_cache: Vec<InlayHint>,
132}
133
134impl InlayHintCache {
135    pub fn new(inlay_hint_settings: editor_settings::InlayHints) -> Self {
136        Self {
137            allowed_hint_kinds: allowed_hint_types(inlay_hint_settings),
138            hints: HashMap::default(),
139            update_tasks: HashMap::default(),
140            version: 0,
141        }
142    }
143
144    pub fn update_settings(
145        &mut self,
146        multi_buffer: &ModelHandle<MultiBuffer>,
147        inlay_hint_settings: editor_settings::InlayHints,
148        visible_hints: Vec<Inlay>,
149        cx: &mut ViewContext<Editor>,
150    ) -> Option<InlaySplice> {
151        let new_allowed_hint_kinds = allowed_hint_types(inlay_hint_settings);
152        if !inlay_hint_settings.enabled {
153            if self.hints.is_empty() {
154                self.allowed_hint_kinds = new_allowed_hint_kinds;
155                None
156            } else {
157                self.clear();
158                self.allowed_hint_kinds = new_allowed_hint_kinds;
159                Some(InlaySplice {
160                    to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(),
161                    to_insert: Vec::new(),
162                })
163            }
164        } else if new_allowed_hint_kinds == self.allowed_hint_kinds {
165            None
166        } else {
167            let new_splice = self.new_allowed_hint_kinds_splice(
168                multi_buffer,
169                &visible_hints,
170                &new_allowed_hint_kinds,
171                cx,
172            );
173            if new_splice.is_some() {
174                self.version += 1;
175                self.update_tasks.clear();
176                self.allowed_hint_kinds = new_allowed_hint_kinds;
177            }
178            new_splice
179        }
180    }
181
182    pub fn refresh_inlay_hints(
183        &mut self,
184        mut excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Range<usize>)>,
185        invalidate: InvalidationStrategy,
186        cx: &mut ViewContext<Editor>,
187    ) {
188        let update_tasks = &mut self.update_tasks;
189        let invalidate_cache = matches!(
190            invalidate,
191            InvalidationStrategy::Forced | InvalidationStrategy::OnConflict
192        );
193        if invalidate_cache {
194            update_tasks
195                .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
196        }
197        let cache_version = self.version;
198        excerpts_to_query.retain(|visible_excerpt_id, _| {
199            match update_tasks.entry(*visible_excerpt_id) {
200                hash_map::Entry::Occupied(o) => match o.get().cache_version().cmp(&cache_version) {
201                    cmp::Ordering::Less => true,
202                    cmp::Ordering::Equal => invalidate_cache,
203                    cmp::Ordering::Greater => false,
204                },
205                hash_map::Entry::Vacant(_) => true,
206            }
207        });
208
209        cx.spawn(|editor, mut cx| async move {
210            editor
211                .update(&mut cx, |editor, cx| {
212                    spawn_new_update_tasks(editor, excerpts_to_query, invalidate, cache_version, cx)
213                })
214                .ok();
215        })
216        .detach();
217    }
218
219    fn new_allowed_hint_kinds_splice(
220        &self,
221        multi_buffer: &ModelHandle<MultiBuffer>,
222        visible_hints: &[Inlay],
223        new_kinds: &HashSet<Option<InlayHintKind>>,
224        cx: &mut ViewContext<Editor>,
225    ) -> Option<InlaySplice> {
226        let old_kinds = &self.allowed_hint_kinds;
227        if new_kinds == old_kinds {
228            return None;
229        }
230
231        let mut to_remove = Vec::new();
232        let mut to_insert = Vec::new();
233        let mut shown_hints_to_remove = visible_hints.iter().fold(
234            HashMap::<ExcerptId, Vec<(Anchor, InlayId)>>::default(),
235            |mut current_hints, inlay| {
236                current_hints
237                    .entry(inlay.position.excerpt_id)
238                    .or_default()
239                    .push((inlay.position, inlay.id));
240                current_hints
241            },
242        );
243
244        let multi_buffer = multi_buffer.read(cx);
245        let multi_buffer_snapshot = multi_buffer.snapshot(cx);
246
247        for (excerpt_id, excerpt_cached_hints) in &self.hints {
248            let shown_excerpt_hints_to_remove =
249                shown_hints_to_remove.entry(*excerpt_id).or_default();
250            let excerpt_cached_hints = excerpt_cached_hints.read();
251            let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable();
252            shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
253                let Some(buffer) = shown_anchor
254                    .buffer_id
255                    .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false };
256                let buffer_snapshot = buffer.read(cx).snapshot();
257                loop {
258                    match excerpt_cache.peek() {
259                        Some((cached_hint_id, cached_hint)) => {
260                            if cached_hint_id == shown_hint_id {
261                                excerpt_cache.next();
262                                return !new_kinds.contains(&cached_hint.kind);
263                            }
264
265                            match cached_hint
266                                .position
267                                .cmp(&shown_anchor.text_anchor, &buffer_snapshot)
268                            {
269                                cmp::Ordering::Less | cmp::Ordering::Equal => {
270                                    if !old_kinds.contains(&cached_hint.kind)
271                                        && new_kinds.contains(&cached_hint.kind)
272                                    {
273                                        to_insert.push((
274                                            multi_buffer_snapshot.anchor_in_excerpt(
275                                                *excerpt_id,
276                                                cached_hint.position,
277                                            ),
278                                            *cached_hint_id,
279                                            cached_hint.clone(),
280                                        ));
281                                    }
282                                    excerpt_cache.next();
283                                }
284                                cmp::Ordering::Greater => return true,
285                            }
286                        }
287                        None => return true,
288                    }
289                }
290            });
291
292            for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache {
293                let cached_hint_kind = maybe_missed_cached_hint.kind;
294                if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
295                    to_insert.push((
296                        multi_buffer_snapshot
297                            .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
298                        *cached_hint_id,
299                        maybe_missed_cached_hint.clone(),
300                    ));
301                }
302            }
303        }
304
305        to_remove.extend(
306            shown_hints_to_remove
307                .into_values()
308                .flatten()
309                .map(|(_, hint_id)| hint_id),
310        );
311        if to_remove.is_empty() && to_insert.is_empty() {
312            None
313        } else {
314            Some(InlaySplice {
315                to_remove,
316                to_insert,
317            })
318        }
319    }
320
321    fn clear(&mut self) {
322        self.version += 1;
323        self.update_tasks.clear();
324        self.hints.clear();
325        self.allowed_hint_kinds.clear();
326    }
327}
328
329fn spawn_new_update_tasks(
330    editor: &mut Editor,
331    excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Range<usize>)>,
332    invalidation_strategy: InvalidationStrategy,
333    update_cache_version: usize,
334    cx: &mut ViewContext<'_, '_, Editor>,
335) {
336    let visible_hints = Arc::new(visible_inlay_hints(editor, cx).cloned().collect::<Vec<_>>());
337    for (excerpt_id, (buffer_handle, excerpt_visible_range)) in excerpts_to_query {
338        if !excerpt_visible_range.is_empty() {
339            let buffer = buffer_handle.read(cx);
340            let buffer_snapshot = buffer.snapshot();
341            let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
342            let cache_is_empty = match &cached_excerpt_hints {
343                Some(cached_excerpt_hints) => {
344                    let new_task_buffer_version = buffer_snapshot.version();
345                    let cached_excerpt_hints = cached_excerpt_hints.read();
346                    let cached_buffer_version = &cached_excerpt_hints.buffer_version;
347                    if cached_excerpt_hints.version > update_cache_version
348                        || cached_buffer_version.changed_since(new_task_buffer_version)
349                    {
350                        return;
351                    }
352                    if !new_task_buffer_version.changed_since(&cached_buffer_version)
353                        && !matches!(invalidation_strategy, InvalidationStrategy::Forced)
354                    {
355                        return;
356                    }
357
358                    cached_excerpt_hints.hints.is_empty()
359                }
360                None => true,
361            };
362
363            let buffer_id = buffer.remote_id();
364            let excerpt_visible_range_start = buffer.anchor_before(excerpt_visible_range.start);
365            let excerpt_visible_range_end = buffer.anchor_after(excerpt_visible_range.end);
366
367            let (multi_buffer_snapshot, full_excerpt_range) =
368                editor.buffer.update(cx, |multi_buffer, cx| {
369                    let multi_buffer_snapshot = multi_buffer.snapshot(cx);
370                    (
371                        multi_buffer_snapshot,
372                        multi_buffer
373                            .excerpts_for_buffer(&buffer_handle, cx)
374                            .into_iter()
375                            .find(|(id, _)| id == &excerpt_id)
376                            .map(|(_, range)| range.context),
377                    )
378                });
379
380            if let Some(full_excerpt_range) = full_excerpt_range {
381                let query = ExcerptQuery {
382                    buffer_id,
383                    excerpt_id,
384                    dimensions: ExcerptDimensions {
385                        excerpt_range_start: full_excerpt_range.start,
386                        excerpt_range_end: full_excerpt_range.end,
387                        excerpt_visible_range_start,
388                        excerpt_visible_range_end,
389                    },
390                    cache_version: update_cache_version,
391                    invalidate: invalidation_strategy,
392                };
393
394                let new_update_task = |previous_task| {
395                    new_update_task(
396                        query,
397                        multi_buffer_snapshot,
398                        buffer_snapshot,
399                        Arc::clone(&visible_hints),
400                        cached_excerpt_hints,
401                        previous_task,
402                        cx,
403                    )
404                };
405                match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
406                    hash_map::Entry::Occupied(mut o) => {
407                        let update_task = o.get_mut();
408                        if update_task.is_running() {
409                            match (update_task.invalidation_strategy(), invalidation_strategy) {
410                                (InvalidationStrategy::Forced, InvalidationStrategy::Forced)
411                                | (_, InvalidationStrategy::OnConflict) => {
412                                    o.insert(UpdateTask::new(
413                                        invalidation_strategy,
414                                        new_update_task(None),
415                                    ));
416                                }
417                                (InvalidationStrategy::Forced, _) => {}
418                                (_, InvalidationStrategy::Forced) => {
419                                    if cache_is_empty {
420                                        o.insert(UpdateTask::new(
421                                            invalidation_strategy,
422                                            new_update_task(None),
423                                        ));
424                                    } else if update_task.pending_refresh.is_none() {
425                                        update_task.pending_refresh = Some(new_update_task(Some(
426                                            update_task.current.1.is_running_rx.clone(),
427                                        )));
428                                    }
429                                }
430                                _ => {}
431                            }
432                        } else {
433                            o.insert(UpdateTask::new(
434                                invalidation_strategy,
435                                new_update_task(None),
436                            ));
437                        }
438                    }
439                    hash_map::Entry::Vacant(v) => {
440                        v.insert(UpdateTask::new(
441                            invalidation_strategy,
442                            new_update_task(None),
443                        ));
444                    }
445                }
446            }
447        }
448    }
449}
450
451fn new_update_task(
452    query: ExcerptQuery,
453    multi_buffer_snapshot: MultiBufferSnapshot,
454    buffer_snapshot: BufferSnapshot,
455    visible_hints: Arc<Vec<Inlay>>,
456    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
457    previous_task: Option<smol::channel::Receiver<()>>,
458    cx: &mut ViewContext<'_, '_, Editor>,
459) -> SpawnedTask {
460    let hints_fetch_tasks = hints_fetch_tasks(query, &buffer_snapshot, cx);
461    let (is_running_tx, is_running_rx) = smol::channel::bounded(1);
462    let _task = cx.spawn(|editor, cx| async move {
463        let _is_running_tx = is_running_tx;
464        if let Some(previous_task) = previous_task {
465            previous_task.recv().await.ok();
466        }
467        let create_update_task = |range, hint_fetch_task| {
468            fetch_and_update_hints(
469                editor.clone(),
470                multi_buffer_snapshot.clone(),
471                buffer_snapshot.clone(),
472                Arc::clone(&visible_hints),
473                cached_excerpt_hints.as_ref().map(Arc::clone),
474                query,
475                range,
476                hint_fetch_task,
477                cx.clone(),
478            )
479        };
480
481        let (visible_range, visible_range_hint_fetch_task) = hints_fetch_tasks.visible_range;
482        let visible_range_has_updates =
483            match create_update_task(visible_range, visible_range_hint_fetch_task).await {
484                Ok(updated) => updated,
485                Err(e) => {
486                    error!("inlay hint visible range update task failed: {e:#}");
487                    return;
488                }
489            };
490
491        if visible_range_has_updates {
492            let other_update_results =
493                futures::future::join_all(hints_fetch_tasks.other_ranges.into_iter().map(
494                    |(fetch_range, hints_fetch_task)| {
495                        create_update_task(fetch_range, hints_fetch_task)
496                    },
497                ))
498                .await;
499
500            for result in other_update_results {
501                if let Err(e) = result {
502                    error!("inlay hint update task failed: {e:#}");
503                    return;
504                }
505            }
506        }
507    });
508
509    SpawnedTask {
510        version: query.cache_version,
511        _task,
512        is_running_rx,
513    }
514}
515
516async fn fetch_and_update_hints(
517    editor: gpui::WeakViewHandle<Editor>,
518    multi_buffer_snapshot: MultiBufferSnapshot,
519    buffer_snapshot: BufferSnapshot,
520    visible_hints: Arc<Vec<Inlay>>,
521    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
522    query: ExcerptQuery,
523    fetch_range: Range<language::Anchor>,
524    hints_fetch_task: Task<anyhow::Result<Option<Vec<InlayHint>>>>,
525    mut cx: gpui::AsyncAppContext,
526) -> anyhow::Result<bool> {
527    let mut update_happened = false;
528    match hints_fetch_task.await.context("inlay hint fetch task")? {
529        Some(new_hints) => {
530            let background_task_buffer_snapshot = buffer_snapshot.clone();
531            let backround_fetch_range = fetch_range.clone();
532            if let Some(new_update) = cx
533                .background()
534                .spawn(async move {
535                    calculate_hint_updates(
536                        query,
537                        backround_fetch_range,
538                        new_hints,
539                        &background_task_buffer_snapshot,
540                        cached_excerpt_hints,
541                        &visible_hints,
542                    )
543                })
544                .await
545            {
546                update_happened = !new_update.add_to_cache.is_empty()
547                    || !new_update.remove_from_cache.is_empty()
548                    || !new_update.remove_from_visible.is_empty();
549                editor
550                    .update(&mut cx, |editor, cx| {
551                        let cached_excerpt_hints = editor
552                            .inlay_hint_cache
553                            .hints
554                            .entry(new_update.excerpt_id)
555                            .or_insert_with(|| {
556                                Arc::new(RwLock::new(CachedExcerptHints {
557                                    version: new_update.cache_version,
558                                    buffer_version: buffer_snapshot.version().clone(),
559                                    hints: Vec::new(),
560                                }))
561                            });
562                        let mut cached_excerpt_hints = cached_excerpt_hints.write();
563                        match new_update.cache_version.cmp(&cached_excerpt_hints.version) {
564                            cmp::Ordering::Less => return,
565                            cmp::Ordering::Greater | cmp::Ordering::Equal => {
566                                cached_excerpt_hints.version = new_update.cache_version;
567                            }
568                        }
569                        cached_excerpt_hints
570                            .hints
571                            .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
572                        cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
573                        editor.inlay_hint_cache.version += 1;
574
575                        let mut splice = InlaySplice {
576                            to_remove: new_update.remove_from_visible,
577                            to_insert: Vec::new(),
578                        };
579
580                        for new_hint in new_update.add_to_cache {
581                            let new_hint_position = multi_buffer_snapshot
582                                .anchor_in_excerpt(query.excerpt_id, new_hint.position);
583                            let new_inlay_id = InlayId(post_inc(&mut editor.next_inlay_id));
584                            if editor
585                                .inlay_hint_cache
586                                .allowed_hint_kinds
587                                .contains(&new_hint.kind)
588                            {
589                                splice.to_insert.push((
590                                    new_hint_position,
591                                    new_inlay_id,
592                                    new_hint.clone(),
593                                ));
594                            }
595
596                            cached_excerpt_hints.hints.push((new_inlay_id, new_hint));
597                        }
598
599                        cached_excerpt_hints
600                            .hints
601                            .sort_by(|(_, hint_a), (_, hint_b)| {
602                                hint_a.position.cmp(&hint_b.position, &buffer_snapshot)
603                            });
604                        drop(cached_excerpt_hints);
605
606                        let InlaySplice {
607                            to_remove,
608                            to_insert,
609                        } = splice;
610                        if !to_remove.is_empty() || !to_insert.is_empty() {
611                            editor.splice_inlay_hints(to_remove, to_insert, cx)
612                        }
613                    })
614                    .ok();
615            }
616        }
617        None => {}
618    }
619
620    Ok(update_happened)
621}
622
623fn calculate_hint_updates(
624    query: ExcerptQuery,
625    fetch_range: Range<language::Anchor>,
626    new_excerpt_hints: Vec<InlayHint>,
627    buffer_snapshot: &BufferSnapshot,
628    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
629    visible_hints: &[Inlay],
630) -> Option<ExcerptHintsUpdate> {
631    let mut add_to_cache: Vec<InlayHint> = Vec::new();
632
633    let mut excerpt_hints_to_persist = HashMap::default();
634    for new_hint in new_excerpt_hints {
635        if !query
636            .dimensions
637            .contains_position(new_hint.position, buffer_snapshot, false)
638        {
639            continue;
640        }
641        let missing_from_cache = match &cached_excerpt_hints {
642            Some(cached_excerpt_hints) => {
643                let cached_excerpt_hints = cached_excerpt_hints.read();
644                match cached_excerpt_hints.hints.binary_search_by(|probe| {
645                    probe.1.position.cmp(&new_hint.position, buffer_snapshot)
646                }) {
647                    Ok(ix) => {
648                        let (cached_inlay_id, cached_hint) = &cached_excerpt_hints.hints[ix];
649                        if cached_hint == &new_hint {
650                            excerpt_hints_to_persist.insert(*cached_inlay_id, cached_hint.kind);
651                            false
652                        } else {
653                            true
654                        }
655                    }
656                    Err(_) => true,
657                }
658            }
659            None => true,
660        };
661        if missing_from_cache {
662            add_to_cache.push(new_hint);
663        }
664    }
665
666    let mut remove_from_visible = Vec::new();
667    let mut remove_from_cache = HashSet::default();
668    if matches!(
669        query.invalidate,
670        InvalidationStrategy::Forced | InvalidationStrategy::OnConflict
671    ) {
672        remove_from_visible.extend(
673            visible_hints
674                .iter()
675                .filter(|hint| hint.position.excerpt_id == query.excerpt_id)
676                .filter(|hint| {
677                    query.dimensions.contains_position(
678                        hint.position.text_anchor,
679                        buffer_snapshot,
680                        false,
681                    )
682                })
683                .filter(|hint| {
684                    fetch_range
685                        .start
686                        .cmp(&hint.position.text_anchor, buffer_snapshot)
687                        .is_le()
688                        && fetch_range
689                            .end
690                            .cmp(&hint.position.text_anchor, buffer_snapshot)
691                            .is_ge()
692                })
693                .map(|inlay_hint| inlay_hint.id)
694                .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
695        );
696
697        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
698            let cached_excerpt_hints = cached_excerpt_hints.read();
699            remove_from_cache.extend(
700                cached_excerpt_hints
701                    .hints
702                    .iter()
703                    .filter(|(cached_inlay_id, _)| {
704                        !excerpt_hints_to_persist.contains_key(cached_inlay_id)
705                    })
706                    .filter(|(_, cached_hint)| {
707                        fetch_range
708                            .start
709                            .cmp(&cached_hint.position, buffer_snapshot)
710                            .is_le()
711                            && fetch_range
712                                .end
713                                .cmp(&cached_hint.position, buffer_snapshot)
714                                .is_ge()
715                    })
716                    .map(|(cached_inlay_id, _)| *cached_inlay_id),
717            );
718        }
719    }
720
721    if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
722        None
723    } else {
724        Some(ExcerptHintsUpdate {
725            cache_version: query.cache_version,
726            excerpt_id: query.excerpt_id,
727            remove_from_visible,
728            remove_from_cache,
729            add_to_cache,
730        })
731    }
732}
733
734fn allowed_hint_types(
735    inlay_hint_settings: editor_settings::InlayHints,
736) -> HashSet<Option<InlayHintKind>> {
737    let mut new_allowed_hint_types = HashSet::default();
738    if inlay_hint_settings.show_type_hints {
739        new_allowed_hint_types.insert(Some(InlayHintKind::Type));
740    }
741    if inlay_hint_settings.show_parameter_hints {
742        new_allowed_hint_types.insert(Some(InlayHintKind::Parameter));
743    }
744    if inlay_hint_settings.show_other_hints {
745        new_allowed_hint_types.insert(None);
746    }
747    new_allowed_hint_types
748}
749
750struct HintFetchTasks {
751    visible_range: (
752        Range<language::Anchor>,
753        Task<anyhow::Result<Option<Vec<InlayHint>>>>,
754    ),
755    other_ranges: Vec<(
756        Range<language::Anchor>,
757        Task<anyhow::Result<Option<Vec<InlayHint>>>>,
758    )>,
759}
760
761fn hints_fetch_tasks(
762    query: ExcerptQuery,
763    buffer: &BufferSnapshot,
764    cx: &mut ViewContext<'_, '_, Editor>,
765) -> HintFetchTasks {
766    let visible_range =
767        query.dimensions.excerpt_visible_range_start..query.dimensions.excerpt_visible_range_end;
768    let mut other_ranges = Vec::new();
769    if query
770        .dimensions
771        .excerpt_range_start
772        .cmp(&query.dimensions.excerpt_visible_range_start, buffer)
773        .is_lt()
774    {
775        let mut end = query.dimensions.excerpt_visible_range_start;
776        end.offset -= 1;
777        other_ranges.push(query.dimensions.excerpt_range_start..end);
778    }
779    if query
780        .dimensions
781        .excerpt_range_end
782        .cmp(&query.dimensions.excerpt_visible_range_end, buffer)
783        .is_gt()
784    {
785        let mut start = query.dimensions.excerpt_visible_range_end;
786        start.offset += 1;
787        other_ranges.push(start..query.dimensions.excerpt_range_end);
788    }
789
790    let mut query_task_for_range = |range_to_query| {
791        cx.spawn(|editor, mut cx| async move {
792            let task = editor
793                .update(&mut cx, |editor, cx| {
794                    editor
795                        .buffer()
796                        .read(cx)
797                        .buffer(query.buffer_id)
798                        .and_then(|buffer| {
799                            let project = editor.project.as_ref()?;
800                            Some(project.update(cx, |project, cx| {
801                                project.inlay_hints(buffer, range_to_query, cx)
802                            }))
803                        })
804                })
805                .ok()
806                .flatten();
807            anyhow::Ok(match task {
808                Some(task) => Some(task.await.context("inlays for buffer task")?),
809                None => None,
810            })
811        })
812    };
813
814    HintFetchTasks {
815        visible_range: (visible_range.clone(), query_task_for_range(visible_range)),
816        other_ranges: other_ranges
817            .into_iter()
818            .map(|range| (range.clone(), query_task_for_range(range)))
819            .collect(),
820    }
821}
822
823pub fn visible_inlay_hints<'a, 'b: 'a, 'c, 'd: 'a>(
824    editor: &'a Editor,
825    cx: &'b ViewContext<'c, 'd, Editor>,
826) -> impl Iterator<Item = &'b Inlay> + 'a {
827    editor
828        .display_map
829        .read(cx)
830        .current_inlays()
831        .filter(|inlay| Some(inlay.id) != editor.copilot_state.suggestion.as_ref().map(|h| h.id))
832}