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