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