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}