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