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