1use std::{
2 cmp,
3 ops::{ControlFlow, Range},
4 sync::Arc,
5};
6
7use crate::{
8 display_map::Inlay, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot,
9};
10use anyhow::Context;
11use clock::Global;
12use futures::{future::Shared, FutureExt};
13use gpui::{ModelHandle, Task, ViewContext};
14use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
15use log::error;
16use parking_lot::RwLock;
17use project::InlayHint;
18
19use collections::{hash_map, HashMap, HashSet};
20use language::language_settings::InlayHintSettings;
21use util::post_inc;
22
23pub struct InlayHintCache {
24 pub hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
25 pub allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
26 pub version: usize,
27 pub enabled: bool,
28 update_tasks: HashMap<ExcerptId, UpdateTask>,
29}
30
31struct UpdateTask {
32 invalidation_strategy: InvalidationStrategy,
33 cache_version: usize,
34 _task: Shared<Task<()>>,
35 pending_refresh: Option<Task<()>>,
36}
37
38#[derive(Debug)]
39pub struct CachedExcerptHints {
40 version: usize,
41 buffer_version: Global,
42 buffer_id: u64,
43 pub hints: Vec<(InlayId, InlayHint)>,
44}
45
46#[derive(Debug, Clone, Copy)]
47struct ExcerptQuery {
48 buffer_id: u64,
49 excerpt_id: ExcerptId,
50 dimensions: ExcerptDimensions,
51 cache_version: usize,
52 invalidate: InvalidationStrategy,
53}
54
55#[derive(Debug, Clone, Copy)]
56struct ExcerptDimensions {
57 excerpt_range_start: language::Anchor,
58 excerpt_range_end: language::Anchor,
59 excerpt_visible_range_start: language::Anchor,
60 excerpt_visible_range_end: language::Anchor,
61}
62
63impl ExcerptQuery {
64 fn should_invalidate(&self) -> bool {
65 matches!(
66 self.invalidate,
67 InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited
68 )
69 }
70
71 fn hints_fetch_ranges(&self, buffer: &BufferSnapshot) -> HintFetchRanges {
72 let visible_range =
73 self.dimensions.excerpt_visible_range_start..self.dimensions.excerpt_visible_range_end;
74 let mut other_ranges = Vec::new();
75 if self
76 .dimensions
77 .excerpt_range_start
78 .cmp(&visible_range.start, buffer)
79 .is_lt()
80 {
81 let mut end = visible_range.start;
82 end.offset -= 1;
83 other_ranges.push(self.dimensions.excerpt_range_start..end);
84 }
85 if self
86 .dimensions
87 .excerpt_range_end
88 .cmp(&visible_range.end, buffer)
89 .is_gt()
90 {
91 let mut start = visible_range.end;
92 start.offset += 1;
93 other_ranges.push(start..self.dimensions.excerpt_range_end);
94 }
95
96 HintFetchRanges {
97 visible_range,
98 other_ranges: other_ranges.into_iter().map(|range| range).collect(),
99 }
100 }
101}
102
103#[derive(Debug, Clone, Copy)]
104pub enum InvalidationStrategy {
105 RefreshRequested,
106 ExcerptEdited,
107 None,
108}
109
110#[derive(Debug, Default)]
111pub struct InlaySplice {
112 pub to_remove: Vec<InlayId>,
113 pub to_insert: Vec<(Anchor, InlayId, InlayHint)>,
114}
115
116#[derive(Debug)]
117struct ExcerptHintsUpdate {
118 excerpt_id: ExcerptId,
119 cache_version: usize,
120 remove_from_visible: Vec<InlayId>,
121 remove_from_cache: HashSet<InlayId>,
122 add_to_cache: HashSet<InlayHint>,
123}
124
125impl InlayHintCache {
126 pub fn new(inlay_hint_settings: InlayHintSettings) -> Self {
127 Self {
128 allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
129 enabled: inlay_hint_settings.enabled,
130 hints: HashMap::default(),
131 update_tasks: HashMap::default(),
132 version: 0,
133 }
134 }
135
136 pub fn update_settings(
137 &mut self,
138 multi_buffer: &ModelHandle<MultiBuffer>,
139 new_hint_settings: InlayHintSettings,
140 visible_hints: Vec<Inlay>,
141 cx: &mut ViewContext<Editor>,
142 ) -> ControlFlow<Option<InlaySplice>> {
143 let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
144 match (self.enabled, new_hint_settings.enabled) {
145 (false, false) => {
146 self.allowed_hint_kinds = new_allowed_hint_kinds;
147 ControlFlow::Break(None)
148 }
149 (true, true) => {
150 if new_allowed_hint_kinds == self.allowed_hint_kinds {
151 ControlFlow::Break(None)
152 } else {
153 let new_splice = self.new_allowed_hint_kinds_splice(
154 multi_buffer,
155 &visible_hints,
156 &new_allowed_hint_kinds,
157 cx,
158 );
159 if new_splice.is_some() {
160 self.version += 1;
161 self.update_tasks.clear();
162 self.allowed_hint_kinds = new_allowed_hint_kinds;
163 }
164 ControlFlow::Break(new_splice)
165 }
166 }
167 (true, false) => {
168 self.enabled = new_hint_settings.enabled;
169 self.allowed_hint_kinds = new_allowed_hint_kinds;
170 if self.hints.is_empty() {
171 ControlFlow::Break(None)
172 } else {
173 self.clear();
174 ControlFlow::Break(Some(InlaySplice {
175 to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(),
176 to_insert: Vec::new(),
177 }))
178 }
179 }
180 (false, true) => {
181 self.enabled = new_hint_settings.enabled;
182 self.allowed_hint_kinds = new_allowed_hint_kinds;
183 ControlFlow::Continue(())
184 }
185 }
186 }
187
188 pub fn refresh_inlay_hints(
189 &mut self,
190 mut excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Range<usize>)>,
191 invalidate: InvalidationStrategy,
192 cx: &mut ViewContext<Editor>,
193 ) {
194 if !self.enabled {
195 return;
196 }
197 let update_tasks = &mut self.update_tasks;
198 let invalidate_cache = matches!(
199 invalidate,
200 InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited
201 );
202 if invalidate_cache {
203 update_tasks
204 .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
205 }
206 let cache_version = self.version;
207 excerpts_to_query.retain(|visible_excerpt_id, _| {
208 match update_tasks.entry(*visible_excerpt_id) {
209 hash_map::Entry::Occupied(o) => match o.get().cache_version.cmp(&cache_version) {
210 cmp::Ordering::Less => true,
211 cmp::Ordering::Equal => invalidate_cache,
212 cmp::Ordering::Greater => false,
213 },
214 hash_map::Entry::Vacant(_) => true,
215 }
216 });
217
218 cx.spawn(|editor, mut cx| async move {
219 editor
220 .update(&mut cx, |editor, cx| {
221 spawn_new_update_tasks(editor, excerpts_to_query, invalidate, cache_version, cx)
222 })
223 .ok();
224 })
225 .detach();
226 }
227
228 fn new_allowed_hint_kinds_splice(
229 &self,
230 multi_buffer: &ModelHandle<MultiBuffer>,
231 visible_hints: &[Inlay],
232 new_kinds: &HashSet<Option<InlayHintKind>>,
233 cx: &mut ViewContext<Editor>,
234 ) -> Option<InlaySplice> {
235 let old_kinds = &self.allowed_hint_kinds;
236 if new_kinds == old_kinds {
237 return None;
238 }
239
240 let mut to_remove = Vec::new();
241 let mut to_insert = Vec::new();
242 let mut shown_hints_to_remove = visible_hints.iter().fold(
243 HashMap::<ExcerptId, Vec<(Anchor, InlayId)>>::default(),
244 |mut current_hints, inlay| {
245 current_hints
246 .entry(inlay.position.excerpt_id)
247 .or_default()
248 .push((inlay.position, inlay.id));
249 current_hints
250 },
251 );
252
253 let multi_buffer = multi_buffer.read(cx);
254 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
255
256 for (excerpt_id, excerpt_cached_hints) in &self.hints {
257 let shown_excerpt_hints_to_remove =
258 shown_hints_to_remove.entry(*excerpt_id).or_default();
259 let excerpt_cached_hints = excerpt_cached_hints.read();
260 let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable();
261 shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
262 let Some(buffer) = shown_anchor
263 .buffer_id
264 .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false };
265 let buffer_snapshot = buffer.read(cx).snapshot();
266 loop {
267 match excerpt_cache.peek() {
268 Some((cached_hint_id, cached_hint)) => {
269 if cached_hint_id == shown_hint_id {
270 excerpt_cache.next();
271 return !new_kinds.contains(&cached_hint.kind);
272 }
273
274 match cached_hint
275 .position
276 .cmp(&shown_anchor.text_anchor, &buffer_snapshot)
277 {
278 cmp::Ordering::Less | cmp::Ordering::Equal => {
279 if !old_kinds.contains(&cached_hint.kind)
280 && new_kinds.contains(&cached_hint.kind)
281 {
282 to_insert.push((
283 multi_buffer_snapshot.anchor_in_excerpt(
284 *excerpt_id,
285 cached_hint.position,
286 ),
287 *cached_hint_id,
288 cached_hint.clone(),
289 ));
290 }
291 excerpt_cache.next();
292 }
293 cmp::Ordering::Greater => return true,
294 }
295 }
296 None => return true,
297 }
298 }
299 });
300
301 for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache {
302 let cached_hint_kind = maybe_missed_cached_hint.kind;
303 if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
304 to_insert.push((
305 multi_buffer_snapshot
306 .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
307 *cached_hint_id,
308 maybe_missed_cached_hint.clone(),
309 ));
310 }
311 }
312 }
313
314 to_remove.extend(
315 shown_hints_to_remove
316 .into_values()
317 .flatten()
318 .map(|(_, hint_id)| hint_id),
319 );
320 if to_remove.is_empty() && to_insert.is_empty() {
321 None
322 } else {
323 Some(InlaySplice {
324 to_remove,
325 to_insert,
326 })
327 }
328 }
329
330 fn clear(&mut self) {
331 self.version += 1;
332 self.update_tasks.clear();
333 self.hints.clear();
334 }
335}
336
337fn spawn_new_update_tasks(
338 editor: &mut Editor,
339 excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Range<usize>)>,
340 invalidation_strategy: InvalidationStrategy,
341 update_cache_version: usize,
342 cx: &mut ViewContext<'_, '_, Editor>,
343) {
344 let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
345 for (excerpt_id, (buffer_handle, excerpt_visible_range)) in excerpts_to_query {
346 if !excerpt_visible_range.is_empty() {
347 let buffer = buffer_handle.read(cx);
348 let buffer_snapshot = buffer.snapshot();
349 let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
350 if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
351 let new_task_buffer_version = buffer_snapshot.version();
352 let cached_excerpt_hints = cached_excerpt_hints.read();
353 let cached_buffer_version = &cached_excerpt_hints.buffer_version;
354 if cached_excerpt_hints.version > update_cache_version
355 || cached_buffer_version.changed_since(new_task_buffer_version)
356 {
357 return;
358 }
359 if !new_task_buffer_version.changed_since(&cached_buffer_version)
360 && !matches!(
361 invalidation_strategy,
362 InvalidationStrategy::RefreshRequested
363 )
364 {
365 return;
366 }
367 };
368
369 let buffer_id = buffer.remote_id();
370 let excerpt_visible_range_start = buffer.anchor_before(excerpt_visible_range.start);
371 let excerpt_visible_range_end = buffer.anchor_after(excerpt_visible_range.end);
372
373 let (multi_buffer_snapshot, full_excerpt_range) =
374 editor.buffer.update(cx, |multi_buffer, cx| {
375 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
376 (
377 multi_buffer_snapshot,
378 multi_buffer
379 .excerpts_for_buffer(&buffer_handle, cx)
380 .into_iter()
381 .find(|(id, _)| id == &excerpt_id)
382 .map(|(_, range)| range.context),
383 )
384 });
385
386 if let Some(full_excerpt_range) = full_excerpt_range {
387 let query = ExcerptQuery {
388 buffer_id,
389 excerpt_id,
390 dimensions: ExcerptDimensions {
391 excerpt_range_start: full_excerpt_range.start,
392 excerpt_range_end: full_excerpt_range.end,
393 excerpt_visible_range_start,
394 excerpt_visible_range_end,
395 },
396 cache_version: update_cache_version,
397 invalidate: invalidation_strategy,
398 };
399
400 let new_update_task = |is_refresh_after_regular_task| {
401 new_update_task(
402 query,
403 multi_buffer_snapshot,
404 buffer_snapshot,
405 Arc::clone(&visible_hints),
406 cached_excerpt_hints,
407 is_refresh_after_regular_task,
408 cx,
409 )
410 };
411 match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
412 hash_map::Entry::Occupied(mut o) => {
413 let update_task = o.get_mut();
414 match (update_task.invalidation_strategy, invalidation_strategy) {
415 (_, InvalidationStrategy::None) => {}
416 (InvalidationStrategy::RefreshRequested, _)
417 | (_, InvalidationStrategy::ExcerptEdited)
418 | (
419 InvalidationStrategy::None,
420 InvalidationStrategy::RefreshRequested,
421 ) => {
422 o.insert(UpdateTask {
423 invalidation_strategy,
424 cache_version: query.cache_version,
425 _task: new_update_task(false).shared(),
426 pending_refresh: None,
427 });
428 }
429 (_, InvalidationStrategy::RefreshRequested) => {
430 let pending_fetch = o.get()._task.clone();
431 let refresh_task = new_update_task(true);
432 o.get_mut().pending_refresh =
433 Some(cx.background().spawn(async move {
434 pending_fetch.await;
435 refresh_task.await
436 }));
437 }
438 }
439 }
440 hash_map::Entry::Vacant(v) => {
441 v.insert(UpdateTask {
442 invalidation_strategy,
443 cache_version: query.cache_version,
444 _task: new_update_task(false).shared(),
445 pending_refresh: None,
446 });
447 }
448 }
449 }
450 }
451 }
452}
453
454fn new_update_task(
455 query: ExcerptQuery,
456 multi_buffer_snapshot: MultiBufferSnapshot,
457 buffer_snapshot: BufferSnapshot,
458 visible_hints: Arc<Vec<Inlay>>,
459 cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
460 is_refresh_after_regular_task: bool,
461 cx: &mut ViewContext<'_, '_, Editor>,
462) -> Task<()> {
463 let hints_fetch_tasks = query.hints_fetch_ranges(&buffer_snapshot);
464 cx.spawn(|editor, cx| async move {
465 let create_update_task = |range| {
466 fetch_and_update_hints(
467 editor.clone(),
468 multi_buffer_snapshot.clone(),
469 buffer_snapshot.clone(),
470 Arc::clone(&visible_hints),
471 cached_excerpt_hints.as_ref().map(Arc::clone),
472 query,
473 range,
474 cx.clone(),
475 )
476 };
477
478 if is_refresh_after_regular_task {
479 let visible_range_has_updates =
480 match create_update_task(hints_fetch_tasks.visible_range).await {
481 Ok(updated) => updated,
482 Err(e) => {
483 error!("inlay hint visible range update task failed: {e:#}");
484 return;
485 }
486 };
487
488 if visible_range_has_updates {
489 let other_update_results = futures::future::join_all(
490 hints_fetch_tasks
491 .other_ranges
492 .into_iter()
493 .map(create_update_task),
494 )
495 .await;
496
497 for result in other_update_results {
498 if let Err(e) = result {
499 error!("inlay hint update task failed: {e:#}");
500 return;
501 }
502 }
503 }
504 } else {
505 let task_update_results = futures::future::join_all(
506 std::iter::once(hints_fetch_tasks.visible_range)
507 .chain(hints_fetch_tasks.other_ranges.into_iter())
508 .map(create_update_task),
509 )
510 .await;
511
512 for result in task_update_results {
513 if let Err(e) = result {
514 error!("inlay hint update task failed: {e:#}");
515 }
516 }
517 }
518 })
519}
520
521async fn fetch_and_update_hints(
522 editor: gpui::WeakViewHandle<Editor>,
523 multi_buffer_snapshot: MultiBufferSnapshot,
524 buffer_snapshot: BufferSnapshot,
525 visible_hints: Arc<Vec<Inlay>>,
526 cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
527 query: ExcerptQuery,
528 fetch_range: Range<language::Anchor>,
529 mut cx: gpui::AsyncAppContext,
530) -> anyhow::Result<bool> {
531 let inlay_hints_fetch_task = editor
532 .update(&mut cx, |editor, cx| {
533 editor
534 .buffer()
535 .read(cx)
536 .buffer(query.buffer_id)
537 .and_then(|buffer| {
538 let project = editor.project.as_ref()?;
539 Some(project.update(cx, |project, cx| {
540 project.inlay_hints(buffer, fetch_range.clone(), cx)
541 }))
542 })
543 })
544 .ok()
545 .flatten();
546 let mut update_happened = false;
547 let Some(inlay_hints_fetch_task) = inlay_hints_fetch_task else { return Ok(update_happened) };
548
549 let new_hints = inlay_hints_fetch_task
550 .await
551 .context("inlay hint fetch task")?;
552 let background_task_buffer_snapshot = buffer_snapshot.clone();
553 let backround_fetch_range = fetch_range.clone();
554 if let Some(new_update) = cx
555 .background()
556 .spawn(async move {
557 calculate_hint_updates(
558 query,
559 backround_fetch_range,
560 new_hints,
561 &background_task_buffer_snapshot,
562 cached_excerpt_hints,
563 &visible_hints,
564 )
565 })
566 .await
567 {
568 update_happened = !new_update.add_to_cache.is_empty()
569 || !new_update.remove_from_cache.is_empty()
570 || !new_update.remove_from_visible.is_empty();
571 editor
572 .update(&mut cx, |editor, cx| {
573 let cached_excerpt_hints = editor
574 .inlay_hint_cache
575 .hints
576 .entry(new_update.excerpt_id)
577 .or_insert_with(|| {
578 Arc::new(RwLock::new(CachedExcerptHints {
579 version: new_update.cache_version,
580 buffer_version: buffer_snapshot.version().clone(),
581 buffer_id: query.buffer_id,
582 hints: Vec::new(),
583 }))
584 });
585 let mut cached_excerpt_hints = cached_excerpt_hints.write();
586 match new_update.cache_version.cmp(&cached_excerpt_hints.version) {
587 cmp::Ordering::Less => return,
588 cmp::Ordering::Greater | cmp::Ordering::Equal => {
589 cached_excerpt_hints.version = new_update.cache_version;
590 }
591 }
592 cached_excerpt_hints
593 .hints
594 .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
595 cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
596 editor.inlay_hint_cache.version += 1;
597
598 let mut splice = InlaySplice {
599 to_remove: new_update.remove_from_visible,
600 to_insert: Vec::new(),
601 };
602
603 for new_hint in new_update.add_to_cache {
604 let new_hint_position = multi_buffer_snapshot
605 .anchor_in_excerpt(query.excerpt_id, new_hint.position);
606 let new_inlay_id = InlayId::Hint(post_inc(&mut editor.next_inlay_id));
607 if editor
608 .inlay_hint_cache
609 .allowed_hint_kinds
610 .contains(&new_hint.kind)
611 {
612 splice
613 .to_insert
614 .push((new_hint_position, new_inlay_id, new_hint.clone()));
615 }
616
617 cached_excerpt_hints.hints.push((new_inlay_id, new_hint));
618 }
619
620 cached_excerpt_hints
621 .hints
622 .sort_by(|(_, hint_a), (_, hint_b)| {
623 hint_a.position.cmp(&hint_b.position, &buffer_snapshot)
624 });
625 drop(cached_excerpt_hints);
626
627 if query.should_invalidate() {
628 let mut outdated_excerpt_caches = HashSet::default();
629 for (excerpt_id, excerpt_hints) in editor.inlay_hint_cache().hints.iter() {
630 let excerpt_hints = excerpt_hints.read();
631 if excerpt_hints.buffer_id == query.buffer_id
632 && excerpt_id != &query.excerpt_id
633 && buffer_snapshot
634 .version()
635 .changed_since(&excerpt_hints.buffer_version)
636 {
637 outdated_excerpt_caches.insert(*excerpt_id);
638 splice
639 .to_remove
640 .extend(excerpt_hints.hints.iter().map(|(id, _)| id));
641 }
642 }
643 editor
644 .inlay_hint_cache
645 .hints
646 .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
647 }
648
649 let InlaySplice {
650 to_remove,
651 to_insert,
652 } = splice;
653 if !to_remove.is_empty() || !to_insert.is_empty() {
654 editor.splice_inlay_hints(to_remove, to_insert, cx)
655 }
656 })
657 .ok();
658 }
659
660 Ok(update_happened)
661}
662
663fn calculate_hint_updates(
664 query: ExcerptQuery,
665 fetch_range: Range<language::Anchor>,
666 new_excerpt_hints: Vec<InlayHint>,
667 buffer_snapshot: &BufferSnapshot,
668 cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
669 visible_hints: &[Inlay],
670) -> Option<ExcerptHintsUpdate> {
671 let mut add_to_cache: HashSet<InlayHint> = HashSet::default();
672 let mut excerpt_hints_to_persist = HashMap::default();
673 for new_hint in new_excerpt_hints {
674 if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) {
675 continue;
676 }
677 let missing_from_cache = match &cached_excerpt_hints {
678 Some(cached_excerpt_hints) => {
679 let cached_excerpt_hints = cached_excerpt_hints.read();
680 match cached_excerpt_hints.hints.binary_search_by(|probe| {
681 probe.1.position.cmp(&new_hint.position, buffer_snapshot)
682 }) {
683 Ok(ix) => {
684 let (cached_inlay_id, cached_hint) = &cached_excerpt_hints.hints[ix];
685 if cached_hint == &new_hint {
686 excerpt_hints_to_persist.insert(*cached_inlay_id, cached_hint.kind);
687 false
688 } else {
689 true
690 }
691 }
692 Err(_) => true,
693 }
694 }
695 None => true,
696 };
697 if missing_from_cache {
698 add_to_cache.insert(new_hint);
699 }
700 }
701
702 let mut remove_from_visible = Vec::new();
703 let mut remove_from_cache = HashSet::default();
704 if query.should_invalidate() {
705 remove_from_visible.extend(
706 visible_hints
707 .iter()
708 .filter(|hint| hint.position.excerpt_id == query.excerpt_id)
709 .filter(|hint| {
710 contains_position(&fetch_range, hint.position.text_anchor, buffer_snapshot)
711 })
712 .filter(|hint| {
713 fetch_range
714 .start
715 .cmp(&hint.position.text_anchor, buffer_snapshot)
716 .is_le()
717 && fetch_range
718 .end
719 .cmp(&hint.position.text_anchor, buffer_snapshot)
720 .is_ge()
721 })
722 .map(|inlay_hint| inlay_hint.id)
723 .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
724 );
725
726 if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
727 let cached_excerpt_hints = cached_excerpt_hints.read();
728 remove_from_cache.extend(
729 cached_excerpt_hints
730 .hints
731 .iter()
732 .filter(|(cached_inlay_id, _)| {
733 !excerpt_hints_to_persist.contains_key(cached_inlay_id)
734 })
735 .filter(|(_, cached_hint)| {
736 fetch_range
737 .start
738 .cmp(&cached_hint.position, buffer_snapshot)
739 .is_le()
740 && fetch_range
741 .end
742 .cmp(&cached_hint.position, buffer_snapshot)
743 .is_ge()
744 })
745 .map(|(cached_inlay_id, _)| *cached_inlay_id),
746 );
747 }
748 }
749
750 if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
751 None
752 } else {
753 Some(ExcerptHintsUpdate {
754 cache_version: query.cache_version,
755 excerpt_id: query.excerpt_id,
756 remove_from_visible,
757 remove_from_cache,
758 add_to_cache,
759 })
760 }
761}
762
763struct HintFetchRanges {
764 visible_range: Range<language::Anchor>,
765 other_ranges: Vec<Range<language::Anchor>>,
766}
767
768fn contains_position(
769 range: &Range<language::Anchor>,
770 position: language::Anchor,
771 buffer_snapshot: &BufferSnapshot,
772) -> bool {
773 range.start.cmp(&position, buffer_snapshot).is_le()
774 && range.end.cmp(&position, buffer_snapshot).is_ge()
775}
776
777#[cfg(test)]
778mod tests {
779 use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
780
781 use crate::{
782 scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
783 serde_json::json,
784 ExcerptRange, InlayHintSettings,
785 };
786 use futures::StreamExt;
787 use gpui::{TestAppContext, ViewHandle};
788 use language::{
789 language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
790 };
791 use lsp::FakeLanguageServer;
792 use parking_lot::Mutex;
793 use project::{FakeFs, Project};
794 use settings::SettingsStore;
795 use text::Point;
796 use workspace::Workspace;
797
798 use crate::editor_tests::update_test_settings;
799
800 use super::*;
801
802 #[gpui::test]
803 async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
804 let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
805 init_test(cx, |settings| {
806 settings.defaults.inlay_hints = Some(InlayHintSettings {
807 enabled: true,
808 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
809 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
810 show_other_hints: allowed_hint_kinds.contains(&None),
811 })
812 });
813
814 cx.foreground().start_waiting();
815 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
816 let lsp_request_count = Arc::new(AtomicU32::new(0));
817 fake_server
818 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
819 let task_lsp_request_count = Arc::clone(&lsp_request_count);
820 async move {
821 assert_eq!(
822 params.text_document.uri,
823 lsp::Url::from_file_path(file_with_hints).unwrap(),
824 );
825 let current_call_id =
826 Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
827 let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
828 for _ in 0..2 {
829 let mut i = current_call_id;
830 loop {
831 new_hints.push(lsp::InlayHint {
832 position: lsp::Position::new(0, i),
833 label: lsp::InlayHintLabel::String(i.to_string()),
834 kind: None,
835 text_edits: None,
836 tooltip: None,
837 padding_left: None,
838 padding_right: None,
839 data: None,
840 });
841 if i == 0 {
842 break;
843 }
844 i -= 1;
845 }
846 }
847
848 Ok(Some(new_hints))
849 }
850 })
851 .next()
852 .await;
853 cx.foreground().finish_waiting();
854 cx.foreground().run_until_parked();
855
856 let mut edits_made = 1;
857 editor.update(cx, |editor, cx| {
858 let expected_layers = vec!["0".to_string()];
859 assert_eq!(
860 expected_layers,
861 cached_hint_labels(editor),
862 "Should get its first hints when opening the editor"
863 );
864 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
865 let inlay_cache = editor.inlay_hint_cache();
866 assert_eq!(
867 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
868 "Cache should use editor settings to get the allowed hint kinds"
869 );
870 assert_eq!(
871 inlay_cache.version, edits_made,
872 "The editor update the cache version after every cache/view change"
873 );
874 });
875
876 editor.update(cx, |editor, cx| {
877 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
878 editor.handle_input("some change", cx);
879 edits_made += 1;
880 });
881 cx.foreground().run_until_parked();
882 editor.update(cx, |editor, cx| {
883 let expected_layers = vec!["0".to_string(), "1".to_string()];
884 assert_eq!(
885 expected_layers,
886 cached_hint_labels(editor),
887 "Should get new hints after an edit"
888 );
889 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
890 let inlay_cache = editor.inlay_hint_cache();
891 assert_eq!(
892 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
893 "Cache should use editor settings to get the allowed hint kinds"
894 );
895 assert_eq!(
896 inlay_cache.version, edits_made,
897 "The editor update the cache version after every cache/view change"
898 );
899 });
900
901 fake_server
902 .request::<lsp::request::InlayHintRefreshRequest>(())
903 .await
904 .expect("inlay refresh request failed");
905 edits_made += 1;
906 cx.foreground().run_until_parked();
907 editor.update(cx, |editor, cx| {
908 let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()];
909 assert_eq!(
910 expected_layers,
911 cached_hint_labels(editor),
912 "Should get new hints after hint refresh/ request"
913 );
914 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
915 let inlay_cache = editor.inlay_hint_cache();
916 assert_eq!(
917 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
918 "Cache should use editor settings to get the allowed hint kinds"
919 );
920 assert_eq!(
921 inlay_cache.version, edits_made,
922 "The editor update the cache version after every cache/view change"
923 );
924 });
925 }
926
927 #[gpui::test]
928 async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
929 let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
930 init_test(cx, |settings| {
931 settings.defaults.inlay_hints = Some(InlayHintSettings {
932 enabled: true,
933 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
934 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
935 show_other_hints: allowed_hint_kinds.contains(&None),
936 })
937 });
938
939 cx.foreground().start_waiting();
940 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
941 let lsp_request_count = Arc::new(AtomicU32::new(0));
942 let another_lsp_request_count = Arc::clone(&lsp_request_count);
943 fake_server
944 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
945 let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
946 async move {
947 Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
948 assert_eq!(
949 params.text_document.uri,
950 lsp::Url::from_file_path(file_with_hints).unwrap(),
951 );
952 Ok(Some(vec![
953 lsp::InlayHint {
954 position: lsp::Position::new(0, 1),
955 label: lsp::InlayHintLabel::String("type hint".to_string()),
956 kind: Some(lsp::InlayHintKind::TYPE),
957 text_edits: None,
958 tooltip: None,
959 padding_left: None,
960 padding_right: None,
961 data: None,
962 },
963 lsp::InlayHint {
964 position: lsp::Position::new(0, 2),
965 label: lsp::InlayHintLabel::String("parameter hint".to_string()),
966 kind: Some(lsp::InlayHintKind::PARAMETER),
967 text_edits: None,
968 tooltip: None,
969 padding_left: None,
970 padding_right: None,
971 data: None,
972 },
973 lsp::InlayHint {
974 position: lsp::Position::new(0, 3),
975 label: lsp::InlayHintLabel::String("other hint".to_string()),
976 kind: None,
977 text_edits: None,
978 tooltip: None,
979 padding_left: None,
980 padding_right: None,
981 data: None,
982 },
983 ]))
984 }
985 })
986 .next()
987 .await;
988 cx.foreground().finish_waiting();
989 cx.foreground().run_until_parked();
990
991 let mut edits_made = 1;
992 editor.update(cx, |editor, cx| {
993 assert_eq!(
994 lsp_request_count.load(Ordering::Relaxed),
995 1,
996 "Should query new hints once"
997 );
998 assert_eq!(
999 vec![
1000 "other hint".to_string(),
1001 "parameter hint".to_string(),
1002 "type hint".to_string(),
1003 ],
1004 cached_hint_labels(editor),
1005 "Should get its first hints when opening the editor"
1006 );
1007 assert_eq!(
1008 vec!["other hint".to_string(), "type hint".to_string()],
1009 visible_hint_labels(editor, cx)
1010 );
1011 let inlay_cache = editor.inlay_hint_cache();
1012 assert_eq!(
1013 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1014 "Cache should use editor settings to get the allowed hint kinds"
1015 );
1016 assert_eq!(
1017 inlay_cache.version, edits_made,
1018 "The editor update the cache version after every cache/view change"
1019 );
1020 });
1021
1022 fake_server
1023 .request::<lsp::request::InlayHintRefreshRequest>(())
1024 .await
1025 .expect("inlay refresh request failed");
1026 cx.foreground().run_until_parked();
1027 editor.update(cx, |editor, cx| {
1028 assert_eq!(
1029 lsp_request_count.load(Ordering::Relaxed),
1030 2,
1031 "Should load new hints twice"
1032 );
1033 assert_eq!(
1034 vec![
1035 "other hint".to_string(),
1036 "parameter hint".to_string(),
1037 "type hint".to_string(),
1038 ],
1039 cached_hint_labels(editor),
1040 "Cached hints should not change due to allowed hint kinds settings update"
1041 );
1042 assert_eq!(
1043 vec!["other hint".to_string(), "type hint".to_string()],
1044 visible_hint_labels(editor, cx)
1045 );
1046 let inlay_cache = editor.inlay_hint_cache();
1047 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1048 assert_eq!(
1049 inlay_cache.version, edits_made,
1050 "Should not update cache version due to new loaded hints being the same"
1051 );
1052 });
1053
1054 for (new_allowed_hint_kinds, expected_visible_hints) in [
1055 (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1056 (
1057 HashSet::from_iter([Some(InlayHintKind::Type)]),
1058 vec!["type hint".to_string()],
1059 ),
1060 (
1061 HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1062 vec!["parameter hint".to_string()],
1063 ),
1064 (
1065 HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1066 vec!["other hint".to_string(), "type hint".to_string()],
1067 ),
1068 (
1069 HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1070 vec!["other hint".to_string(), "parameter hint".to_string()],
1071 ),
1072 (
1073 HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1074 vec!["parameter hint".to_string(), "type hint".to_string()],
1075 ),
1076 (
1077 HashSet::from_iter([
1078 None,
1079 Some(InlayHintKind::Type),
1080 Some(InlayHintKind::Parameter),
1081 ]),
1082 vec![
1083 "other hint".to_string(),
1084 "parameter hint".to_string(),
1085 "type hint".to_string(),
1086 ],
1087 ),
1088 ] {
1089 edits_made += 1;
1090 update_test_settings(cx, |settings| {
1091 settings.defaults.inlay_hints = Some(InlayHintSettings {
1092 enabled: true,
1093 show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1094 show_parameter_hints: new_allowed_hint_kinds
1095 .contains(&Some(InlayHintKind::Parameter)),
1096 show_other_hints: new_allowed_hint_kinds.contains(&None),
1097 })
1098 });
1099 cx.foreground().run_until_parked();
1100 editor.update(cx, |editor, cx| {
1101 assert_eq!(
1102 lsp_request_count.load(Ordering::Relaxed),
1103 2,
1104 "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1105 );
1106 assert_eq!(
1107 vec![
1108 "other hint".to_string(),
1109 "parameter hint".to_string(),
1110 "type hint".to_string(),
1111 ],
1112 cached_hint_labels(editor),
1113 "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1114 );
1115 assert_eq!(
1116 expected_visible_hints,
1117 visible_hint_labels(editor, cx),
1118 "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1119 );
1120 let inlay_cache = editor.inlay_hint_cache();
1121 assert_eq!(
1122 inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1123 "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1124 );
1125 assert_eq!(
1126 inlay_cache.version, edits_made,
1127 "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1128 );
1129 });
1130 }
1131
1132 edits_made += 1;
1133 let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1134 update_test_settings(cx, |settings| {
1135 settings.defaults.inlay_hints = Some(InlayHintSettings {
1136 enabled: false,
1137 show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1138 show_parameter_hints: another_allowed_hint_kinds
1139 .contains(&Some(InlayHintKind::Parameter)),
1140 show_other_hints: another_allowed_hint_kinds.contains(&None),
1141 })
1142 });
1143 cx.foreground().run_until_parked();
1144 editor.update(cx, |editor, cx| {
1145 assert_eq!(
1146 lsp_request_count.load(Ordering::Relaxed),
1147 2,
1148 "Should not load new hints when hints got disabled"
1149 );
1150 assert!(
1151 cached_hint_labels(editor).is_empty(),
1152 "Should clear the cache when hints got disabled"
1153 );
1154 assert!(
1155 visible_hint_labels(editor, cx).is_empty(),
1156 "Should clear visible hints when hints got disabled"
1157 );
1158 let inlay_cache = editor.inlay_hint_cache();
1159 assert_eq!(
1160 inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1161 "Should update its allowed hint kinds even when hints got disabled"
1162 );
1163 assert_eq!(
1164 inlay_cache.version, edits_made,
1165 "The editor should update the cache version after hints got disabled"
1166 );
1167 });
1168
1169 fake_server
1170 .request::<lsp::request::InlayHintRefreshRequest>(())
1171 .await
1172 .expect("inlay refresh request failed");
1173 cx.foreground().run_until_parked();
1174 editor.update(cx, |editor, cx| {
1175 assert_eq!(
1176 lsp_request_count.load(Ordering::Relaxed),
1177 2,
1178 "Should not load new hints when they got disabled"
1179 );
1180 assert!(cached_hint_labels(editor).is_empty());
1181 assert!(visible_hint_labels(editor, cx).is_empty());
1182 let inlay_cache = editor.inlay_hint_cache();
1183 assert_eq!(inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds);
1184 assert_eq!(
1185 inlay_cache.version, edits_made,
1186 "The editor should not update the cache version after /refresh query without updates"
1187 );
1188 });
1189
1190 let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1191 edits_made += 1;
1192 update_test_settings(cx, |settings| {
1193 settings.defaults.inlay_hints = Some(InlayHintSettings {
1194 enabled: true,
1195 show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1196 show_parameter_hints: final_allowed_hint_kinds
1197 .contains(&Some(InlayHintKind::Parameter)),
1198 show_other_hints: final_allowed_hint_kinds.contains(&None),
1199 })
1200 });
1201 cx.foreground().run_until_parked();
1202 editor.update(cx, |editor, cx| {
1203 assert_eq!(
1204 lsp_request_count.load(Ordering::Relaxed),
1205 3,
1206 "Should query for new hints when they got reenabled"
1207 );
1208 assert_eq!(
1209 vec![
1210 "other hint".to_string(),
1211 "parameter hint".to_string(),
1212 "type hint".to_string(),
1213 ],
1214 cached_hint_labels(editor),
1215 "Should get its cached hints fully repopulated after the hints got reenabled"
1216 );
1217 assert_eq!(
1218 vec!["parameter hint".to_string()],
1219 visible_hint_labels(editor, cx),
1220 "Should get its visible hints repopulated and filtered after the h"
1221 );
1222 let inlay_cache = editor.inlay_hint_cache();
1223 assert_eq!(
1224 inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1225 "Cache should update editor settings when hints got reenabled"
1226 );
1227 assert_eq!(
1228 inlay_cache.version, edits_made,
1229 "Cache should update its version after hints got reenabled"
1230 );
1231 });
1232
1233 fake_server
1234 .request::<lsp::request::InlayHintRefreshRequest>(())
1235 .await
1236 .expect("inlay refresh request failed");
1237 cx.foreground().run_until_parked();
1238 editor.update(cx, |editor, cx| {
1239 assert_eq!(
1240 lsp_request_count.load(Ordering::Relaxed),
1241 4,
1242 "Should query for new hints again"
1243 );
1244 assert_eq!(
1245 vec![
1246 "other hint".to_string(),
1247 "parameter hint".to_string(),
1248 "type hint".to_string(),
1249 ],
1250 cached_hint_labels(editor),
1251 );
1252 assert_eq!(
1253 vec!["parameter hint".to_string()],
1254 visible_hint_labels(editor, cx),
1255 );
1256 let inlay_cache = editor.inlay_hint_cache();
1257 assert_eq!(inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,);
1258 assert_eq!(inlay_cache.version, edits_made);
1259 });
1260 }
1261
1262 #[gpui::test]
1263 async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
1264 let allowed_hint_kinds = HashSet::from_iter([None]);
1265 init_test(cx, |settings| {
1266 settings.defaults.inlay_hints = Some(InlayHintSettings {
1267 enabled: true,
1268 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1269 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1270 show_other_hints: allowed_hint_kinds.contains(&None),
1271 })
1272 });
1273
1274 cx.foreground().start_waiting();
1275 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1276 let fake_server = Arc::new(fake_server);
1277 let lsp_request_count = Arc::new(AtomicU32::new(0));
1278 let another_lsp_request_count = Arc::clone(&lsp_request_count);
1279 fake_server
1280 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1281 let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1282 async move {
1283 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1284 assert_eq!(
1285 params.text_document.uri,
1286 lsp::Url::from_file_path(file_with_hints).unwrap(),
1287 );
1288 Ok(Some(vec![lsp::InlayHint {
1289 position: lsp::Position::new(0, i),
1290 label: lsp::InlayHintLabel::String(i.to_string()),
1291 kind: None,
1292 text_edits: None,
1293 tooltip: None,
1294 padding_left: None,
1295 padding_right: None,
1296 data: None,
1297 }]))
1298 }
1299 })
1300 .next()
1301 .await;
1302
1303 let mut expected_changes = Vec::new();
1304 for change_after_opening in [
1305 "initial change #1",
1306 "initial change #2",
1307 "initial change #3",
1308 ] {
1309 editor.update(cx, |editor, cx| {
1310 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1311 editor.handle_input(change_after_opening, cx);
1312 });
1313 expected_changes.push(change_after_opening);
1314 }
1315
1316 cx.foreground().finish_waiting();
1317 cx.foreground().run_until_parked();
1318
1319 editor.update(cx, |editor, cx| {
1320 let current_text = editor.text(cx);
1321 for change in &expected_changes {
1322 assert!(
1323 current_text.contains(change),
1324 "Should apply all changes made"
1325 );
1326 }
1327 assert_eq!(
1328 lsp_request_count.load(Ordering::Relaxed),
1329 2,
1330 "Should query new hints twice: for editor init and for the last edit that interrupted all others"
1331 );
1332 let expected_hints = vec!["2".to_string()];
1333 assert_eq!(
1334 expected_hints,
1335 cached_hint_labels(editor),
1336 "Should get hints from the last edit landed only"
1337 );
1338 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1339 let inlay_cache = editor.inlay_hint_cache();
1340 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1341 assert_eq!(
1342 inlay_cache.version, 1,
1343 "Only one update should be registered in the cache after all cancellations"
1344 );
1345 });
1346
1347 let mut edits = Vec::new();
1348 for async_later_change in [
1349 "another change #1",
1350 "another change #2",
1351 "another change #3",
1352 ] {
1353 expected_changes.push(async_later_change);
1354 let task_editor = editor.clone();
1355 let mut task_cx = cx.clone();
1356 edits.push(cx.foreground().spawn(async move {
1357 task_editor.update(&mut task_cx, |editor, cx| {
1358 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1359 editor.handle_input(async_later_change, cx);
1360 });
1361 }));
1362 }
1363 let _ = futures::future::join_all(edits).await;
1364 cx.foreground().run_until_parked();
1365
1366 editor.update(cx, |editor, cx| {
1367 let current_text = editor.text(cx);
1368 for change in &expected_changes {
1369 assert!(
1370 current_text.contains(change),
1371 "Should apply all changes made"
1372 );
1373 }
1374 assert_eq!(
1375 lsp_request_count.load(Ordering::Relaxed),
1376 3,
1377 "Should query new hints one more time, for the last edit only"
1378 );
1379 let expected_hints = vec!["3".to_string()];
1380 assert_eq!(
1381 expected_hints,
1382 cached_hint_labels(editor),
1383 "Should get hints from the last edit landed only"
1384 );
1385 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1386 let inlay_cache = editor.inlay_hint_cache();
1387 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1388 assert_eq!(
1389 inlay_cache.version, 2,
1390 "Should update the cache version once more, for the new change"
1391 );
1392 });
1393 }
1394
1395 #[gpui::test]
1396 async fn test_hint_refresh_request_cancellation(cx: &mut gpui::TestAppContext) {
1397 let allowed_hint_kinds = HashSet::from_iter([None]);
1398 init_test(cx, |settings| {
1399 settings.defaults.inlay_hints = Some(InlayHintSettings {
1400 enabled: true,
1401 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1402 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1403 show_other_hints: allowed_hint_kinds.contains(&None),
1404 })
1405 });
1406
1407 cx.foreground().start_waiting();
1408 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1409 let fake_server = Arc::new(fake_server);
1410 let lsp_request_count = Arc::new(AtomicU32::new(0));
1411 let another_lsp_request_count = Arc::clone(&lsp_request_count);
1412 fake_server
1413 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1414 let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1415 async move {
1416 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1417 assert_eq!(
1418 params.text_document.uri,
1419 lsp::Url::from_file_path(file_with_hints).unwrap(),
1420 );
1421 Ok(Some(vec![lsp::InlayHint {
1422 position: lsp::Position::new(0, i),
1423 label: lsp::InlayHintLabel::String(i.to_string()),
1424 kind: None,
1425 text_edits: None,
1426 tooltip: None,
1427 padding_left: None,
1428 padding_right: None,
1429 data: None,
1430 }]))
1431 }
1432 })
1433 .next()
1434 .await;
1435
1436 let mut initial_refresh_tasks = Vec::new();
1437 let task_cx = cx.clone();
1438 let add_refresh_task = |tasks: &mut Vec<Task<()>>| {
1439 let task_fake_server = Arc::clone(&fake_server);
1440 tasks.push(task_cx.foreground().spawn(async move {
1441 task_fake_server
1442 .request::<lsp::request::InlayHintRefreshRequest>(())
1443 .await
1444 .expect("inlay refresh request failed");
1445 }))
1446 };
1447 add_refresh_task(&mut initial_refresh_tasks);
1448 add_refresh_task(&mut initial_refresh_tasks);
1449 let _ = futures::future::join_all(initial_refresh_tasks).await;
1450
1451 cx.foreground().finish_waiting();
1452 cx.foreground().run_until_parked();
1453
1454 editor.update(cx, |editor, cx| {
1455 assert_eq!(
1456 lsp_request_count.load(Ordering::Relaxed),
1457 3,
1458 "Should query new hints once for editor opening and 2 times due to 2 refresh requests"
1459 );
1460 let expected_hints = vec!["3".to_string()];
1461 assert_eq!(
1462 expected_hints,
1463 cached_hint_labels(editor),
1464 "Should get hints from the last refresh landed only"
1465 );
1466 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1467 let inlay_cache = editor.inlay_hint_cache();
1468 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1469 assert_eq!(
1470 inlay_cache.version, 1,
1471 "Only one update should be registered in the cache after all cancellations"
1472 );
1473 });
1474
1475 let mut expected_changes = Vec::new();
1476 let mut edits_and_refreshes = Vec::new();
1477 add_refresh_task(&mut edits_and_refreshes);
1478 for async_later_change in ["change #1", "change #2", "change #3"] {
1479 expected_changes.push(async_later_change);
1480 let task_editor = editor.clone();
1481 let mut task_cx = cx.clone();
1482 let task_fake_server = Arc::clone(&fake_server);
1483 edits_and_refreshes.push(cx.foreground().spawn(async move {
1484 task_fake_server
1485 .request::<lsp::request::InlayHintRefreshRequest>(())
1486 .await
1487 .expect("inlay refresh request failed");
1488 task_editor.update(&mut task_cx, |editor, cx| {
1489 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1490 editor.handle_input(async_later_change, cx);
1491 });
1492 task_fake_server
1493 .request::<lsp::request::InlayHintRefreshRequest>(())
1494 .await
1495 .expect("inlay refresh request failed");
1496 }));
1497 }
1498 let _ = futures::future::join_all(edits_and_refreshes).await;
1499 cx.foreground().run_until_parked();
1500
1501 editor.update(cx, |editor, cx| {
1502 let current_text = editor.text(cx);
1503 for change in &expected_changes {
1504 assert!(
1505 current_text.contains(change),
1506 "Should apply all changes made"
1507 );
1508 }
1509 assert_eq!(lsp_request_count.load(Ordering::Relaxed), 13);
1510 let expected_hints = vec!["13".to_string()];
1511 assert_eq!(
1512 expected_hints,
1513 cached_hint_labels(editor),
1514 "Should get hints from the last edit and refresh request only"
1515 );
1516 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1517 let inlay_cache = editor.inlay_hint_cache();
1518 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1519 assert_eq!(
1520 inlay_cache.version, 2,
1521 "Should update the cache version once since refresh did not get new hint updates"
1522 );
1523 });
1524
1525 let mut edits_and_refreshes = Vec::new();
1526 add_refresh_task(&mut edits_and_refreshes);
1527 for async_later_change in ["last change #1", "last change #2", "last change #3"] {
1528 expected_changes.push(async_later_change);
1529 let task_editor = editor.clone();
1530 let mut task_cx = cx.clone();
1531 add_refresh_task(&mut edits_and_refreshes);
1532 edits_and_refreshes.push(cx.foreground().spawn(async move {
1533 task_editor.update(&mut task_cx, |editor, cx| {
1534 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1535 editor.handle_input(async_later_change, cx);
1536 });
1537 }));
1538 }
1539 let _ = futures::future::join_all(edits_and_refreshes).await;
1540 cx.foreground().run_until_parked();
1541
1542 editor.update(cx, |editor, cx| {
1543 let current_text = editor.text(cx);
1544 for change in &expected_changes {
1545 assert!(
1546 current_text.contains(change),
1547 "Should apply all changes made"
1548 );
1549 }
1550 assert_eq!(
1551 lsp_request_count.load(Ordering::Relaxed),
1552 6,
1553 "Should query new hints once more, for last edit. All refresh tasks were before this edit hence should be cancelled."
1554 );
1555 let expected_hints = vec!["6".to_string()];
1556 assert_eq!(
1557 expected_hints,
1558 cached_hint_labels(editor),
1559 "Should get hints from the last edit only"
1560 );
1561 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1562 let inlay_cache = editor.inlay_hint_cache();
1563 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1564 assert_eq!(
1565 inlay_cache.version, 3,
1566 "Should update the cache version once due to the new change"
1567 );
1568 });
1569 }
1570
1571 #[gpui::test]
1572 async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
1573 let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1574 init_test(cx, |settings| {
1575 settings.defaults.inlay_hints = Some(InlayHintSettings {
1576 enabled: true,
1577 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1578 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1579 show_other_hints: allowed_hint_kinds.contains(&None),
1580 })
1581 });
1582
1583 let mut language = Language::new(
1584 LanguageConfig {
1585 name: "Rust".into(),
1586 path_suffixes: vec!["rs".to_string()],
1587 ..Default::default()
1588 },
1589 Some(tree_sitter_rust::language()),
1590 );
1591 let mut fake_servers = language
1592 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1593 capabilities: lsp::ServerCapabilities {
1594 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1595 ..Default::default()
1596 },
1597 ..Default::default()
1598 }))
1599 .await;
1600 let fs = FakeFs::new(cx.background());
1601 fs.insert_tree(
1602 "/a",
1603 json!({
1604 "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
1605 "other.rs": "// Test file",
1606 }),
1607 )
1608 .await;
1609 let project = Project::test(fs, ["/a".as_ref()], cx).await;
1610 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
1611 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
1612 let worktree_id = workspace.update(cx, |workspace, cx| {
1613 workspace.project().read_with(cx, |project, cx| {
1614 project.worktrees(cx).next().unwrap().read(cx).id()
1615 })
1616 });
1617
1618 cx.foreground().start_waiting();
1619 let editor = workspace
1620 .update(cx, |workspace, cx| {
1621 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1622 })
1623 .await
1624 .unwrap()
1625 .downcast::<Editor>()
1626 .unwrap();
1627 let fake_server = fake_servers.next().await.unwrap();
1628 let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
1629 let lsp_request_count = Arc::new(AtomicU32::new(0));
1630 let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
1631 let closure_lsp_request_count = Arc::clone(&lsp_request_count);
1632 fake_server
1633 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1634 let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
1635 let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
1636 async move {
1637 assert_eq!(
1638 params.text_document.uri,
1639 lsp::Url::from_file_path("/a/main.rs").unwrap(),
1640 );
1641
1642 task_lsp_request_ranges.lock().push(params.range);
1643 let query_start = params.range.start;
1644 let query_end = params.range.end;
1645 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1646 Ok(Some(vec![lsp::InlayHint {
1647 position: lsp::Position::new(
1648 (query_end.line - query_start.line) / 2,
1649 (query_end.character - query_start.character) / 2,
1650 ),
1651 label: lsp::InlayHintLabel::String(i.to_string()),
1652 kind: None,
1653 text_edits: None,
1654 tooltip: None,
1655 padding_left: None,
1656 padding_right: None,
1657 data: None,
1658 }]))
1659 }
1660 })
1661 .next()
1662 .await;
1663 cx.foreground().finish_waiting();
1664 cx.foreground().run_until_parked();
1665
1666 editor.update(cx, |editor, cx| {
1667 let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1668 ranges.sort_by_key(|range| range.start);
1669 assert_eq!(ranges.len(), 2, "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints");
1670 assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
1671 assert_eq!(ranges[0].end.line, ranges[1].start.line, "Both requests should be on the same line");
1672 assert_eq!(ranges[0].end.character + 1, ranges[1].start.character, "Both request should be concequent");
1673
1674 assert_eq!(lsp_request_count.load(Ordering::SeqCst), 2,
1675 "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints");
1676 let expected_layers = vec!["1".to_string(), "2".to_string()];
1677 assert_eq!(
1678 expected_layers,
1679 cached_hint_labels(editor),
1680 "Should have hints from both LSP requests made for a big file"
1681 );
1682 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1683 let inlay_cache = editor.inlay_hint_cache();
1684 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1685 assert_eq!(
1686 inlay_cache.version, 2,
1687 "Both LSP queries should've bumped the cache version"
1688 );
1689 });
1690
1691 editor.update(cx, |editor, cx| {
1692 editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1693 editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1694 editor.change_selections(None, cx, |s| s.select_ranges([600..600]));
1695 editor.handle_input("++++more text++++", cx);
1696 });
1697
1698 cx.foreground().run_until_parked();
1699 editor.update(cx, |editor, cx| {
1700 let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1701 ranges.sort_by_key(|range| range.start);
1702 assert_eq!(ranges.len(), 3, "When scroll is at the middle of a big document, its visible part + 2 other inbisible parts should be queried for hints");
1703 assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
1704 assert_eq!(ranges[0].end.line + 1, ranges[1].start.line, "Neighbour requests got on different lines due to the line end");
1705 assert_ne!(ranges[0].end.character, 0, "First query was in the end of the line, not in the beginning");
1706 assert_eq!(ranges[1].start.character, 0, "Second query got pushed into a new line and starts from the beginning");
1707 assert_eq!(ranges[1].end.line, ranges[2].start.line, "Neighbour requests should be on the same line");
1708 assert_eq!(ranges[1].end.character + 1, ranges[2].start.character, "Neighbour request should be concequent");
1709
1710 assert_eq!(lsp_request_count.load(Ordering::SeqCst), 5,
1711 "When scroll not at the edge of a big document, visible part + 2 other parts should be queried for hints");
1712 let expected_layers = vec!["4".to_string(), "5".to_string()];
1713 assert_eq!(expected_layers, cached_hint_labels(editor),
1714 "Should have hints from the new LSP response after edit");
1715 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1716 let inlay_cache = editor.inlay_hint_cache();
1717 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1718 assert_eq!(inlay_cache.version, 4, "Should update the cache for every LSP response with hints added");
1719 });
1720 }
1721
1722 #[gpui::test]
1723 async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
1724 let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1725 init_test(cx, |settings| {
1726 settings.defaults.inlay_hints = Some(InlayHintSettings {
1727 enabled: true,
1728 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1729 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1730 show_other_hints: allowed_hint_kinds.contains(&None),
1731 })
1732 });
1733
1734 let mut language = Language::new(
1735 LanguageConfig {
1736 name: "Rust".into(),
1737 path_suffixes: vec!["rs".to_string()],
1738 ..Default::default()
1739 },
1740 Some(tree_sitter_rust::language()),
1741 );
1742 let mut fake_servers = language
1743 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1744 capabilities: lsp::ServerCapabilities {
1745 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1746 ..Default::default()
1747 },
1748 ..Default::default()
1749 }))
1750 .await;
1751 let language = Arc::new(language);
1752 let fs = FakeFs::new(cx.background());
1753 fs.insert_tree(
1754 "/a",
1755 json!({
1756 "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
1757 "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
1758 }),
1759 )
1760 .await;
1761 let project = Project::test(fs, ["/a".as_ref()], cx).await;
1762 project.update(cx, |project, _| {
1763 project.languages().add(Arc::clone(&language))
1764 });
1765 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1766 let worktree_id = workspace.update(cx, |workspace, cx| {
1767 workspace.project().read_with(cx, |project, cx| {
1768 project.worktrees(cx).next().unwrap().read(cx).id()
1769 })
1770 });
1771
1772 let buffer_1 = project
1773 .update(cx, |project, cx| {
1774 project.open_buffer((worktree_id, "main.rs"), cx)
1775 })
1776 .await
1777 .unwrap();
1778 let buffer_2 = project
1779 .update(cx, |project, cx| {
1780 project.open_buffer((worktree_id, "other.rs"), cx)
1781 })
1782 .await
1783 .unwrap();
1784 let multibuffer = cx.add_model(|cx| {
1785 let mut multibuffer = MultiBuffer::new(0);
1786 multibuffer.push_excerpts(
1787 buffer_1.clone(),
1788 [
1789 ExcerptRange {
1790 context: Point::new(0, 0)..Point::new(2, 0),
1791 primary: None,
1792 },
1793 ExcerptRange {
1794 context: Point::new(4, 0)..Point::new(11, 0),
1795 primary: None,
1796 },
1797 ExcerptRange {
1798 context: Point::new(22, 0)..Point::new(33, 0),
1799 primary: None,
1800 },
1801 ExcerptRange {
1802 context: Point::new(44, 0)..Point::new(55, 0),
1803 primary: None,
1804 },
1805 ExcerptRange {
1806 context: Point::new(56, 0)..Point::new(66, 0),
1807 primary: None,
1808 },
1809 ExcerptRange {
1810 context: Point::new(67, 0)..Point::new(77, 0),
1811 primary: None,
1812 },
1813 ],
1814 cx,
1815 );
1816 multibuffer.push_excerpts(
1817 buffer_2.clone(),
1818 [
1819 ExcerptRange {
1820 context: Point::new(0, 1)..Point::new(2, 1),
1821 primary: None,
1822 },
1823 ExcerptRange {
1824 context: Point::new(4, 1)..Point::new(11, 1),
1825 primary: None,
1826 },
1827 ExcerptRange {
1828 context: Point::new(22, 1)..Point::new(33, 1),
1829 primary: None,
1830 },
1831 ExcerptRange {
1832 context: Point::new(44, 1)..Point::new(55, 1),
1833 primary: None,
1834 },
1835 ExcerptRange {
1836 context: Point::new(56, 1)..Point::new(66, 1),
1837 primary: None,
1838 },
1839 ExcerptRange {
1840 context: Point::new(67, 1)..Point::new(77, 1),
1841 primary: None,
1842 },
1843 ],
1844 cx,
1845 );
1846 multibuffer
1847 });
1848
1849 cx.foreground().start_waiting();
1850 let (_, editor) =
1851 cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
1852
1853 let editor_edited = Arc::new(AtomicBool::new(false));
1854 let fake_server = fake_servers.next().await.unwrap();
1855 let closure_editor_edited = Arc::clone(&editor_edited);
1856 fake_server
1857 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1858 let task_editor_edited = Arc::clone(&closure_editor_edited);
1859 async move {
1860 let hint_text = if params.text_document.uri
1861 == lsp::Url::from_file_path("/a/main.rs").unwrap()
1862 {
1863 "main hint"
1864 } else if params.text_document.uri
1865 == lsp::Url::from_file_path("/a/other.rs").unwrap()
1866 {
1867 "other hint"
1868 } else {
1869 panic!("unexpected uri: {:?}", params.text_document.uri);
1870 };
1871
1872 let positions = [
1873 lsp::Position::new(0, 2),
1874 lsp::Position::new(4, 2),
1875 lsp::Position::new(22, 2),
1876 lsp::Position::new(44, 2),
1877 lsp::Position::new(56, 2),
1878 lsp::Position::new(67, 2),
1879 ];
1880 let out_of_range_hint = lsp::InlayHint {
1881 position: lsp::Position::new(
1882 params.range.start.line + 99,
1883 params.range.start.character + 99,
1884 ),
1885 label: lsp::InlayHintLabel::String(
1886 "out of excerpt range, should be ignored".to_string(),
1887 ),
1888 kind: None,
1889 text_edits: None,
1890 tooltip: None,
1891 padding_left: None,
1892 padding_right: None,
1893 data: None,
1894 };
1895
1896 let edited = task_editor_edited.load(Ordering::Acquire);
1897 Ok(Some(
1898 std::iter::once(out_of_range_hint)
1899 .chain(positions.into_iter().enumerate().map(|(i, position)| {
1900 lsp::InlayHint {
1901 position,
1902 label: lsp::InlayHintLabel::String(format!(
1903 "{hint_text}{} #{i}",
1904 if edited { "(edited)" } else { "" },
1905 )),
1906 kind: None,
1907 text_edits: None,
1908 tooltip: None,
1909 padding_left: None,
1910 padding_right: None,
1911 data: None,
1912 }
1913 }))
1914 .collect(),
1915 ))
1916 }
1917 })
1918 .next()
1919 .await;
1920
1921 cx.foreground().finish_waiting();
1922 cx.foreground().run_until_parked();
1923
1924 editor.update(cx, |editor, cx| {
1925 let expected_layers = vec![
1926 "main hint #0".to_string(),
1927 "main hint #1".to_string(),
1928 "main hint #2".to_string(),
1929 "main hint #3".to_string(),
1930 ];
1931 assert_eq!(
1932 expected_layers,
1933 cached_hint_labels(editor),
1934 "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
1935 );
1936 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1937 let inlay_cache = editor.inlay_hint_cache();
1938 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1939 assert_eq!(inlay_cache.version, 4, "Every visible excerpt hints should bump the verison");
1940 });
1941
1942 editor.update(cx, |editor, cx| {
1943 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1944 s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
1945 });
1946 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1947 s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
1948 });
1949 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1950 s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
1951 });
1952 });
1953 cx.foreground().run_until_parked();
1954 editor.update(cx, |editor, cx| {
1955 let expected_layers = vec![
1956 "main hint #0".to_string(),
1957 "main hint #1".to_string(),
1958 "main hint #2".to_string(),
1959 "main hint #3".to_string(),
1960 "main hint #4".to_string(),
1961 "main hint #5".to_string(),
1962 "other hint #0".to_string(),
1963 "other hint #1".to_string(),
1964 "other hint #2".to_string(),
1965 ];
1966 assert_eq!(expected_layers, cached_hint_labels(editor),
1967 "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
1968 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1969 let inlay_cache = editor.inlay_hint_cache();
1970 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1971 assert_eq!(inlay_cache.version, 9);
1972 });
1973
1974 editor.update(cx, |editor, cx| {
1975 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1976 s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
1977 });
1978 });
1979 cx.foreground().run_until_parked();
1980 editor.update(cx, |editor, cx| {
1981 let expected_layers = vec![
1982 "main hint #0".to_string(),
1983 "main hint #1".to_string(),
1984 "main hint #2".to_string(),
1985 "main hint #3".to_string(),
1986 "main hint #4".to_string(),
1987 "main hint #5".to_string(),
1988 "other hint #0".to_string(),
1989 "other hint #1".to_string(),
1990 "other hint #2".to_string(),
1991 "other hint #3".to_string(),
1992 "other hint #4".to_string(),
1993 "other hint #5".to_string(),
1994 ];
1995 assert_eq!(expected_layers, cached_hint_labels(editor),
1996 "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
1997 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1998 let inlay_cache = editor.inlay_hint_cache();
1999 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
2000 assert_eq!(inlay_cache.version, 12);
2001 });
2002
2003 editor.update(cx, |editor, cx| {
2004 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2005 s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2006 });
2007 });
2008 cx.foreground().run_until_parked();
2009 editor.update(cx, |editor, cx| {
2010 let expected_layers = vec![
2011 "main hint #0".to_string(),
2012 "main hint #1".to_string(),
2013 "main hint #2".to_string(),
2014 "main hint #3".to_string(),
2015 "main hint #4".to_string(),
2016 "main hint #5".to_string(),
2017 "other hint #0".to_string(),
2018 "other hint #1".to_string(),
2019 "other hint #2".to_string(),
2020 "other hint #3".to_string(),
2021 "other hint #4".to_string(),
2022 "other hint #5".to_string(),
2023 ];
2024 assert_eq!(expected_layers, cached_hint_labels(editor),
2025 "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2026 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2027 let inlay_cache = editor.inlay_hint_cache();
2028 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
2029 assert_eq!(inlay_cache.version, 12, "No updates should happen during scrolling already scolled buffer");
2030 });
2031
2032 editor_edited.store(true, Ordering::Release);
2033 editor.update(cx, |editor, cx| {
2034 editor.handle_input("++++more text++++", cx);
2035 });
2036 cx.foreground().run_until_parked();
2037 editor.update(cx, |editor, cx| {
2038 let expected_layers = vec![
2039 "main hint(edited) #0".to_string(),
2040 "main hint(edited) #1".to_string(),
2041 "main hint(edited) #2".to_string(),
2042 "main hint(edited) #3".to_string(),
2043 "other hint #0".to_string(),
2044 "other hint #1".to_string(),
2045 "other hint #2".to_string(),
2046 "other hint #3".to_string(),
2047 "other hint #4".to_string(),
2048 "other hint #5".to_string(),
2049 ];
2050 assert_eq!(expected_layers, cached_hint_labels(editor),
2051 "After multibuffer was edited, hints for the edited buffer (1st) should be invalidated and requeried for all of its visible excerpts, \
2052unedited (2nd) buffer should have the same hint");
2053 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2054 let inlay_cache = editor.inlay_hint_cache();
2055 assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
2056 assert_eq!(inlay_cache.version, 16);
2057 });
2058 }
2059
2060 pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2061 cx.foreground().forbid_parking();
2062
2063 cx.update(|cx| {
2064 cx.set_global(SettingsStore::test(cx));
2065 theme::init((), cx);
2066 client::init_settings(cx);
2067 language::init(cx);
2068 Project::init_settings(cx);
2069 workspace::init_settings(cx);
2070 crate::init(cx);
2071 });
2072
2073 update_test_settings(cx, f);
2074 }
2075
2076 async fn prepare_test_objects(
2077 cx: &mut TestAppContext,
2078 ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
2079 let mut language = Language::new(
2080 LanguageConfig {
2081 name: "Rust".into(),
2082 path_suffixes: vec!["rs".to_string()],
2083 ..Default::default()
2084 },
2085 Some(tree_sitter_rust::language()),
2086 );
2087 let mut fake_servers = language
2088 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2089 capabilities: lsp::ServerCapabilities {
2090 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2091 ..Default::default()
2092 },
2093 ..Default::default()
2094 }))
2095 .await;
2096
2097 let fs = FakeFs::new(cx.background());
2098 fs.insert_tree(
2099 "/a",
2100 json!({
2101 "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
2102 "other.rs": "// Test file",
2103 }),
2104 )
2105 .await;
2106
2107 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2108 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2109 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
2110 let worktree_id = workspace.update(cx, |workspace, cx| {
2111 workspace.project().read_with(cx, |project, cx| {
2112 project.worktrees(cx).next().unwrap().read(cx).id()
2113 })
2114 });
2115
2116 let editor = workspace
2117 .update(cx, |workspace, cx| {
2118 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2119 })
2120 .await
2121 .unwrap()
2122 .downcast::<Editor>()
2123 .unwrap();
2124
2125 let fake_server = fake_servers.next().await.unwrap();
2126
2127 ("/a/main.rs", editor, fake_server)
2128 }
2129
2130 fn cached_hint_labels(editor: &Editor) -> Vec<String> {
2131 let mut labels = Vec::new();
2132 for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
2133 let excerpt_hints = excerpt_hints.read();
2134 for (_, inlay) in excerpt_hints.hints.iter() {
2135 match &inlay.label {
2136 project::InlayHintLabel::String(s) => labels.push(s.to_string()),
2137 _ => unreachable!(),
2138 }
2139 }
2140 }
2141
2142 labels.sort();
2143 labels
2144 }
2145
2146 fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
2147 let mut zz = editor
2148 .visible_inlay_hints(cx)
2149 .into_iter()
2150 .map(|hint| hint.text.to_string())
2151 .collect::<Vec<_>>();
2152 zz.sort();
2153 zz
2154 }
2155}