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::{ModelContext, 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 sum_tree::Bias;
21use util::post_inc;
22
23pub struct InlayHintCache {
24 hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
25 allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
26 version: usize,
27 enabled: bool,
28 update_tasks: HashMap<ExcerptId, TasksForRanges>,
29}
30
31#[derive(Debug)]
32struct TasksForRanges {
33 tasks: Vec<Task<()>>,
34 sorted_ranges: Vec<Range<language::Anchor>>,
35}
36
37#[derive(Debug)]
38pub struct CachedExcerptHints {
39 version: usize,
40 buffer_version: Global,
41 buffer_id: u64,
42 hints: Vec<(InlayId, InlayHint)>,
43}
44
45#[derive(Debug, Clone, Copy)]
46pub enum InvalidationStrategy {
47 RefreshRequested,
48 BufferEdited,
49 None,
50}
51
52#[derive(Debug, Default)]
53pub struct InlaySplice {
54 pub to_remove: Vec<InlayId>,
55 pub to_insert: Vec<Inlay>,
56}
57
58#[derive(Debug)]
59struct ExcerptHintsUpdate {
60 excerpt_id: ExcerptId,
61 remove_from_visible: Vec<InlayId>,
62 remove_from_cache: HashSet<InlayId>,
63 add_to_cache: HashSet<InlayHint>,
64}
65
66#[derive(Debug, Clone, Copy)]
67struct ExcerptQuery {
68 buffer_id: u64,
69 excerpt_id: ExcerptId,
70 cache_version: usize,
71 invalidate: InvalidationStrategy,
72}
73
74impl InvalidationStrategy {
75 fn should_invalidate(&self) -> bool {
76 matches!(
77 self,
78 InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited
79 )
80 }
81}
82
83impl TasksForRanges {
84 fn new(sorted_ranges: Vec<Range<language::Anchor>>, task: Task<()>) -> Self {
85 Self {
86 tasks: vec![task],
87 sorted_ranges,
88 }
89 }
90
91 fn update_cached_tasks(
92 &mut self,
93 buffer_snapshot: &BufferSnapshot,
94 query_range: Range<text::Anchor>,
95 invalidate: InvalidationStrategy,
96 spawn_task: impl FnOnce(Vec<Range<language::Anchor>>) -> Task<()>,
97 ) {
98 let ranges_to_query = match invalidate {
99 InvalidationStrategy::None => {
100 let mut ranges_to_query = Vec::new();
101 let mut latest_cached_range = None::<&mut Range<language::Anchor>>;
102 for cached_range in self
103 .sorted_ranges
104 .iter_mut()
105 .skip_while(|cached_range| {
106 cached_range
107 .end
108 .cmp(&query_range.start, buffer_snapshot)
109 .is_lt()
110 })
111 .take_while(|cached_range| {
112 cached_range
113 .start
114 .cmp(&query_range.end, buffer_snapshot)
115 .is_le()
116 })
117 {
118 match latest_cached_range {
119 Some(latest_cached_range) => {
120 if latest_cached_range.end.offset.saturating_add(1)
121 < cached_range.start.offset
122 {
123 ranges_to_query.push(latest_cached_range.end..cached_range.start);
124 cached_range.start = latest_cached_range.end;
125 }
126 }
127 None => {
128 if query_range
129 .start
130 .cmp(&cached_range.start, buffer_snapshot)
131 .is_lt()
132 {
133 ranges_to_query.push(query_range.start..cached_range.start);
134 cached_range.start = query_range.start;
135 }
136 }
137 }
138 latest_cached_range = Some(cached_range);
139 }
140
141 match latest_cached_range {
142 Some(latest_cached_range) => {
143 if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset
144 {
145 ranges_to_query.push(latest_cached_range.end..query_range.end);
146 latest_cached_range.end = query_range.end;
147 }
148 }
149 None => {
150 ranges_to_query.push(query_range.clone());
151 self.sorted_ranges.push(query_range);
152 self.sorted_ranges.sort_by(|range_a, range_b| {
153 range_a.start.cmp(&range_b.start, buffer_snapshot)
154 });
155 }
156 }
157
158 ranges_to_query
159 }
160 InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited => {
161 self.tasks.clear();
162 self.sorted_ranges.clear();
163 vec![query_range]
164 }
165 };
166
167 if !ranges_to_query.is_empty() {
168 self.tasks.push(spawn_task(ranges_to_query));
169 }
170 }
171}
172
173impl InlayHintCache {
174 pub fn new(inlay_hint_settings: InlayHintSettings) -> Self {
175 Self {
176 allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
177 enabled: inlay_hint_settings.enabled,
178 hints: HashMap::default(),
179 update_tasks: HashMap::default(),
180 version: 0,
181 }
182 }
183
184 pub fn update_settings(
185 &mut self,
186 multi_buffer: &ModelHandle<MultiBuffer>,
187 new_hint_settings: InlayHintSettings,
188 visible_hints: Vec<Inlay>,
189 cx: &mut ViewContext<Editor>,
190 ) -> ControlFlow<Option<InlaySplice>> {
191 let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
192 match (self.enabled, new_hint_settings.enabled) {
193 (false, false) => {
194 self.allowed_hint_kinds = new_allowed_hint_kinds;
195 ControlFlow::Break(None)
196 }
197 (true, true) => {
198 if new_allowed_hint_kinds == self.allowed_hint_kinds {
199 ControlFlow::Break(None)
200 } else {
201 let new_splice = self.new_allowed_hint_kinds_splice(
202 multi_buffer,
203 &visible_hints,
204 &new_allowed_hint_kinds,
205 cx,
206 );
207 if new_splice.is_some() {
208 self.version += 1;
209 self.allowed_hint_kinds = new_allowed_hint_kinds;
210 }
211 ControlFlow::Break(new_splice)
212 }
213 }
214 (true, false) => {
215 self.enabled = new_hint_settings.enabled;
216 self.allowed_hint_kinds = new_allowed_hint_kinds;
217 if self.hints.is_empty() {
218 ControlFlow::Break(None)
219 } else {
220 self.clear();
221 ControlFlow::Break(Some(InlaySplice {
222 to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(),
223 to_insert: Vec::new(),
224 }))
225 }
226 }
227 (false, true) => {
228 self.enabled = new_hint_settings.enabled;
229 self.allowed_hint_kinds = new_allowed_hint_kinds;
230 ControlFlow::Continue(())
231 }
232 }
233 }
234
235 pub fn spawn_hint_refresh(
236 &mut self,
237 excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
238 invalidate: InvalidationStrategy,
239 cx: &mut ViewContext<Editor>,
240 ) -> Option<InlaySplice> {
241 if !self.enabled {
242 return None;
243 }
244
245 let mut invalidated_hints = Vec::new();
246 if invalidate.should_invalidate() {
247 self.update_tasks
248 .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
249 self.hints.retain(|cached_excerpt, cached_hints| {
250 let retain = excerpts_to_query.contains_key(cached_excerpt);
251 if !retain {
252 invalidated_hints.extend(cached_hints.read().hints.iter().map(|&(id, _)| id));
253 }
254 retain
255 });
256 }
257 if excerpts_to_query.is_empty() && invalidated_hints.is_empty() {
258 return None;
259 }
260
261 let cache_version = self.version + 1;
262 cx.spawn(|editor, mut cx| async move {
263 editor
264 .update(&mut cx, |editor, cx| {
265 spawn_new_update_tasks(editor, excerpts_to_query, invalidate, cache_version, cx)
266 })
267 .ok();
268 })
269 .detach();
270
271 if invalidated_hints.is_empty() {
272 None
273 } else {
274 Some(InlaySplice {
275 to_remove: invalidated_hints,
276 to_insert: Vec::new(),
277 })
278 }
279 }
280
281 fn new_allowed_hint_kinds_splice(
282 &self,
283 multi_buffer: &ModelHandle<MultiBuffer>,
284 visible_hints: &[Inlay],
285 new_kinds: &HashSet<Option<InlayHintKind>>,
286 cx: &mut ViewContext<Editor>,
287 ) -> Option<InlaySplice> {
288 let old_kinds = &self.allowed_hint_kinds;
289 if new_kinds == old_kinds {
290 return None;
291 }
292
293 let mut to_remove = Vec::new();
294 let mut to_insert = Vec::new();
295 let mut shown_hints_to_remove = visible_hints.iter().fold(
296 HashMap::<ExcerptId, Vec<(Anchor, InlayId)>>::default(),
297 |mut current_hints, inlay| {
298 current_hints
299 .entry(inlay.position.excerpt_id)
300 .or_default()
301 .push((inlay.position, inlay.id));
302 current_hints
303 },
304 );
305
306 let multi_buffer = multi_buffer.read(cx);
307 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
308
309 for (excerpt_id, excerpt_cached_hints) in &self.hints {
310 let shown_excerpt_hints_to_remove =
311 shown_hints_to_remove.entry(*excerpt_id).or_default();
312 let excerpt_cached_hints = excerpt_cached_hints.read();
313 let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable();
314 shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
315 let Some(buffer) = shown_anchor
316 .buffer_id
317 .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false };
318 let buffer_snapshot = buffer.read(cx).snapshot();
319 loop {
320 match excerpt_cache.peek() {
321 Some((cached_hint_id, cached_hint)) => {
322 if cached_hint_id == shown_hint_id {
323 excerpt_cache.next();
324 return !new_kinds.contains(&cached_hint.kind);
325 }
326
327 match cached_hint
328 .position
329 .cmp(&shown_anchor.text_anchor, &buffer_snapshot)
330 {
331 cmp::Ordering::Less | cmp::Ordering::Equal => {
332 if !old_kinds.contains(&cached_hint.kind)
333 && new_kinds.contains(&cached_hint.kind)
334 {
335 to_insert.push(Inlay::hint(
336 cached_hint_id.id(),
337 multi_buffer_snapshot.anchor_in_excerpt(
338 *excerpt_id,
339 cached_hint.position,
340 ),
341 &cached_hint,
342 ));
343 }
344 excerpt_cache.next();
345 }
346 cmp::Ordering::Greater => return true,
347 }
348 }
349 None => return true,
350 }
351 }
352 });
353
354 for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache {
355 let cached_hint_kind = maybe_missed_cached_hint.kind;
356 if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
357 to_insert.push(Inlay::hint(
358 cached_hint_id.id(),
359 multi_buffer_snapshot
360 .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
361 &maybe_missed_cached_hint,
362 ));
363 }
364 }
365 }
366
367 to_remove.extend(
368 shown_hints_to_remove
369 .into_values()
370 .flatten()
371 .map(|(_, hint_id)| hint_id),
372 );
373 if to_remove.is_empty() && to_insert.is_empty() {
374 None
375 } else {
376 Some(InlaySplice {
377 to_remove,
378 to_insert,
379 })
380 }
381 }
382
383 fn clear(&mut self) {
384 self.version += 1;
385 self.update_tasks.clear();
386 self.hints.clear();
387 }
388
389 pub fn hints(&self) -> Vec<InlayHint> {
390 let mut hints = Vec::new();
391 for excerpt_hints in self.hints.values() {
392 let excerpt_hints = excerpt_hints.read();
393 hints.extend(excerpt_hints.hints.iter().map(|(_, hint)| hint).cloned());
394 }
395 hints
396 }
397
398 pub fn version(&self) -> usize {
399 self.version
400 }
401}
402
403fn spawn_new_update_tasks(
404 editor: &mut Editor,
405 excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
406 invalidate: InvalidationStrategy,
407 update_cache_version: usize,
408 cx: &mut ViewContext<'_, '_, Editor>,
409) {
410 let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
411 for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in
412 excerpts_to_query
413 {
414 if excerpt_visible_range.is_empty() {
415 continue;
416 }
417 let buffer = excerpt_buffer.read(cx);
418 let buffer_id = buffer.remote_id();
419 let buffer_snapshot = buffer.snapshot();
420 if buffer_snapshot
421 .version()
422 .changed_since(&new_task_buffer_version)
423 {
424 continue;
425 }
426
427 let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
428 if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
429 let cached_excerpt_hints = cached_excerpt_hints.read();
430 let cached_buffer_version = &cached_excerpt_hints.buffer_version;
431 if cached_excerpt_hints.version > update_cache_version
432 || cached_buffer_version.changed_since(&new_task_buffer_version)
433 {
434 continue;
435 }
436 };
437
438 let (multi_buffer_snapshot, Some(query_range)) =
439 editor.buffer.update(cx, |multi_buffer, cx| {
440 (
441 multi_buffer.snapshot(cx),
442 determine_query_range(
443 multi_buffer,
444 excerpt_id,
445 &excerpt_buffer,
446 excerpt_visible_range,
447 cx,
448 ),
449 )
450 }) else { return; };
451 let query = ExcerptQuery {
452 buffer_id,
453 excerpt_id,
454 cache_version: update_cache_version,
455 invalidate,
456 };
457
458 let new_update_task = |fetch_ranges| {
459 new_update_task(
460 query,
461 fetch_ranges,
462 multi_buffer_snapshot,
463 buffer_snapshot.clone(),
464 Arc::clone(&visible_hints),
465 cached_excerpt_hints,
466 cx,
467 )
468 };
469
470 match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
471 hash_map::Entry::Occupied(mut o) => {
472 o.get_mut().update_cached_tasks(
473 &buffer_snapshot,
474 query_range,
475 invalidate,
476 new_update_task,
477 );
478 }
479 hash_map::Entry::Vacant(v) => {
480 v.insert(TasksForRanges::new(
481 vec![query_range.clone()],
482 new_update_task(vec![query_range]),
483 ));
484 }
485 }
486 }
487}
488
489fn determine_query_range(
490 multi_buffer: &mut MultiBuffer,
491 excerpt_id: ExcerptId,
492 excerpt_buffer: &ModelHandle<Buffer>,
493 excerpt_visible_range: Range<usize>,
494 cx: &mut ModelContext<'_, MultiBuffer>,
495) -> Option<Range<language::Anchor>> {
496 let full_excerpt_range = multi_buffer
497 .excerpts_for_buffer(excerpt_buffer, cx)
498 .into_iter()
499 .find(|(id, _)| id == &excerpt_id)
500 .map(|(_, range)| range.context)?;
501
502 let buffer = excerpt_buffer.read(cx);
503 let excerpt_visible_len = excerpt_visible_range.end - excerpt_visible_range.start;
504 let start_offset = excerpt_visible_range
505 .start
506 .saturating_sub(excerpt_visible_len)
507 .max(full_excerpt_range.start.offset);
508 let start = buffer.anchor_before(buffer.clip_offset(start_offset, Bias::Left));
509 let end_offset = excerpt_visible_range
510 .end
511 .saturating_add(excerpt_visible_len)
512 .min(full_excerpt_range.end.offset)
513 .min(buffer.len());
514 let end = buffer.anchor_after(buffer.clip_offset(end_offset, Bias::Right));
515 if start.cmp(&end, buffer).is_eq() {
516 None
517 } else {
518 Some(start..end)
519 }
520}
521
522fn new_update_task(
523 query: ExcerptQuery,
524 hint_fetch_ranges: Vec<Range<language::Anchor>>,
525 multi_buffer_snapshot: MultiBufferSnapshot,
526 buffer_snapshot: BufferSnapshot,
527 visible_hints: Arc<Vec<Inlay>>,
528 cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
529 cx: &mut ViewContext<'_, '_, Editor>,
530) -> Task<()> {
531 cx.spawn(|editor, cx| async move {
532 let task_update_results =
533 futures::future::join_all(hint_fetch_ranges.into_iter().map(|range| {
534 fetch_and_update_hints(
535 editor.clone(),
536 multi_buffer_snapshot.clone(),
537 buffer_snapshot.clone(),
538 Arc::clone(&visible_hints),
539 cached_excerpt_hints.as_ref().map(Arc::clone),
540 query,
541 range,
542 cx.clone(),
543 )
544 }))
545 .await;
546
547 for result in task_update_results {
548 if let Err(e) = result {
549 error!("inlay hint update task failed: {e:#}");
550 }
551 }
552 })
553}
554
555async fn fetch_and_update_hints(
556 editor: gpui::WeakViewHandle<Editor>,
557 multi_buffer_snapshot: MultiBufferSnapshot,
558 buffer_snapshot: BufferSnapshot,
559 visible_hints: Arc<Vec<Inlay>>,
560 cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
561 query: ExcerptQuery,
562 fetch_range: Range<language::Anchor>,
563 mut cx: gpui::AsyncAppContext,
564) -> anyhow::Result<()> {
565 let inlay_hints_fetch_task = editor
566 .update(&mut cx, |editor, cx| {
567 editor
568 .buffer()
569 .read(cx)
570 .buffer(query.buffer_id)
571 .and_then(|buffer| {
572 let project = editor.project.as_ref()?;
573 Some(project.update(cx, |project, cx| {
574 project.inlay_hints(buffer, fetch_range.clone(), cx)
575 }))
576 })
577 })
578 .ok()
579 .flatten();
580 let new_hints = match inlay_hints_fetch_task {
581 Some(task) => task.await.context("inlay hint fetch task")?,
582 None => return Ok(()),
583 };
584 let background_task_buffer_snapshot = buffer_snapshot.clone();
585 let backround_fetch_range = fetch_range.clone();
586 let new_update = cx
587 .background()
588 .spawn(async move {
589 calculate_hint_updates(
590 query,
591 backround_fetch_range,
592 new_hints,
593 &background_task_buffer_snapshot,
594 cached_excerpt_hints,
595 &visible_hints,
596 )
597 })
598 .await;
599 if let Some(new_update) = new_update {
600 editor
601 .update(&mut cx, |editor, cx| {
602 apply_hint_update(
603 editor,
604 new_update,
605 query,
606 buffer_snapshot,
607 multi_buffer_snapshot,
608 cx,
609 );
610 })
611 .ok();
612 }
613 Ok(())
614}
615
616fn calculate_hint_updates(
617 query: ExcerptQuery,
618 fetch_range: Range<language::Anchor>,
619 new_excerpt_hints: Vec<InlayHint>,
620 buffer_snapshot: &BufferSnapshot,
621 cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
622 visible_hints: &[Inlay],
623) -> Option<ExcerptHintsUpdate> {
624 let mut add_to_cache: HashSet<InlayHint> = HashSet::default();
625 let mut excerpt_hints_to_persist = HashMap::default();
626 for new_hint in new_excerpt_hints {
627 if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) {
628 continue;
629 }
630 let missing_from_cache = match &cached_excerpt_hints {
631 Some(cached_excerpt_hints) => {
632 let cached_excerpt_hints = cached_excerpt_hints.read();
633 match cached_excerpt_hints.hints.binary_search_by(|probe| {
634 probe.1.position.cmp(&new_hint.position, buffer_snapshot)
635 }) {
636 Ok(ix) => {
637 let (cached_inlay_id, cached_hint) = &cached_excerpt_hints.hints[ix];
638 if cached_hint == &new_hint {
639 excerpt_hints_to_persist.insert(*cached_inlay_id, cached_hint.kind);
640 false
641 } else {
642 true
643 }
644 }
645 Err(_) => true,
646 }
647 }
648 None => true,
649 };
650 if missing_from_cache {
651 add_to_cache.insert(new_hint);
652 }
653 }
654
655 let mut remove_from_visible = Vec::new();
656 let mut remove_from_cache = HashSet::default();
657 if query.invalidate.should_invalidate() {
658 remove_from_visible.extend(
659 visible_hints
660 .iter()
661 .filter(|hint| hint.position.excerpt_id == query.excerpt_id)
662 .filter(|hint| {
663 contains_position(&fetch_range, hint.position.text_anchor, buffer_snapshot)
664 })
665 .filter(|hint| {
666 fetch_range
667 .start
668 .cmp(&hint.position.text_anchor, buffer_snapshot)
669 .is_le()
670 && fetch_range
671 .end
672 .cmp(&hint.position.text_anchor, buffer_snapshot)
673 .is_ge()
674 })
675 .map(|inlay_hint| inlay_hint.id)
676 .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
677 );
678
679 if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
680 let cached_excerpt_hints = cached_excerpt_hints.read();
681 remove_from_cache.extend(
682 cached_excerpt_hints
683 .hints
684 .iter()
685 .filter(|(cached_inlay_id, _)| {
686 !excerpt_hints_to_persist.contains_key(cached_inlay_id)
687 })
688 .filter(|(_, cached_hint)| {
689 fetch_range
690 .start
691 .cmp(&cached_hint.position, buffer_snapshot)
692 .is_le()
693 && fetch_range
694 .end
695 .cmp(&cached_hint.position, buffer_snapshot)
696 .is_ge()
697 })
698 .map(|(cached_inlay_id, _)| *cached_inlay_id),
699 );
700 }
701 }
702
703 if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
704 None
705 } else {
706 Some(ExcerptHintsUpdate {
707 excerpt_id: query.excerpt_id,
708 remove_from_visible,
709 remove_from_cache,
710 add_to_cache,
711 })
712 }
713}
714
715fn contains_position(
716 range: &Range<language::Anchor>,
717 position: language::Anchor,
718 buffer_snapshot: &BufferSnapshot,
719) -> bool {
720 range.start.cmp(&position, buffer_snapshot).is_le()
721 && range.end.cmp(&position, buffer_snapshot).is_ge()
722}
723
724fn apply_hint_update(
725 editor: &mut Editor,
726 new_update: ExcerptHintsUpdate,
727 query: ExcerptQuery,
728 buffer_snapshot: BufferSnapshot,
729 multi_buffer_snapshot: MultiBufferSnapshot,
730 cx: &mut ViewContext<'_, '_, Editor>,
731) {
732 let cached_excerpt_hints = editor
733 .inlay_hint_cache
734 .hints
735 .entry(new_update.excerpt_id)
736 .or_insert_with(|| {
737 Arc::new(RwLock::new(CachedExcerptHints {
738 version: query.cache_version,
739 buffer_version: buffer_snapshot.version().clone(),
740 buffer_id: query.buffer_id,
741 hints: Vec::new(),
742 }))
743 });
744 let mut cached_excerpt_hints = cached_excerpt_hints.write();
745 match query.cache_version.cmp(&cached_excerpt_hints.version) {
746 cmp::Ordering::Less => return,
747 cmp::Ordering::Greater | cmp::Ordering::Equal => {
748 cached_excerpt_hints.version = query.cache_version;
749 }
750 }
751
752 let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
753 cached_excerpt_hints
754 .hints
755 .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
756 let mut splice = InlaySplice {
757 to_remove: new_update.remove_from_visible,
758 to_insert: Vec::new(),
759 };
760 for new_hint in new_update.add_to_cache {
761 let cached_hints = &mut cached_excerpt_hints.hints;
762 let insert_position = match cached_hints
763 .binary_search_by(|probe| probe.1.position.cmp(&new_hint.position, &buffer_snapshot))
764 {
765 Ok(i) => {
766 if cached_hints[i].1.text() == new_hint.text() {
767 None
768 } else {
769 Some(i)
770 }
771 }
772 Err(i) => Some(i),
773 };
774
775 if let Some(insert_position) = insert_position {
776 let new_inlay_id = post_inc(&mut editor.next_inlay_id);
777 if editor
778 .inlay_hint_cache
779 .allowed_hint_kinds
780 .contains(&new_hint.kind)
781 {
782 let new_hint_position =
783 multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
784 splice
785 .to_insert
786 .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
787 }
788 cached_hints.insert(insert_position, (InlayId::Hint(new_inlay_id), new_hint));
789 cached_inlays_changed = true;
790 }
791 }
792 cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
793 drop(cached_excerpt_hints);
794
795 if query.invalidate.should_invalidate() {
796 let mut outdated_excerpt_caches = HashSet::default();
797 for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
798 let excerpt_hints = excerpt_hints.read();
799 if excerpt_hints.buffer_id == query.buffer_id
800 && excerpt_id != &query.excerpt_id
801 && buffer_snapshot
802 .version()
803 .changed_since(&excerpt_hints.buffer_version)
804 {
805 outdated_excerpt_caches.insert(*excerpt_id);
806 splice
807 .to_remove
808 .extend(excerpt_hints.hints.iter().map(|(id, _)| id));
809 }
810 }
811 cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
812 editor
813 .inlay_hint_cache
814 .hints
815 .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
816 }
817
818 let InlaySplice {
819 to_remove,
820 to_insert,
821 } = splice;
822 let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
823 if cached_inlays_changed || displayed_inlays_changed {
824 editor.inlay_hint_cache.version += 1;
825 }
826 if displayed_inlays_changed {
827 editor.splice_inlay_hints(to_remove, to_insert, cx)
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
834
835 use crate::{
836 scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
837 serde_json::json,
838 ExcerptRange,
839 };
840 use futures::StreamExt;
841 use gpui::{executor::Deterministic, TestAppContext, ViewHandle};
842 use itertools::Itertools;
843 use language::{
844 language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
845 };
846 use lsp::FakeLanguageServer;
847 use parking_lot::Mutex;
848 use project::{FakeFs, Project};
849 use settings::SettingsStore;
850 use text::{Point, ToPoint};
851 use workspace::Workspace;
852
853 use crate::editor_tests::update_test_language_settings;
854
855 use super::*;
856
857 #[gpui::test]
858 async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
859 let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
860 init_test(cx, |settings| {
861 settings.defaults.inlay_hints = Some(InlayHintSettings {
862 enabled: true,
863 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
864 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
865 show_other_hints: allowed_hint_kinds.contains(&None),
866 })
867 });
868
869 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
870 let lsp_request_count = Arc::new(AtomicU32::new(0));
871 fake_server
872 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
873 let task_lsp_request_count = Arc::clone(&lsp_request_count);
874 async move {
875 assert_eq!(
876 params.text_document.uri,
877 lsp::Url::from_file_path(file_with_hints).unwrap(),
878 );
879 let current_call_id =
880 Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
881 let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
882 for _ in 0..2 {
883 let mut i = current_call_id;
884 loop {
885 new_hints.push(lsp::InlayHint {
886 position: lsp::Position::new(0, i),
887 label: lsp::InlayHintLabel::String(i.to_string()),
888 kind: None,
889 text_edits: None,
890 tooltip: None,
891 padding_left: None,
892 padding_right: None,
893 data: None,
894 });
895 if i == 0 {
896 break;
897 }
898 i -= 1;
899 }
900 }
901
902 Ok(Some(new_hints))
903 }
904 })
905 .next()
906 .await;
907 cx.foreground().run_until_parked();
908
909 let mut edits_made = 1;
910 editor.update(cx, |editor, cx| {
911 let expected_layers = vec!["0".to_string()];
912 assert_eq!(
913 expected_layers,
914 cached_hint_labels(editor),
915 "Should get its first hints when opening the editor"
916 );
917 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
918 let inlay_cache = editor.inlay_hint_cache();
919 assert_eq!(
920 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
921 "Cache should use editor settings to get the allowed hint kinds"
922 );
923 assert_eq!(
924 inlay_cache.version, edits_made,
925 "The editor update the cache version after every cache/view change"
926 );
927 });
928
929 editor.update(cx, |editor, cx| {
930 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
931 editor.handle_input("some change", cx);
932 edits_made += 1;
933 });
934 cx.foreground().run_until_parked();
935 editor.update(cx, |editor, cx| {
936 let expected_layers = vec!["0".to_string(), "1".to_string()];
937 assert_eq!(
938 expected_layers,
939 cached_hint_labels(editor),
940 "Should get new hints after an edit"
941 );
942 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
943 let inlay_cache = editor.inlay_hint_cache();
944 assert_eq!(
945 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
946 "Cache should use editor settings to get the allowed hint kinds"
947 );
948 assert_eq!(
949 inlay_cache.version, edits_made,
950 "The editor update the cache version after every cache/view change"
951 );
952 });
953
954 fake_server
955 .request::<lsp::request::InlayHintRefreshRequest>(())
956 .await
957 .expect("inlay refresh request failed");
958 edits_made += 1;
959 cx.foreground().run_until_parked();
960 editor.update(cx, |editor, cx| {
961 let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()];
962 assert_eq!(
963 expected_layers,
964 cached_hint_labels(editor),
965 "Should get new hints after hint refresh/ request"
966 );
967 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
968 let inlay_cache = editor.inlay_hint_cache();
969 assert_eq!(
970 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
971 "Cache should use editor settings to get the allowed hint kinds"
972 );
973 assert_eq!(
974 inlay_cache.version, edits_made,
975 "The editor update the cache version after every cache/view change"
976 );
977 });
978 }
979
980 #[gpui::test]
981 async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
982 init_test(cx, |settings| {
983 settings.defaults.inlay_hints = Some(InlayHintSettings {
984 enabled: true,
985 show_type_hints: true,
986 show_parameter_hints: true,
987 show_other_hints: true,
988 })
989 });
990
991 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
992 let lsp_request_count = Arc::new(AtomicU32::new(0));
993 fake_server
994 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
995 let task_lsp_request_count = Arc::clone(&lsp_request_count);
996 async move {
997 assert_eq!(
998 params.text_document.uri,
999 lsp::Url::from_file_path(file_with_hints).unwrap(),
1000 );
1001 let current_call_id =
1002 Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1003 Ok(Some(vec![lsp::InlayHint {
1004 position: lsp::Position::new(0, current_call_id),
1005 label: lsp::InlayHintLabel::String(current_call_id.to_string()),
1006 kind: None,
1007 text_edits: None,
1008 tooltip: None,
1009 padding_left: None,
1010 padding_right: None,
1011 data: None,
1012 }]))
1013 }
1014 })
1015 .next()
1016 .await;
1017 cx.foreground().run_until_parked();
1018
1019 let mut edits_made = 1;
1020 editor.update(cx, |editor, cx| {
1021 let expected_layers = vec!["0".to_string()];
1022 assert_eq!(
1023 expected_layers,
1024 cached_hint_labels(editor),
1025 "Should get its first hints when opening the editor"
1026 );
1027 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1028 assert_eq!(
1029 editor.inlay_hint_cache().version,
1030 edits_made,
1031 "The editor update the cache version after every cache/view change"
1032 );
1033 });
1034
1035 let progress_token = "test_progress_token";
1036 fake_server
1037 .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1038 token: lsp::ProgressToken::String(progress_token.to_string()),
1039 })
1040 .await
1041 .expect("work done progress create request failed");
1042 cx.foreground().run_until_parked();
1043 fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1044 token: lsp::ProgressToken::String(progress_token.to_string()),
1045 value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
1046 lsp::WorkDoneProgressBegin::default(),
1047 )),
1048 });
1049 cx.foreground().run_until_parked();
1050
1051 editor.update(cx, |editor, cx| {
1052 let expected_layers = vec!["0".to_string()];
1053 assert_eq!(
1054 expected_layers,
1055 cached_hint_labels(editor),
1056 "Should not update hints while the work task is running"
1057 );
1058 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1059 assert_eq!(
1060 editor.inlay_hint_cache().version,
1061 edits_made,
1062 "Should not update the cache while the work task is running"
1063 );
1064 });
1065
1066 fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1067 token: lsp::ProgressToken::String(progress_token.to_string()),
1068 value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
1069 lsp::WorkDoneProgressEnd::default(),
1070 )),
1071 });
1072 cx.foreground().run_until_parked();
1073
1074 edits_made += 1;
1075 editor.update(cx, |editor, cx| {
1076 let expected_layers = vec!["1".to_string()];
1077 assert_eq!(
1078 expected_layers,
1079 cached_hint_labels(editor),
1080 "New hints should be queried after the work task is done"
1081 );
1082 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1083 assert_eq!(
1084 editor.inlay_hint_cache().version,
1085 edits_made,
1086 "Cache version should udpate once after the work task is done"
1087 );
1088 });
1089 }
1090
1091 #[gpui::test]
1092 async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1093 init_test(cx, |settings| {
1094 settings.defaults.inlay_hints = Some(InlayHintSettings {
1095 enabled: true,
1096 show_type_hints: true,
1097 show_parameter_hints: true,
1098 show_other_hints: true,
1099 })
1100 });
1101
1102 let fs = FakeFs::new(cx.background());
1103 fs.insert_tree(
1104 "/a",
1105 json!({
1106 "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1107 "other.md": "Test md file with some text",
1108 }),
1109 )
1110 .await;
1111 let project = Project::test(fs, ["/a".as_ref()], cx).await;
1112 let workspace = cx
1113 .add_window(|cx| Workspace::test_new(project.clone(), cx))
1114 .root(cx);
1115 let worktree_id = workspace.update(cx, |workspace, cx| {
1116 workspace.project().read_with(cx, |project, cx| {
1117 project.worktrees(cx).next().unwrap().read(cx).id()
1118 })
1119 });
1120
1121 let mut rs_fake_servers = None;
1122 let mut md_fake_servers = None;
1123 for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1124 let mut language = Language::new(
1125 LanguageConfig {
1126 name: name.into(),
1127 path_suffixes: vec![path_suffix.to_string()],
1128 ..Default::default()
1129 },
1130 Some(tree_sitter_rust::language()),
1131 );
1132 let fake_servers = language
1133 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1134 name,
1135 capabilities: lsp::ServerCapabilities {
1136 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1137 ..Default::default()
1138 },
1139 ..Default::default()
1140 }))
1141 .await;
1142 match name {
1143 "Rust" => rs_fake_servers = Some(fake_servers),
1144 "Markdown" => md_fake_servers = Some(fake_servers),
1145 _ => unreachable!(),
1146 }
1147 project.update(cx, |project, _| {
1148 project.languages().add(Arc::new(language));
1149 });
1150 }
1151
1152 let _rs_buffer = project
1153 .update(cx, |project, cx| {
1154 project.open_local_buffer("/a/main.rs", cx)
1155 })
1156 .await
1157 .unwrap();
1158 cx.foreground().run_until_parked();
1159 cx.foreground().start_waiting();
1160 let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1161 let rs_editor = workspace
1162 .update(cx, |workspace, cx| {
1163 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1164 })
1165 .await
1166 .unwrap()
1167 .downcast::<Editor>()
1168 .unwrap();
1169 let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1170 rs_fake_server
1171 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1172 let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
1173 async move {
1174 assert_eq!(
1175 params.text_document.uri,
1176 lsp::Url::from_file_path("/a/main.rs").unwrap(),
1177 );
1178 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1179 Ok(Some(vec![lsp::InlayHint {
1180 position: lsp::Position::new(0, i),
1181 label: lsp::InlayHintLabel::String(i.to_string()),
1182 kind: None,
1183 text_edits: None,
1184 tooltip: None,
1185 padding_left: None,
1186 padding_right: None,
1187 data: None,
1188 }]))
1189 }
1190 })
1191 .next()
1192 .await;
1193 cx.foreground().run_until_parked();
1194 rs_editor.update(cx, |editor, cx| {
1195 let expected_layers = vec!["0".to_string()];
1196 assert_eq!(
1197 expected_layers,
1198 cached_hint_labels(editor),
1199 "Should get its first hints when opening the editor"
1200 );
1201 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1202 assert_eq!(
1203 editor.inlay_hint_cache().version,
1204 1,
1205 "Rust editor update the cache version after every cache/view change"
1206 );
1207 });
1208
1209 cx.foreground().run_until_parked();
1210 let _md_buffer = project
1211 .update(cx, |project, cx| {
1212 project.open_local_buffer("/a/other.md", cx)
1213 })
1214 .await
1215 .unwrap();
1216 cx.foreground().run_until_parked();
1217 cx.foreground().start_waiting();
1218 let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1219 let md_editor = workspace
1220 .update(cx, |workspace, cx| {
1221 workspace.open_path((worktree_id, "other.md"), None, true, cx)
1222 })
1223 .await
1224 .unwrap()
1225 .downcast::<Editor>()
1226 .unwrap();
1227 let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1228 md_fake_server
1229 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1230 let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
1231 async move {
1232 assert_eq!(
1233 params.text_document.uri,
1234 lsp::Url::from_file_path("/a/other.md").unwrap(),
1235 );
1236 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1237 Ok(Some(vec![lsp::InlayHint {
1238 position: lsp::Position::new(0, i),
1239 label: lsp::InlayHintLabel::String(i.to_string()),
1240 kind: None,
1241 text_edits: None,
1242 tooltip: None,
1243 padding_left: None,
1244 padding_right: None,
1245 data: None,
1246 }]))
1247 }
1248 })
1249 .next()
1250 .await;
1251 cx.foreground().run_until_parked();
1252 md_editor.update(cx, |editor, cx| {
1253 let expected_layers = vec!["0".to_string()];
1254 assert_eq!(
1255 expected_layers,
1256 cached_hint_labels(editor),
1257 "Markdown editor should have a separate verison, repeating Rust editor rules"
1258 );
1259 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1260 assert_eq!(editor.inlay_hint_cache().version, 1);
1261 });
1262
1263 rs_editor.update(cx, |editor, cx| {
1264 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1265 editor.handle_input("some rs change", cx);
1266 });
1267 cx.foreground().run_until_parked();
1268 rs_editor.update(cx, |editor, cx| {
1269 let expected_layers = vec!["1".to_string()];
1270 assert_eq!(
1271 expected_layers,
1272 cached_hint_labels(editor),
1273 "Rust inlay cache should change after the edit"
1274 );
1275 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1276 assert_eq!(
1277 editor.inlay_hint_cache().version,
1278 2,
1279 "Every time hint cache changes, cache version should be incremented"
1280 );
1281 });
1282 md_editor.update(cx, |editor, cx| {
1283 let expected_layers = vec!["0".to_string()];
1284 assert_eq!(
1285 expected_layers,
1286 cached_hint_labels(editor),
1287 "Markdown editor should not be affected by Rust editor changes"
1288 );
1289 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1290 assert_eq!(editor.inlay_hint_cache().version, 1);
1291 });
1292
1293 md_editor.update(cx, |editor, cx| {
1294 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1295 editor.handle_input("some md change", cx);
1296 });
1297 cx.foreground().run_until_parked();
1298 md_editor.update(cx, |editor, cx| {
1299 let expected_layers = vec!["1".to_string()];
1300 assert_eq!(
1301 expected_layers,
1302 cached_hint_labels(editor),
1303 "Rust editor should not be affected by Markdown editor changes"
1304 );
1305 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1306 assert_eq!(editor.inlay_hint_cache().version, 2);
1307 });
1308 rs_editor.update(cx, |editor, cx| {
1309 let expected_layers = vec!["1".to_string()];
1310 assert_eq!(
1311 expected_layers,
1312 cached_hint_labels(editor),
1313 "Markdown editor should also change independently"
1314 );
1315 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1316 assert_eq!(editor.inlay_hint_cache().version, 2);
1317 });
1318 }
1319
1320 #[gpui::test]
1321 async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1322 let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1323 init_test(cx, |settings| {
1324 settings.defaults.inlay_hints = Some(InlayHintSettings {
1325 enabled: true,
1326 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1327 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1328 show_other_hints: allowed_hint_kinds.contains(&None),
1329 })
1330 });
1331
1332 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1333 let lsp_request_count = Arc::new(AtomicU32::new(0));
1334 let another_lsp_request_count = Arc::clone(&lsp_request_count);
1335 fake_server
1336 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1337 let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1338 async move {
1339 Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1340 assert_eq!(
1341 params.text_document.uri,
1342 lsp::Url::from_file_path(file_with_hints).unwrap(),
1343 );
1344 Ok(Some(vec![
1345 lsp::InlayHint {
1346 position: lsp::Position::new(0, 1),
1347 label: lsp::InlayHintLabel::String("type hint".to_string()),
1348 kind: Some(lsp::InlayHintKind::TYPE),
1349 text_edits: None,
1350 tooltip: None,
1351 padding_left: None,
1352 padding_right: None,
1353 data: None,
1354 },
1355 lsp::InlayHint {
1356 position: lsp::Position::new(0, 2),
1357 label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1358 kind: Some(lsp::InlayHintKind::PARAMETER),
1359 text_edits: None,
1360 tooltip: None,
1361 padding_left: None,
1362 padding_right: None,
1363 data: None,
1364 },
1365 lsp::InlayHint {
1366 position: lsp::Position::new(0, 3),
1367 label: lsp::InlayHintLabel::String("other hint".to_string()),
1368 kind: None,
1369 text_edits: None,
1370 tooltip: None,
1371 padding_left: None,
1372 padding_right: None,
1373 data: None,
1374 },
1375 ]))
1376 }
1377 })
1378 .next()
1379 .await;
1380 cx.foreground().run_until_parked();
1381
1382 let mut edits_made = 1;
1383 editor.update(cx, |editor, cx| {
1384 assert_eq!(
1385 lsp_request_count.load(Ordering::Relaxed),
1386 1,
1387 "Should query new hints once"
1388 );
1389 assert_eq!(
1390 vec![
1391 "other hint".to_string(),
1392 "parameter hint".to_string(),
1393 "type hint".to_string(),
1394 ],
1395 cached_hint_labels(editor),
1396 "Should get its first hints when opening the editor"
1397 );
1398 assert_eq!(
1399 vec!["other hint".to_string(), "type hint".to_string()],
1400 visible_hint_labels(editor, cx)
1401 );
1402 let inlay_cache = editor.inlay_hint_cache();
1403 assert_eq!(
1404 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1405 "Cache should use editor settings to get the allowed hint kinds"
1406 );
1407 assert_eq!(
1408 inlay_cache.version, edits_made,
1409 "The editor update the cache version after every cache/view change"
1410 );
1411 });
1412
1413 fake_server
1414 .request::<lsp::request::InlayHintRefreshRequest>(())
1415 .await
1416 .expect("inlay refresh request failed");
1417 cx.foreground().run_until_parked();
1418 editor.update(cx, |editor, cx| {
1419 assert_eq!(
1420 lsp_request_count.load(Ordering::Relaxed),
1421 2,
1422 "Should load new hints twice"
1423 );
1424 assert_eq!(
1425 vec![
1426 "other hint".to_string(),
1427 "parameter hint".to_string(),
1428 "type hint".to_string(),
1429 ],
1430 cached_hint_labels(editor),
1431 "Cached hints should not change due to allowed hint kinds settings update"
1432 );
1433 assert_eq!(
1434 vec!["other hint".to_string(), "type hint".to_string()],
1435 visible_hint_labels(editor, cx)
1436 );
1437 assert_eq!(
1438 editor.inlay_hint_cache().version,
1439 edits_made,
1440 "Should not update cache version due to new loaded hints being the same"
1441 );
1442 });
1443
1444 for (new_allowed_hint_kinds, expected_visible_hints) in [
1445 (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1446 (
1447 HashSet::from_iter([Some(InlayHintKind::Type)]),
1448 vec!["type hint".to_string()],
1449 ),
1450 (
1451 HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1452 vec!["parameter hint".to_string()],
1453 ),
1454 (
1455 HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1456 vec!["other hint".to_string(), "type hint".to_string()],
1457 ),
1458 (
1459 HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1460 vec!["other hint".to_string(), "parameter hint".to_string()],
1461 ),
1462 (
1463 HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1464 vec!["parameter hint".to_string(), "type hint".to_string()],
1465 ),
1466 (
1467 HashSet::from_iter([
1468 None,
1469 Some(InlayHintKind::Type),
1470 Some(InlayHintKind::Parameter),
1471 ]),
1472 vec![
1473 "other hint".to_string(),
1474 "parameter hint".to_string(),
1475 "type hint".to_string(),
1476 ],
1477 ),
1478 ] {
1479 edits_made += 1;
1480 update_test_language_settings(cx, |settings| {
1481 settings.defaults.inlay_hints = Some(InlayHintSettings {
1482 enabled: true,
1483 show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1484 show_parameter_hints: new_allowed_hint_kinds
1485 .contains(&Some(InlayHintKind::Parameter)),
1486 show_other_hints: new_allowed_hint_kinds.contains(&None),
1487 })
1488 });
1489 cx.foreground().run_until_parked();
1490 editor.update(cx, |editor, cx| {
1491 assert_eq!(
1492 lsp_request_count.load(Ordering::Relaxed),
1493 2,
1494 "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1495 );
1496 assert_eq!(
1497 vec![
1498 "other hint".to_string(),
1499 "parameter hint".to_string(),
1500 "type hint".to_string(),
1501 ],
1502 cached_hint_labels(editor),
1503 "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1504 );
1505 assert_eq!(
1506 expected_visible_hints,
1507 visible_hint_labels(editor, cx),
1508 "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1509 );
1510 let inlay_cache = editor.inlay_hint_cache();
1511 assert_eq!(
1512 inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1513 "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1514 );
1515 assert_eq!(
1516 inlay_cache.version, edits_made,
1517 "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1518 );
1519 });
1520 }
1521
1522 edits_made += 1;
1523 let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1524 update_test_language_settings(cx, |settings| {
1525 settings.defaults.inlay_hints = Some(InlayHintSettings {
1526 enabled: false,
1527 show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1528 show_parameter_hints: another_allowed_hint_kinds
1529 .contains(&Some(InlayHintKind::Parameter)),
1530 show_other_hints: another_allowed_hint_kinds.contains(&None),
1531 })
1532 });
1533 cx.foreground().run_until_parked();
1534 editor.update(cx, |editor, cx| {
1535 assert_eq!(
1536 lsp_request_count.load(Ordering::Relaxed),
1537 2,
1538 "Should not load new hints when hints got disabled"
1539 );
1540 assert!(
1541 cached_hint_labels(editor).is_empty(),
1542 "Should clear the cache when hints got disabled"
1543 );
1544 assert!(
1545 visible_hint_labels(editor, cx).is_empty(),
1546 "Should clear visible hints when hints got disabled"
1547 );
1548 let inlay_cache = editor.inlay_hint_cache();
1549 assert_eq!(
1550 inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1551 "Should update its allowed hint kinds even when hints got disabled"
1552 );
1553 assert_eq!(
1554 inlay_cache.version, edits_made,
1555 "The editor should update the cache version after hints got disabled"
1556 );
1557 });
1558
1559 fake_server
1560 .request::<lsp::request::InlayHintRefreshRequest>(())
1561 .await
1562 .expect("inlay refresh request failed");
1563 cx.foreground().run_until_parked();
1564 editor.update(cx, |editor, cx| {
1565 assert_eq!(
1566 lsp_request_count.load(Ordering::Relaxed),
1567 2,
1568 "Should not load new hints when they got disabled"
1569 );
1570 assert!(cached_hint_labels(editor).is_empty());
1571 assert!(visible_hint_labels(editor, cx).is_empty());
1572 assert_eq!(
1573 editor.inlay_hint_cache().version, edits_made,
1574 "The editor should not update the cache version after /refresh query without updates"
1575 );
1576 });
1577
1578 let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1579 edits_made += 1;
1580 update_test_language_settings(cx, |settings| {
1581 settings.defaults.inlay_hints = Some(InlayHintSettings {
1582 enabled: true,
1583 show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1584 show_parameter_hints: final_allowed_hint_kinds
1585 .contains(&Some(InlayHintKind::Parameter)),
1586 show_other_hints: final_allowed_hint_kinds.contains(&None),
1587 })
1588 });
1589 cx.foreground().run_until_parked();
1590 editor.update(cx, |editor, cx| {
1591 assert_eq!(
1592 lsp_request_count.load(Ordering::Relaxed),
1593 3,
1594 "Should query for new hints when they got reenabled"
1595 );
1596 assert_eq!(
1597 vec![
1598 "other hint".to_string(),
1599 "parameter hint".to_string(),
1600 "type hint".to_string(),
1601 ],
1602 cached_hint_labels(editor),
1603 "Should get its cached hints fully repopulated after the hints got reenabled"
1604 );
1605 assert_eq!(
1606 vec!["parameter hint".to_string()],
1607 visible_hint_labels(editor, cx),
1608 "Should get its visible hints repopulated and filtered after the h"
1609 );
1610 let inlay_cache = editor.inlay_hint_cache();
1611 assert_eq!(
1612 inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1613 "Cache should update editor settings when hints got reenabled"
1614 );
1615 assert_eq!(
1616 inlay_cache.version, edits_made,
1617 "Cache should update its version after hints got reenabled"
1618 );
1619 });
1620
1621 fake_server
1622 .request::<lsp::request::InlayHintRefreshRequest>(())
1623 .await
1624 .expect("inlay refresh request failed");
1625 cx.foreground().run_until_parked();
1626 editor.update(cx, |editor, cx| {
1627 assert_eq!(
1628 lsp_request_count.load(Ordering::Relaxed),
1629 4,
1630 "Should query for new hints again"
1631 );
1632 assert_eq!(
1633 vec![
1634 "other hint".to_string(),
1635 "parameter hint".to_string(),
1636 "type hint".to_string(),
1637 ],
1638 cached_hint_labels(editor),
1639 );
1640 assert_eq!(
1641 vec!["parameter hint".to_string()],
1642 visible_hint_labels(editor, cx),
1643 );
1644 assert_eq!(editor.inlay_hint_cache().version, edits_made);
1645 });
1646 }
1647
1648 #[gpui::test]
1649 async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
1650 init_test(cx, |settings| {
1651 settings.defaults.inlay_hints = Some(InlayHintSettings {
1652 enabled: true,
1653 show_type_hints: true,
1654 show_parameter_hints: true,
1655 show_other_hints: true,
1656 })
1657 });
1658
1659 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1660 let fake_server = Arc::new(fake_server);
1661 let lsp_request_count = Arc::new(AtomicU32::new(0));
1662 let another_lsp_request_count = Arc::clone(&lsp_request_count);
1663 fake_server
1664 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1665 let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1666 async move {
1667 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1668 assert_eq!(
1669 params.text_document.uri,
1670 lsp::Url::from_file_path(file_with_hints).unwrap(),
1671 );
1672 Ok(Some(vec![lsp::InlayHint {
1673 position: lsp::Position::new(0, i),
1674 label: lsp::InlayHintLabel::String(i.to_string()),
1675 kind: None,
1676 text_edits: None,
1677 tooltip: None,
1678 padding_left: None,
1679 padding_right: None,
1680 data: None,
1681 }]))
1682 }
1683 })
1684 .next()
1685 .await;
1686
1687 let mut expected_changes = Vec::new();
1688 for change_after_opening in [
1689 "initial change #1",
1690 "initial change #2",
1691 "initial change #3",
1692 ] {
1693 editor.update(cx, |editor, cx| {
1694 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1695 editor.handle_input(change_after_opening, cx);
1696 });
1697 expected_changes.push(change_after_opening);
1698 }
1699
1700 cx.foreground().run_until_parked();
1701
1702 editor.update(cx, |editor, cx| {
1703 let current_text = editor.text(cx);
1704 for change in &expected_changes {
1705 assert!(
1706 current_text.contains(change),
1707 "Should apply all changes made"
1708 );
1709 }
1710 assert_eq!(
1711 lsp_request_count.load(Ordering::Relaxed),
1712 2,
1713 "Should query new hints twice: for editor init and for the last edit that interrupted all others"
1714 );
1715 let expected_hints = vec!["2".to_string()];
1716 assert_eq!(
1717 expected_hints,
1718 cached_hint_labels(editor),
1719 "Should get hints from the last edit landed only"
1720 );
1721 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1722 assert_eq!(
1723 editor.inlay_hint_cache().version, 1,
1724 "Only one update should be registered in the cache after all cancellations"
1725 );
1726 });
1727
1728 let mut edits = Vec::new();
1729 for async_later_change in [
1730 "another change #1",
1731 "another change #2",
1732 "another change #3",
1733 ] {
1734 expected_changes.push(async_later_change);
1735 let task_editor = editor.clone();
1736 let mut task_cx = cx.clone();
1737 edits.push(cx.foreground().spawn(async move {
1738 task_editor.update(&mut task_cx, |editor, cx| {
1739 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1740 editor.handle_input(async_later_change, cx);
1741 });
1742 }));
1743 }
1744 let _ = futures::future::join_all(edits).await;
1745 cx.foreground().run_until_parked();
1746
1747 editor.update(cx, |editor, cx| {
1748 let current_text = editor.text(cx);
1749 for change in &expected_changes {
1750 assert!(
1751 current_text.contains(change),
1752 "Should apply all changes made"
1753 );
1754 }
1755 assert_eq!(
1756 lsp_request_count.load(Ordering::SeqCst),
1757 3,
1758 "Should query new hints one more time, for the last edit only"
1759 );
1760 let expected_hints = vec!["3".to_string()];
1761 assert_eq!(
1762 expected_hints,
1763 cached_hint_labels(editor),
1764 "Should get hints from the last edit landed only"
1765 );
1766 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1767 assert_eq!(
1768 editor.inlay_hint_cache().version,
1769 2,
1770 "Should update the cache version once more, for the new change"
1771 );
1772 });
1773 }
1774
1775 #[gpui::test]
1776 async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
1777 init_test(cx, |settings| {
1778 settings.defaults.inlay_hints = Some(InlayHintSettings {
1779 enabled: true,
1780 show_type_hints: true,
1781 show_parameter_hints: true,
1782 show_other_hints: true,
1783 })
1784 });
1785
1786 let mut language = Language::new(
1787 LanguageConfig {
1788 name: "Rust".into(),
1789 path_suffixes: vec!["rs".to_string()],
1790 ..Default::default()
1791 },
1792 Some(tree_sitter_rust::language()),
1793 );
1794 let mut fake_servers = language
1795 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1796 capabilities: lsp::ServerCapabilities {
1797 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1798 ..Default::default()
1799 },
1800 ..Default::default()
1801 }))
1802 .await;
1803 let fs = FakeFs::new(cx.background());
1804 fs.insert_tree(
1805 "/a",
1806 json!({
1807 "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
1808 "other.rs": "// Test file",
1809 }),
1810 )
1811 .await;
1812 let project = Project::test(fs, ["/a".as_ref()], cx).await;
1813 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
1814 let workspace = cx
1815 .add_window(|cx| Workspace::test_new(project.clone(), cx))
1816 .root(cx);
1817 let worktree_id = workspace.update(cx, |workspace, cx| {
1818 workspace.project().read_with(cx, |project, cx| {
1819 project.worktrees(cx).next().unwrap().read(cx).id()
1820 })
1821 });
1822
1823 let _buffer = project
1824 .update(cx, |project, cx| {
1825 project.open_local_buffer("/a/main.rs", cx)
1826 })
1827 .await
1828 .unwrap();
1829 cx.foreground().run_until_parked();
1830 cx.foreground().start_waiting();
1831 let fake_server = fake_servers.next().await.unwrap();
1832 let editor = workspace
1833 .update(cx, |workspace, cx| {
1834 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1835 })
1836 .await
1837 .unwrap()
1838 .downcast::<Editor>()
1839 .unwrap();
1840 let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
1841 let lsp_request_count = Arc::new(AtomicU32::new(0));
1842 let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
1843 let closure_lsp_request_count = Arc::clone(&lsp_request_count);
1844 fake_server
1845 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1846 let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
1847 let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
1848 async move {
1849 assert_eq!(
1850 params.text_document.uri,
1851 lsp::Url::from_file_path("/a/main.rs").unwrap(),
1852 );
1853
1854 task_lsp_request_ranges.lock().push(params.range);
1855 let query_start = params.range.start;
1856 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
1857 Ok(Some(vec![lsp::InlayHint {
1858 position: query_start,
1859 label: lsp::InlayHintLabel::String(i.to_string()),
1860 kind: None,
1861 text_edits: None,
1862 tooltip: None,
1863 padding_left: None,
1864 padding_right: None,
1865 data: None,
1866 }]))
1867 }
1868 })
1869 .next()
1870 .await;
1871 fn editor_visible_range(
1872 editor: &ViewHandle<Editor>,
1873 cx: &mut gpui::TestAppContext,
1874 ) -> Range<Point> {
1875 let ranges = editor.update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx));
1876 assert_eq!(
1877 ranges.len(),
1878 1,
1879 "Single buffer should produce a single excerpt with visible range"
1880 );
1881 let (_, (excerpt_buffer, _, excerpt_visible_range)) =
1882 ranges.into_iter().next().unwrap();
1883 excerpt_buffer.update(cx, |buffer, _| {
1884 let snapshot = buffer.snapshot();
1885 let start = buffer
1886 .anchor_before(excerpt_visible_range.start)
1887 .to_point(&snapshot);
1888 let end = buffer
1889 .anchor_after(excerpt_visible_range.end)
1890 .to_point(&snapshot);
1891 start..end
1892 })
1893 }
1894
1895 let initial_visible_range = editor_visible_range(&editor, cx);
1896 let expected_initial_query_range_end =
1897 lsp::Position::new(initial_visible_range.end.row * 2, 1);
1898 cx.foreground().run_until_parked();
1899 editor.update(cx, |editor, cx| {
1900 let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1901 assert_eq!(ranges.len(), 1,
1902 "When scroll is at the edge of a big document, double of its visible part range should be queried for hints in one single big request, but got: {ranges:?}");
1903 let query_range = &ranges[0];
1904 assert_eq!(query_range.start, lsp::Position::new(0, 0), "Should query initially from the beginning of the document");
1905 assert_eq!(query_range.end, expected_initial_query_range_end, "Should query initially for double lines of the visible part of the document");
1906
1907 assert_eq!(lsp_request_count.load(Ordering::Acquire), 1);
1908 let expected_layers = vec!["1".to_string()];
1909 assert_eq!(
1910 expected_layers,
1911 cached_hint_labels(editor),
1912 "Should have hints from both LSP requests made for a big file"
1913 );
1914 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1915 assert_eq!(
1916 editor.inlay_hint_cache().version, 1,
1917 "LSP queries should've bumped the cache version"
1918 );
1919 });
1920
1921 editor.update(cx, |editor, cx| {
1922 editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1923 editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1924 });
1925
1926 let visible_range_after_scrolls = editor_visible_range(&editor, cx);
1927 cx.foreground().run_until_parked();
1928 let selection_in_cached_range = editor.update(cx, |editor, cx| {
1929 let ranges = lsp_request_ranges
1930 .lock()
1931 .drain(..)
1932 .sorted_by_key(|r| r.start)
1933 .collect::<Vec<_>>();
1934 assert_eq!(
1935 ranges.len(),
1936 2,
1937 "Should query 2 ranges after both scrolls, but got: {ranges:?}"
1938 );
1939 let first_scroll = &ranges[0];
1940 let second_scroll = &ranges[1];
1941 assert_eq!(
1942 first_scroll.end, second_scroll.start,
1943 "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
1944 );
1945 assert_eq!(
1946 first_scroll.start, expected_initial_query_range_end,
1947 "First scroll should start the query right after the end of the original scroll",
1948 );
1949 let expected_increment = editor.visible_line_count().unwrap().ceil() as u32;
1950 assert_eq!(
1951 second_scroll.end,
1952 lsp::Position::new(
1953 visible_range_after_scrolls.end.row
1954 + expected_increment,
1955 0
1956 ),
1957 "Second scroll should query one more screen down after the end of the visible range"
1958 );
1959
1960 assert_eq!(
1961 lsp_request_count.load(Ordering::Acquire),
1962 3,
1963 "Should query for hints after every scroll"
1964 );
1965 let expected_layers = vec!["1".to_string(), "2".to_string(), "3".to_string()];
1966 assert_eq!(
1967 expected_layers,
1968 cached_hint_labels(editor),
1969 "Should have hints from the new LSP response after the edit"
1970 );
1971 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1972 assert_eq!(
1973 editor.inlay_hint_cache().version,
1974 3,
1975 "Should update the cache for every LSP response with hints added"
1976 );
1977
1978 let mut selection_in_cached_range = visible_range_after_scrolls.end;
1979 selection_in_cached_range.row -= expected_increment;
1980 selection_in_cached_range
1981 });
1982
1983 editor.update(cx, |editor, cx| {
1984 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1985 s.select_ranges([selection_in_cached_range..selection_in_cached_range])
1986 });
1987 });
1988 cx.foreground().run_until_parked();
1989 editor.update(cx, |_, _| {
1990 let ranges = lsp_request_ranges
1991 .lock()
1992 .drain(..)
1993 .sorted_by_key(|r| r.start)
1994 .collect::<Vec<_>>();
1995 assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
1996 assert_eq!(lsp_request_count.load(Ordering::Acquire), 3);
1997 });
1998
1999 editor.update(cx, |editor, cx| {
2000 editor.handle_input("++++more text++++", cx);
2001 });
2002 cx.foreground().run_until_parked();
2003 editor.update(cx, |editor, cx| {
2004 let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2005 let expected_increment = editor.visible_line_count().unwrap().ceil() as u32;
2006 assert_eq!(ranges.len(), 1,
2007 "On edit, should scroll to selection and query a range around it. Instead, got query ranges {ranges:?}");
2008 let query_range = &ranges[0];
2009 assert_eq!(query_range.start, lsp::Position::new(selection_in_cached_range.row - expected_increment, 0));
2010 assert_eq!(query_range.end, lsp::Position::new(selection_in_cached_range.row + expected_increment, 0));
2011
2012 assert_eq!(lsp_request_count.load(Ordering::Acquire), 3, "Should query for hints after the scroll and again after the edit");
2013 let expected_layers = vec!["1".to_string(), "2".to_string(), "3".to_string()];
2014 assert_eq!(expected_layers, cached_hint_labels(editor),
2015 "Should have hints from the new LSP response after the edit");
2016 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2017 assert_eq!(editor.inlay_hint_cache().version, 3, "Should update the cache for every LSP response with hints added");
2018 });
2019 }
2020
2021 #[gpui::test]
2022 async fn test_multiple_excerpts_large_multibuffer(
2023 deterministic: Arc<Deterministic>,
2024 cx: &mut gpui::TestAppContext,
2025 ) {
2026 init_test(cx, |settings| {
2027 settings.defaults.inlay_hints = Some(InlayHintSettings {
2028 enabled: true,
2029 show_type_hints: true,
2030 show_parameter_hints: true,
2031 show_other_hints: true,
2032 })
2033 });
2034
2035 let mut language = Language::new(
2036 LanguageConfig {
2037 name: "Rust".into(),
2038 path_suffixes: vec!["rs".to_string()],
2039 ..Default::default()
2040 },
2041 Some(tree_sitter_rust::language()),
2042 );
2043 let mut fake_servers = language
2044 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2045 capabilities: lsp::ServerCapabilities {
2046 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2047 ..Default::default()
2048 },
2049 ..Default::default()
2050 }))
2051 .await;
2052 let language = Arc::new(language);
2053 let fs = FakeFs::new(cx.background());
2054 fs.insert_tree(
2055 "/a",
2056 json!({
2057 "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2058 "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2059 }),
2060 )
2061 .await;
2062 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2063 project.update(cx, |project, _| {
2064 project.languages().add(Arc::clone(&language))
2065 });
2066 let workspace = cx
2067 .add_window(|cx| Workspace::test_new(project.clone(), cx))
2068 .root(cx);
2069 let worktree_id = workspace.update(cx, |workspace, cx| {
2070 workspace.project().read_with(cx, |project, cx| {
2071 project.worktrees(cx).next().unwrap().read(cx).id()
2072 })
2073 });
2074
2075 let buffer_1 = project
2076 .update(cx, |project, cx| {
2077 project.open_buffer((worktree_id, "main.rs"), cx)
2078 })
2079 .await
2080 .unwrap();
2081 let buffer_2 = project
2082 .update(cx, |project, cx| {
2083 project.open_buffer((worktree_id, "other.rs"), cx)
2084 })
2085 .await
2086 .unwrap();
2087 let multibuffer = cx.add_model(|cx| {
2088 let mut multibuffer = MultiBuffer::new(0);
2089 multibuffer.push_excerpts(
2090 buffer_1.clone(),
2091 [
2092 ExcerptRange {
2093 context: Point::new(0, 0)..Point::new(2, 0),
2094 primary: None,
2095 },
2096 ExcerptRange {
2097 context: Point::new(4, 0)..Point::new(11, 0),
2098 primary: None,
2099 },
2100 ExcerptRange {
2101 context: Point::new(22, 0)..Point::new(33, 0),
2102 primary: None,
2103 },
2104 ExcerptRange {
2105 context: Point::new(44, 0)..Point::new(55, 0),
2106 primary: None,
2107 },
2108 ExcerptRange {
2109 context: Point::new(56, 0)..Point::new(66, 0),
2110 primary: None,
2111 },
2112 ExcerptRange {
2113 context: Point::new(67, 0)..Point::new(77, 0),
2114 primary: None,
2115 },
2116 ],
2117 cx,
2118 );
2119 multibuffer.push_excerpts(
2120 buffer_2.clone(),
2121 [
2122 ExcerptRange {
2123 context: Point::new(0, 1)..Point::new(2, 1),
2124 primary: None,
2125 },
2126 ExcerptRange {
2127 context: Point::new(4, 1)..Point::new(11, 1),
2128 primary: None,
2129 },
2130 ExcerptRange {
2131 context: Point::new(22, 1)..Point::new(33, 1),
2132 primary: None,
2133 },
2134 ExcerptRange {
2135 context: Point::new(44, 1)..Point::new(55, 1),
2136 primary: None,
2137 },
2138 ExcerptRange {
2139 context: Point::new(56, 1)..Point::new(66, 1),
2140 primary: None,
2141 },
2142 ExcerptRange {
2143 context: Point::new(67, 1)..Point::new(77, 1),
2144 primary: None,
2145 },
2146 ],
2147 cx,
2148 );
2149 multibuffer
2150 });
2151
2152 deterministic.run_until_parked();
2153 cx.foreground().run_until_parked();
2154 let editor = cx
2155 .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
2156 .root(cx);
2157 let editor_edited = Arc::new(AtomicBool::new(false));
2158 let fake_server = fake_servers.next().await.unwrap();
2159 let closure_editor_edited = Arc::clone(&editor_edited);
2160 fake_server
2161 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2162 let task_editor_edited = Arc::clone(&closure_editor_edited);
2163 async move {
2164 let hint_text = if params.text_document.uri
2165 == lsp::Url::from_file_path("/a/main.rs").unwrap()
2166 {
2167 "main hint"
2168 } else if params.text_document.uri
2169 == lsp::Url::from_file_path("/a/other.rs").unwrap()
2170 {
2171 "other hint"
2172 } else {
2173 panic!("unexpected uri: {:?}", params.text_document.uri);
2174 };
2175
2176 // one hint per excerpt
2177 let positions = [
2178 lsp::Position::new(0, 2),
2179 lsp::Position::new(4, 2),
2180 lsp::Position::new(22, 2),
2181 lsp::Position::new(44, 2),
2182 lsp::Position::new(56, 2),
2183 lsp::Position::new(67, 2),
2184 ];
2185 let out_of_range_hint = lsp::InlayHint {
2186 position: lsp::Position::new(
2187 params.range.start.line + 99,
2188 params.range.start.character + 99,
2189 ),
2190 label: lsp::InlayHintLabel::String(
2191 "out of excerpt range, should be ignored".to_string(),
2192 ),
2193 kind: None,
2194 text_edits: None,
2195 tooltip: None,
2196 padding_left: None,
2197 padding_right: None,
2198 data: None,
2199 };
2200
2201 let edited = task_editor_edited.load(Ordering::Acquire);
2202 Ok(Some(
2203 std::iter::once(out_of_range_hint)
2204 .chain(positions.into_iter().enumerate().map(|(i, position)| {
2205 lsp::InlayHint {
2206 position,
2207 label: lsp::InlayHintLabel::String(format!(
2208 "{hint_text}{} #{i}",
2209 if edited { "(edited)" } else { "" },
2210 )),
2211 kind: None,
2212 text_edits: None,
2213 tooltip: None,
2214 padding_left: None,
2215 padding_right: None,
2216 data: None,
2217 }
2218 }))
2219 .collect(),
2220 ))
2221 }
2222 })
2223 .next()
2224 .await;
2225 cx.foreground().run_until_parked();
2226
2227 editor.update(cx, |editor, cx| {
2228 let expected_layers = vec![
2229 "main hint #0".to_string(),
2230 "main hint #1".to_string(),
2231 "main hint #2".to_string(),
2232 "main hint #3".to_string(),
2233 ];
2234 assert_eq!(
2235 expected_layers,
2236 cached_hint_labels(editor),
2237 "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2238 );
2239 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2240 assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(), "Every visible excerpt hints should bump the verison");
2241 });
2242
2243 editor.update(cx, |editor, cx| {
2244 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2245 s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2246 });
2247 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2248 s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2249 });
2250 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2251 s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2252 });
2253 });
2254 cx.foreground().run_until_parked();
2255 editor.update(cx, |editor, cx| {
2256 let expected_layers = vec![
2257 "main hint #0".to_string(),
2258 "main hint #1".to_string(),
2259 "main hint #2".to_string(),
2260 "main hint #3".to_string(),
2261 "main hint #4".to_string(),
2262 "main hint #5".to_string(),
2263 "other hint #0".to_string(),
2264 "other hint #1".to_string(),
2265 "other hint #2".to_string(),
2266 ];
2267 assert_eq!(expected_layers, cached_hint_labels(editor),
2268 "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2269 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2270 assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(),
2271 "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
2272 });
2273
2274 editor.update(cx, |editor, cx| {
2275 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2276 s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2277 });
2278 });
2279 cx.foreground().run_until_parked();
2280 let last_scroll_update_version = editor.update(cx, |editor, cx| {
2281 let expected_layers = vec![
2282 "main hint #0".to_string(),
2283 "main hint #1".to_string(),
2284 "main hint #2".to_string(),
2285 "main hint #3".to_string(),
2286 "main hint #4".to_string(),
2287 "main hint #5".to_string(),
2288 "other hint #0".to_string(),
2289 "other hint #1".to_string(),
2290 "other hint #2".to_string(),
2291 "other hint #3".to_string(),
2292 "other hint #4".to_string(),
2293 "other hint #5".to_string(),
2294 ];
2295 assert_eq!(expected_layers, cached_hint_labels(editor),
2296 "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2297 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2298 assert_eq!(editor.inlay_hint_cache().version, expected_layers.len());
2299 expected_layers.len()
2300 });
2301
2302 editor.update(cx, |editor, cx| {
2303 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2304 s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2305 });
2306 });
2307 cx.foreground().run_until_parked();
2308 editor.update(cx, |editor, cx| {
2309 let expected_layers = vec![
2310 "main hint #0".to_string(),
2311 "main hint #1".to_string(),
2312 "main hint #2".to_string(),
2313 "main hint #3".to_string(),
2314 "main hint #4".to_string(),
2315 "main hint #5".to_string(),
2316 "other hint #0".to_string(),
2317 "other hint #1".to_string(),
2318 "other hint #2".to_string(),
2319 "other hint #3".to_string(),
2320 "other hint #4".to_string(),
2321 "other hint #5".to_string(),
2322 ];
2323 assert_eq!(expected_layers, cached_hint_labels(editor),
2324 "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2325 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2326 assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
2327 });
2328
2329 editor_edited.store(true, Ordering::Release);
2330 editor.update(cx, |editor, cx| {
2331 editor.change_selections(None, cx, |s| {
2332 s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
2333 });
2334 editor.handle_input("++++more text++++", cx);
2335 });
2336 cx.foreground().run_until_parked();
2337 editor.update(cx, |editor, cx| {
2338 let expected_layers = vec![
2339 "main hint(edited) #0".to_string(),
2340 "main hint(edited) #1".to_string(),
2341 "main hint(edited) #2".to_string(),
2342 "main hint(edited) #3".to_string(),
2343 "main hint(edited) #4".to_string(),
2344 "main hint(edited) #5".to_string(),
2345 "other hint(edited) #0".to_string(),
2346 "other hint(edited) #1".to_string(),
2347 ];
2348 assert_eq!(
2349 expected_layers,
2350 cached_hint_labels(editor),
2351 "After multibuffer edit, editor gets scolled back to the last selection; \
2352all hints should be invalidated and requeried for all of its visible excerpts"
2353 );
2354 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2355 assert_eq!(
2356 editor.inlay_hint_cache().version,
2357 last_scroll_update_version + expected_layers.len(),
2358 "Due to every excerpt having one hint, cache should update per new excerpt received"
2359 );
2360 });
2361 }
2362
2363 #[gpui::test]
2364 async fn test_excerpts_removed(
2365 deterministic: Arc<Deterministic>,
2366 cx: &mut gpui::TestAppContext,
2367 ) {
2368 init_test(cx, |settings| {
2369 settings.defaults.inlay_hints = Some(InlayHintSettings {
2370 enabled: true,
2371 show_type_hints: false,
2372 show_parameter_hints: false,
2373 show_other_hints: false,
2374 })
2375 });
2376
2377 let mut language = Language::new(
2378 LanguageConfig {
2379 name: "Rust".into(),
2380 path_suffixes: vec!["rs".to_string()],
2381 ..Default::default()
2382 },
2383 Some(tree_sitter_rust::language()),
2384 );
2385 let mut fake_servers = language
2386 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2387 capabilities: lsp::ServerCapabilities {
2388 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2389 ..Default::default()
2390 },
2391 ..Default::default()
2392 }))
2393 .await;
2394 let language = Arc::new(language);
2395 let fs = FakeFs::new(cx.background());
2396 fs.insert_tree(
2397 "/a",
2398 json!({
2399 "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2400 "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2401 }),
2402 )
2403 .await;
2404 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2405 project.update(cx, |project, _| {
2406 project.languages().add(Arc::clone(&language))
2407 });
2408 let workspace = cx
2409 .add_window(|cx| Workspace::test_new(project.clone(), cx))
2410 .root(cx);
2411 let worktree_id = workspace.update(cx, |workspace, cx| {
2412 workspace.project().read_with(cx, |project, cx| {
2413 project.worktrees(cx).next().unwrap().read(cx).id()
2414 })
2415 });
2416
2417 let buffer_1 = project
2418 .update(cx, |project, cx| {
2419 project.open_buffer((worktree_id, "main.rs"), cx)
2420 })
2421 .await
2422 .unwrap();
2423 let buffer_2 = project
2424 .update(cx, |project, cx| {
2425 project.open_buffer((worktree_id, "other.rs"), cx)
2426 })
2427 .await
2428 .unwrap();
2429 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
2430 let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2431 let buffer_1_excerpts = multibuffer.push_excerpts(
2432 buffer_1.clone(),
2433 [ExcerptRange {
2434 context: Point::new(0, 0)..Point::new(2, 0),
2435 primary: None,
2436 }],
2437 cx,
2438 );
2439 let buffer_2_excerpts = multibuffer.push_excerpts(
2440 buffer_2.clone(),
2441 [ExcerptRange {
2442 context: Point::new(0, 1)..Point::new(2, 1),
2443 primary: None,
2444 }],
2445 cx,
2446 );
2447 (buffer_1_excerpts, buffer_2_excerpts)
2448 });
2449
2450 assert!(!buffer_1_excerpts.is_empty());
2451 assert!(!buffer_2_excerpts.is_empty());
2452
2453 deterministic.run_until_parked();
2454 cx.foreground().run_until_parked();
2455 let editor = cx
2456 .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
2457 .root(cx);
2458 let editor_edited = Arc::new(AtomicBool::new(false));
2459 let fake_server = fake_servers.next().await.unwrap();
2460 let closure_editor_edited = Arc::clone(&editor_edited);
2461 fake_server
2462 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2463 let task_editor_edited = Arc::clone(&closure_editor_edited);
2464 async move {
2465 let hint_text = if params.text_document.uri
2466 == lsp::Url::from_file_path("/a/main.rs").unwrap()
2467 {
2468 "main hint"
2469 } else if params.text_document.uri
2470 == lsp::Url::from_file_path("/a/other.rs").unwrap()
2471 {
2472 "other hint"
2473 } else {
2474 panic!("unexpected uri: {:?}", params.text_document.uri);
2475 };
2476
2477 let positions = [
2478 lsp::Position::new(0, 2),
2479 lsp::Position::new(4, 2),
2480 lsp::Position::new(22, 2),
2481 lsp::Position::new(44, 2),
2482 lsp::Position::new(56, 2),
2483 lsp::Position::new(67, 2),
2484 ];
2485 let out_of_range_hint = lsp::InlayHint {
2486 position: lsp::Position::new(
2487 params.range.start.line + 99,
2488 params.range.start.character + 99,
2489 ),
2490 label: lsp::InlayHintLabel::String(
2491 "out of excerpt range, should be ignored".to_string(),
2492 ),
2493 kind: None,
2494 text_edits: None,
2495 tooltip: None,
2496 padding_left: None,
2497 padding_right: None,
2498 data: None,
2499 };
2500
2501 let edited = task_editor_edited.load(Ordering::Acquire);
2502 Ok(Some(
2503 std::iter::once(out_of_range_hint)
2504 .chain(positions.into_iter().enumerate().map(|(i, position)| {
2505 lsp::InlayHint {
2506 position,
2507 label: lsp::InlayHintLabel::String(format!(
2508 "{hint_text}{} #{i}",
2509 if edited { "(edited)" } else { "" },
2510 )),
2511 kind: None,
2512 text_edits: None,
2513 tooltip: None,
2514 padding_left: None,
2515 padding_right: None,
2516 data: None,
2517 }
2518 }))
2519 .collect(),
2520 ))
2521 }
2522 })
2523 .next()
2524 .await;
2525 cx.foreground().run_until_parked();
2526
2527 editor.update(cx, |editor, cx| {
2528 assert_eq!(
2529 vec!["main hint #0".to_string(), "other hint #0".to_string()],
2530 cached_hint_labels(editor),
2531 "Cache should update for both excerpts despite hints display was disabled"
2532 );
2533 assert!(
2534 visible_hint_labels(editor, cx).is_empty(),
2535 "All hints are disabled and should not be shown despite being present in the cache"
2536 );
2537 assert_eq!(
2538 editor.inlay_hint_cache().version,
2539 2,
2540 "Cache should update once per excerpt query"
2541 );
2542 });
2543
2544 editor.update(cx, |editor, cx| {
2545 editor.buffer().update(cx, |multibuffer, cx| {
2546 multibuffer.remove_excerpts(buffer_2_excerpts, cx)
2547 })
2548 });
2549 cx.foreground().run_until_parked();
2550 editor.update(cx, |editor, cx| {
2551 assert_eq!(
2552 vec!["main hint #0".to_string()],
2553 cached_hint_labels(editor),
2554 "For the removed excerpt, should clean corresponding cached hints"
2555 );
2556 assert!(
2557 visible_hint_labels(editor, cx).is_empty(),
2558 "All hints are disabled and should not be shown despite being present in the cache"
2559 );
2560 assert_eq!(
2561 editor.inlay_hint_cache().version,
2562 2,
2563 "Excerpt removal should trigger a cache update"
2564 );
2565 });
2566
2567 update_test_language_settings(cx, |settings| {
2568 settings.defaults.inlay_hints = Some(InlayHintSettings {
2569 enabled: true,
2570 show_type_hints: true,
2571 show_parameter_hints: true,
2572 show_other_hints: true,
2573 })
2574 });
2575 cx.foreground().run_until_parked();
2576 editor.update(cx, |editor, cx| {
2577 let expected_hints = vec!["main hint #0".to_string()];
2578 assert_eq!(
2579 expected_hints,
2580 cached_hint_labels(editor),
2581 "Hint display settings change should not change the cache"
2582 );
2583 assert_eq!(
2584 expected_hints,
2585 visible_hint_labels(editor, cx),
2586 "Settings change should make cached hints visible"
2587 );
2588 assert_eq!(
2589 editor.inlay_hint_cache().version,
2590 3,
2591 "Settings change should trigger a cache update"
2592 );
2593 });
2594 }
2595
2596 #[gpui::test]
2597 async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
2598 init_test(cx, |settings| {
2599 settings.defaults.inlay_hints = Some(InlayHintSettings {
2600 enabled: true,
2601 show_type_hints: true,
2602 show_parameter_hints: true,
2603 show_other_hints: true,
2604 })
2605 });
2606
2607 let mut language = Language::new(
2608 LanguageConfig {
2609 name: "Rust".into(),
2610 path_suffixes: vec!["rs".to_string()],
2611 ..Default::default()
2612 },
2613 Some(tree_sitter_rust::language()),
2614 );
2615 let mut fake_servers = language
2616 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2617 capabilities: lsp::ServerCapabilities {
2618 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2619 ..Default::default()
2620 },
2621 ..Default::default()
2622 }))
2623 .await;
2624 let fs = FakeFs::new(cx.background());
2625 fs.insert_tree(
2626 "/a",
2627 json!({
2628 "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "√".repeat(10)).repeat(500)),
2629 "other.rs": "// Test file",
2630 }),
2631 )
2632 .await;
2633 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2634 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2635 let workspace = cx
2636 .add_window(|cx| Workspace::test_new(project.clone(), cx))
2637 .root(cx);
2638 let worktree_id = workspace.update(cx, |workspace, cx| {
2639 workspace.project().read_with(cx, |project, cx| {
2640 project.worktrees(cx).next().unwrap().read(cx).id()
2641 })
2642 });
2643
2644 let _buffer = project
2645 .update(cx, |project, cx| {
2646 project.open_local_buffer("/a/main.rs", cx)
2647 })
2648 .await
2649 .unwrap();
2650 cx.foreground().run_until_parked();
2651 cx.foreground().start_waiting();
2652 let fake_server = fake_servers.next().await.unwrap();
2653 let editor = workspace
2654 .update(cx, |workspace, cx| {
2655 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2656 })
2657 .await
2658 .unwrap()
2659 .downcast::<Editor>()
2660 .unwrap();
2661 let lsp_request_count = Arc::new(AtomicU32::new(0));
2662 let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2663 fake_server
2664 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2665 let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2666 async move {
2667 assert_eq!(
2668 params.text_document.uri,
2669 lsp::Url::from_file_path("/a/main.rs").unwrap(),
2670 );
2671 let query_start = params.range.start;
2672 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
2673 Ok(Some(vec![lsp::InlayHint {
2674 position: query_start,
2675 label: lsp::InlayHintLabel::String(i.to_string()),
2676 kind: None,
2677 text_edits: None,
2678 tooltip: None,
2679 padding_left: None,
2680 padding_right: None,
2681 data: None,
2682 }]))
2683 }
2684 })
2685 .next()
2686 .await;
2687
2688 cx.foreground().run_until_parked();
2689 editor.update(cx, |editor, cx| {
2690 editor.change_selections(None, cx, |s| {
2691 s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
2692 })
2693 });
2694 cx.foreground().run_until_parked();
2695 editor.update(cx, |editor, cx| {
2696 let expected_layers = vec!["1".to_string()];
2697 assert_eq!(expected_layers, cached_hint_labels(editor));
2698 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2699 assert_eq!(editor.inlay_hint_cache().version, 1);
2700 });
2701 }
2702
2703 pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2704 cx.foreground().forbid_parking();
2705
2706 cx.update(|cx| {
2707 cx.set_global(SettingsStore::test(cx));
2708 theme::init((), cx);
2709 client::init_settings(cx);
2710 language::init(cx);
2711 Project::init_settings(cx);
2712 workspace::init_settings(cx);
2713 crate::init(cx);
2714 });
2715
2716 update_test_language_settings(cx, f);
2717 }
2718
2719 async fn prepare_test_objects(
2720 cx: &mut TestAppContext,
2721 ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
2722 let mut language = Language::new(
2723 LanguageConfig {
2724 name: "Rust".into(),
2725 path_suffixes: vec!["rs".to_string()],
2726 ..Default::default()
2727 },
2728 Some(tree_sitter_rust::language()),
2729 );
2730 let mut fake_servers = language
2731 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2732 capabilities: lsp::ServerCapabilities {
2733 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2734 ..Default::default()
2735 },
2736 ..Default::default()
2737 }))
2738 .await;
2739
2740 let fs = FakeFs::new(cx.background());
2741 fs.insert_tree(
2742 "/a",
2743 json!({
2744 "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
2745 "other.rs": "// Test file",
2746 }),
2747 )
2748 .await;
2749
2750 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2751 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2752 let workspace = cx
2753 .add_window(|cx| Workspace::test_new(project.clone(), cx))
2754 .root(cx);
2755 let worktree_id = workspace.update(cx, |workspace, cx| {
2756 workspace.project().read_with(cx, |project, cx| {
2757 project.worktrees(cx).next().unwrap().read(cx).id()
2758 })
2759 });
2760
2761 let _buffer = project
2762 .update(cx, |project, cx| {
2763 project.open_local_buffer("/a/main.rs", cx)
2764 })
2765 .await
2766 .unwrap();
2767 cx.foreground().run_until_parked();
2768 cx.foreground().start_waiting();
2769 let fake_server = fake_servers.next().await.unwrap();
2770 let editor = workspace
2771 .update(cx, |workspace, cx| {
2772 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2773 })
2774 .await
2775 .unwrap()
2776 .downcast::<Editor>()
2777 .unwrap();
2778
2779 ("/a/main.rs", editor, fake_server)
2780 }
2781
2782 fn cached_hint_labels(editor: &Editor) -> Vec<String> {
2783 let mut labels = Vec::new();
2784 for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
2785 let excerpt_hints = excerpt_hints.read();
2786 for (_, inlay) in excerpt_hints.hints.iter() {
2787 match &inlay.label {
2788 project::InlayHintLabel::String(s) => labels.push(s.to_string()),
2789 _ => unreachable!(),
2790 }
2791 }
2792 }
2793
2794 labels.sort();
2795 labels
2796 }
2797
2798 fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
2799 let mut hints = editor
2800 .visible_inlay_hints(cx)
2801 .into_iter()
2802 .map(|hint| hint.text.to_string())
2803 .collect::<Vec<_>>();
2804 hints.sort();
2805 hints
2806 }
2807}