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