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}