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 .map(|inlay_hint| inlay_hint.id)
663 .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
664 );
665
666 if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
667 let cached_excerpt_hints = cached_excerpt_hints.read();
668 remove_from_cache.extend(
669 cached_excerpt_hints
670 .hints
671 .iter()
672 .filter(|(cached_inlay_id, _)| {
673 !excerpt_hints_to_persist.contains_key(cached_inlay_id)
674 })
675 .map(|(cached_inlay_id, _)| *cached_inlay_id),
676 );
677 }
678 }
679
680 if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
681 None
682 } else {
683 Some(ExcerptHintsUpdate {
684 excerpt_id: query.excerpt_id,
685 remove_from_visible,
686 remove_from_cache,
687 add_to_cache,
688 })
689 }
690}
691
692fn contains_position(
693 range: &Range<language::Anchor>,
694 position: language::Anchor,
695 buffer_snapshot: &BufferSnapshot,
696) -> bool {
697 range.start.cmp(&position, buffer_snapshot).is_le()
698 && range.end.cmp(&position, buffer_snapshot).is_ge()
699}
700
701fn apply_hint_update(
702 editor: &mut Editor,
703 new_update: ExcerptHintsUpdate,
704 query: ExcerptQuery,
705 buffer_snapshot: BufferSnapshot,
706 multi_buffer_snapshot: MultiBufferSnapshot,
707 cx: &mut ViewContext<'_, '_, Editor>,
708) {
709 let cached_excerpt_hints = editor
710 .inlay_hint_cache
711 .hints
712 .entry(new_update.excerpt_id)
713 .or_insert_with(|| {
714 Arc::new(RwLock::new(CachedExcerptHints {
715 version: query.cache_version,
716 buffer_version: buffer_snapshot.version().clone(),
717 buffer_id: query.buffer_id,
718 hints: Vec::new(),
719 }))
720 });
721 let mut cached_excerpt_hints = cached_excerpt_hints.write();
722 match query.cache_version.cmp(&cached_excerpt_hints.version) {
723 cmp::Ordering::Less => return,
724 cmp::Ordering::Greater | cmp::Ordering::Equal => {
725 cached_excerpt_hints.version = query.cache_version;
726 }
727 }
728
729 let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
730 cached_excerpt_hints
731 .hints
732 .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
733 let mut splice = InlaySplice {
734 to_remove: new_update.remove_from_visible,
735 to_insert: Vec::new(),
736 };
737 for new_hint in new_update.add_to_cache {
738 let cached_hints = &mut cached_excerpt_hints.hints;
739 let insert_position = match cached_hints
740 .binary_search_by(|probe| probe.1.position.cmp(&new_hint.position, &buffer_snapshot))
741 {
742 Ok(i) => {
743 if cached_hints[i].1.text() == new_hint.text() {
744 None
745 } else {
746 Some(i)
747 }
748 }
749 Err(i) => Some(i),
750 };
751
752 if let Some(insert_position) = insert_position {
753 let new_inlay_id = post_inc(&mut editor.next_inlay_id);
754 if editor
755 .inlay_hint_cache
756 .allowed_hint_kinds
757 .contains(&new_hint.kind)
758 {
759 let new_hint_position =
760 multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
761 splice
762 .to_insert
763 .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
764 }
765 cached_hints.insert(insert_position, (InlayId::Hint(new_inlay_id), new_hint));
766 cached_inlays_changed = true;
767 }
768 }
769 cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
770 drop(cached_excerpt_hints);
771
772 if query.invalidate.should_invalidate() {
773 let mut outdated_excerpt_caches = HashSet::default();
774 for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
775 let excerpt_hints = excerpt_hints.read();
776 if excerpt_hints.buffer_id == query.buffer_id
777 && excerpt_id != &query.excerpt_id
778 && buffer_snapshot
779 .version()
780 .changed_since(&excerpt_hints.buffer_version)
781 {
782 outdated_excerpt_caches.insert(*excerpt_id);
783 splice
784 .to_remove
785 .extend(excerpt_hints.hints.iter().map(|(id, _)| id));
786 }
787 }
788 cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
789 editor
790 .inlay_hint_cache
791 .hints
792 .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
793 }
794
795 let InlaySplice {
796 to_remove,
797 to_insert,
798 } = splice;
799 let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
800 if cached_inlays_changed || displayed_inlays_changed {
801 editor.inlay_hint_cache.version += 1;
802 }
803 if displayed_inlays_changed {
804 editor.splice_inlay_hints(to_remove, to_insert, cx)
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
811
812 use crate::{
813 scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
814 serde_json::json,
815 ExcerptRange,
816 };
817 use futures::StreamExt;
818 use gpui::{executor::Deterministic, TestAppContext, ViewHandle};
819 use itertools::Itertools;
820 use language::{
821 language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
822 };
823 use lsp::FakeLanguageServer;
824 use parking_lot::Mutex;
825 use project::{FakeFs, Project};
826 use settings::SettingsStore;
827 use text::{Point, ToPoint};
828 use workspace::Workspace;
829
830 use crate::editor_tests::update_test_language_settings;
831
832 use super::*;
833
834 #[gpui::test]
835 async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
836 let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
837 init_test(cx, |settings| {
838 settings.defaults.inlay_hints = Some(InlayHintSettings {
839 enabled: true,
840 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
841 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
842 show_other_hints: allowed_hint_kinds.contains(&None),
843 })
844 });
845
846 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
847 let lsp_request_count = Arc::new(AtomicU32::new(0));
848 fake_server
849 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
850 let task_lsp_request_count = Arc::clone(&lsp_request_count);
851 async move {
852 assert_eq!(
853 params.text_document.uri,
854 lsp::Url::from_file_path(file_with_hints).unwrap(),
855 );
856 let current_call_id =
857 Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
858 let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
859 for _ in 0..2 {
860 let mut i = current_call_id;
861 loop {
862 new_hints.push(lsp::InlayHint {
863 position: lsp::Position::new(0, i),
864 label: lsp::InlayHintLabel::String(i.to_string()),
865 kind: None,
866 text_edits: None,
867 tooltip: None,
868 padding_left: None,
869 padding_right: None,
870 data: None,
871 });
872 if i == 0 {
873 break;
874 }
875 i -= 1;
876 }
877 }
878
879 Ok(Some(new_hints))
880 }
881 })
882 .next()
883 .await;
884 cx.foreground().run_until_parked();
885
886 let mut edits_made = 1;
887 editor.update(cx, |editor, cx| {
888 let expected_layers = vec!["0".to_string()];
889 assert_eq!(
890 expected_layers,
891 cached_hint_labels(editor),
892 "Should get its first hints when opening the editor"
893 );
894 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
895 let inlay_cache = editor.inlay_hint_cache();
896 assert_eq!(
897 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
898 "Cache should use editor settings to get the allowed hint kinds"
899 );
900 assert_eq!(
901 inlay_cache.version, edits_made,
902 "The editor update the cache version after every cache/view change"
903 );
904 });
905
906 editor.update(cx, |editor, cx| {
907 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
908 editor.handle_input("some change", cx);
909 edits_made += 1;
910 });
911 cx.foreground().run_until_parked();
912 editor.update(cx, |editor, cx| {
913 let expected_layers = vec!["0".to_string(), "1".to_string()];
914 assert_eq!(
915 expected_layers,
916 cached_hint_labels(editor),
917 "Should get new hints after an edit"
918 );
919 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
920 let inlay_cache = editor.inlay_hint_cache();
921 assert_eq!(
922 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
923 "Cache should use editor settings to get the allowed hint kinds"
924 );
925 assert_eq!(
926 inlay_cache.version, edits_made,
927 "The editor update the cache version after every cache/view change"
928 );
929 });
930
931 fake_server
932 .request::<lsp::request::InlayHintRefreshRequest>(())
933 .await
934 .expect("inlay refresh request failed");
935 edits_made += 1;
936 cx.foreground().run_until_parked();
937 editor.update(cx, |editor, cx| {
938 let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()];
939 assert_eq!(
940 expected_layers,
941 cached_hint_labels(editor),
942 "Should get new hints after hint refresh/ request"
943 );
944 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
945 let inlay_cache = editor.inlay_hint_cache();
946 assert_eq!(
947 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
948 "Cache should use editor settings to get the allowed hint kinds"
949 );
950 assert_eq!(
951 inlay_cache.version, edits_made,
952 "The editor update the cache version after every cache/view change"
953 );
954 });
955 }
956
957 #[gpui::test]
958 async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
959 init_test(cx, |settings| {
960 settings.defaults.inlay_hints = Some(InlayHintSettings {
961 enabled: true,
962 show_type_hints: true,
963 show_parameter_hints: true,
964 show_other_hints: true,
965 })
966 });
967
968 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
969 let lsp_request_count = Arc::new(AtomicU32::new(0));
970 fake_server
971 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
972 let task_lsp_request_count = Arc::clone(&lsp_request_count);
973 async move {
974 assert_eq!(
975 params.text_document.uri,
976 lsp::Url::from_file_path(file_with_hints).unwrap(),
977 );
978 let current_call_id =
979 Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
980 Ok(Some(vec![lsp::InlayHint {
981 position: lsp::Position::new(0, current_call_id),
982 label: lsp::InlayHintLabel::String(current_call_id.to_string()),
983 kind: None,
984 text_edits: None,
985 tooltip: None,
986 padding_left: None,
987 padding_right: None,
988 data: None,
989 }]))
990 }
991 })
992 .next()
993 .await;
994 cx.foreground().run_until_parked();
995
996 let mut edits_made = 1;
997 editor.update(cx, |editor, cx| {
998 let expected_layers = vec!["0".to_string()];
999 assert_eq!(
1000 expected_layers,
1001 cached_hint_labels(editor),
1002 "Should get its first hints when opening the editor"
1003 );
1004 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1005 assert_eq!(
1006 editor.inlay_hint_cache().version,
1007 edits_made,
1008 "The editor update the cache version after every cache/view change"
1009 );
1010 });
1011
1012 let progress_token = "test_progress_token";
1013 fake_server
1014 .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1015 token: lsp::ProgressToken::String(progress_token.to_string()),
1016 })
1017 .await
1018 .expect("work done progress create request failed");
1019 cx.foreground().run_until_parked();
1020 fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1021 token: lsp::ProgressToken::String(progress_token.to_string()),
1022 value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
1023 lsp::WorkDoneProgressBegin::default(),
1024 )),
1025 });
1026 cx.foreground().run_until_parked();
1027
1028 editor.update(cx, |editor, cx| {
1029 let expected_layers = vec!["0".to_string()];
1030 assert_eq!(
1031 expected_layers,
1032 cached_hint_labels(editor),
1033 "Should not update hints while the work task is running"
1034 );
1035 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1036 assert_eq!(
1037 editor.inlay_hint_cache().version,
1038 edits_made,
1039 "Should not update the cache while the work task is running"
1040 );
1041 });
1042
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::End(
1046 lsp::WorkDoneProgressEnd::default(),
1047 )),
1048 });
1049 cx.foreground().run_until_parked();
1050
1051 edits_made += 1;
1052 editor.update(cx, |editor, cx| {
1053 let expected_layers = vec!["1".to_string()];
1054 assert_eq!(
1055 expected_layers,
1056 cached_hint_labels(editor),
1057 "New hints should be queried after the work task is done"
1058 );
1059 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1060 assert_eq!(
1061 editor.inlay_hint_cache().version,
1062 edits_made,
1063 "Cache version should udpate once after the work task is done"
1064 );
1065 });
1066 }
1067
1068 #[gpui::test]
1069 async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1070 init_test(cx, |settings| {
1071 settings.defaults.inlay_hints = Some(InlayHintSettings {
1072 enabled: true,
1073 show_type_hints: true,
1074 show_parameter_hints: true,
1075 show_other_hints: true,
1076 })
1077 });
1078
1079 let fs = FakeFs::new(cx.background());
1080 fs.insert_tree(
1081 "/a",
1082 json!({
1083 "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1084 "other.md": "Test md file with some text",
1085 }),
1086 )
1087 .await;
1088 let project = Project::test(fs, ["/a".as_ref()], cx).await;
1089 let workspace = cx
1090 .add_window(|cx| Workspace::test_new(project.clone(), cx))
1091 .root(cx);
1092 let worktree_id = workspace.update(cx, |workspace, cx| {
1093 workspace.project().read_with(cx, |project, cx| {
1094 project.worktrees(cx).next().unwrap().read(cx).id()
1095 })
1096 });
1097
1098 let mut rs_fake_servers = None;
1099 let mut md_fake_servers = None;
1100 for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1101 let mut language = Language::new(
1102 LanguageConfig {
1103 name: name.into(),
1104 path_suffixes: vec![path_suffix.to_string()],
1105 ..Default::default()
1106 },
1107 Some(tree_sitter_rust::language()),
1108 );
1109 let fake_servers = language
1110 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1111 name,
1112 capabilities: lsp::ServerCapabilities {
1113 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1114 ..Default::default()
1115 },
1116 ..Default::default()
1117 }))
1118 .await;
1119 match name {
1120 "Rust" => rs_fake_servers = Some(fake_servers),
1121 "Markdown" => md_fake_servers = Some(fake_servers),
1122 _ => unreachable!(),
1123 }
1124 project.update(cx, |project, _| {
1125 project.languages().add(Arc::new(language));
1126 });
1127 }
1128
1129 let _rs_buffer = project
1130 .update(cx, |project, cx| {
1131 project.open_local_buffer("/a/main.rs", cx)
1132 })
1133 .await
1134 .unwrap();
1135 cx.foreground().run_until_parked();
1136 cx.foreground().start_waiting();
1137 let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1138 let rs_editor = workspace
1139 .update(cx, |workspace, cx| {
1140 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1141 })
1142 .await
1143 .unwrap()
1144 .downcast::<Editor>()
1145 .unwrap();
1146 let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1147 rs_fake_server
1148 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1149 let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
1150 async move {
1151 assert_eq!(
1152 params.text_document.uri,
1153 lsp::Url::from_file_path("/a/main.rs").unwrap(),
1154 );
1155 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1156 Ok(Some(vec![lsp::InlayHint {
1157 position: lsp::Position::new(0, i),
1158 label: lsp::InlayHintLabel::String(i.to_string()),
1159 kind: None,
1160 text_edits: None,
1161 tooltip: None,
1162 padding_left: None,
1163 padding_right: None,
1164 data: None,
1165 }]))
1166 }
1167 })
1168 .next()
1169 .await;
1170 cx.foreground().run_until_parked();
1171 rs_editor.update(cx, |editor, cx| {
1172 let expected_layers = vec!["0".to_string()];
1173 assert_eq!(
1174 expected_layers,
1175 cached_hint_labels(editor),
1176 "Should get its first hints when opening the editor"
1177 );
1178 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1179 assert_eq!(
1180 editor.inlay_hint_cache().version,
1181 1,
1182 "Rust editor update the cache version after every cache/view change"
1183 );
1184 });
1185
1186 cx.foreground().run_until_parked();
1187 let _md_buffer = project
1188 .update(cx, |project, cx| {
1189 project.open_local_buffer("/a/other.md", cx)
1190 })
1191 .await
1192 .unwrap();
1193 cx.foreground().run_until_parked();
1194 cx.foreground().start_waiting();
1195 let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1196 let md_editor = workspace
1197 .update(cx, |workspace, cx| {
1198 workspace.open_path((worktree_id, "other.md"), None, true, cx)
1199 })
1200 .await
1201 .unwrap()
1202 .downcast::<Editor>()
1203 .unwrap();
1204 let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1205 md_fake_server
1206 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1207 let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
1208 async move {
1209 assert_eq!(
1210 params.text_document.uri,
1211 lsp::Url::from_file_path("/a/other.md").unwrap(),
1212 );
1213 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1214 Ok(Some(vec![lsp::InlayHint {
1215 position: lsp::Position::new(0, i),
1216 label: lsp::InlayHintLabel::String(i.to_string()),
1217 kind: None,
1218 text_edits: None,
1219 tooltip: None,
1220 padding_left: None,
1221 padding_right: None,
1222 data: None,
1223 }]))
1224 }
1225 })
1226 .next()
1227 .await;
1228 cx.foreground().run_until_parked();
1229 md_editor.update(cx, |editor, cx| {
1230 let expected_layers = vec!["0".to_string()];
1231 assert_eq!(
1232 expected_layers,
1233 cached_hint_labels(editor),
1234 "Markdown editor should have a separate verison, repeating Rust editor rules"
1235 );
1236 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1237 assert_eq!(editor.inlay_hint_cache().version, 1);
1238 });
1239
1240 rs_editor.update(cx, |editor, cx| {
1241 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1242 editor.handle_input("some rs change", cx);
1243 });
1244 cx.foreground().run_until_parked();
1245 rs_editor.update(cx, |editor, cx| {
1246 let expected_layers = vec!["1".to_string()];
1247 assert_eq!(
1248 expected_layers,
1249 cached_hint_labels(editor),
1250 "Rust inlay cache should change after the edit"
1251 );
1252 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1253 assert_eq!(
1254 editor.inlay_hint_cache().version,
1255 2,
1256 "Every time hint cache changes, cache version should be incremented"
1257 );
1258 });
1259 md_editor.update(cx, |editor, cx| {
1260 let expected_layers = vec!["0".to_string()];
1261 assert_eq!(
1262 expected_layers,
1263 cached_hint_labels(editor),
1264 "Markdown editor should not be affected by Rust editor changes"
1265 );
1266 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1267 assert_eq!(editor.inlay_hint_cache().version, 1);
1268 });
1269
1270 md_editor.update(cx, |editor, cx| {
1271 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1272 editor.handle_input("some md change", cx);
1273 });
1274 cx.foreground().run_until_parked();
1275 md_editor.update(cx, |editor, cx| {
1276 let expected_layers = vec!["1".to_string()];
1277 assert_eq!(
1278 expected_layers,
1279 cached_hint_labels(editor),
1280 "Rust editor should not be affected by Markdown editor changes"
1281 );
1282 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1283 assert_eq!(editor.inlay_hint_cache().version, 2);
1284 });
1285 rs_editor.update(cx, |editor, cx| {
1286 let expected_layers = vec!["1".to_string()];
1287 assert_eq!(
1288 expected_layers,
1289 cached_hint_labels(editor),
1290 "Markdown editor should also change independently"
1291 );
1292 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1293 assert_eq!(editor.inlay_hint_cache().version, 2);
1294 });
1295 }
1296
1297 #[gpui::test]
1298 async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1299 let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1300 init_test(cx, |settings| {
1301 settings.defaults.inlay_hints = Some(InlayHintSettings {
1302 enabled: true,
1303 show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1304 show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1305 show_other_hints: allowed_hint_kinds.contains(&None),
1306 })
1307 });
1308
1309 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1310 let lsp_request_count = Arc::new(AtomicU32::new(0));
1311 let another_lsp_request_count = Arc::clone(&lsp_request_count);
1312 fake_server
1313 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1314 let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1315 async move {
1316 Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1317 assert_eq!(
1318 params.text_document.uri,
1319 lsp::Url::from_file_path(file_with_hints).unwrap(),
1320 );
1321 Ok(Some(vec![
1322 lsp::InlayHint {
1323 position: lsp::Position::new(0, 1),
1324 label: lsp::InlayHintLabel::String("type hint".to_string()),
1325 kind: Some(lsp::InlayHintKind::TYPE),
1326 text_edits: None,
1327 tooltip: None,
1328 padding_left: None,
1329 padding_right: None,
1330 data: None,
1331 },
1332 lsp::InlayHint {
1333 position: lsp::Position::new(0, 2),
1334 label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1335 kind: Some(lsp::InlayHintKind::PARAMETER),
1336 text_edits: None,
1337 tooltip: None,
1338 padding_left: None,
1339 padding_right: None,
1340 data: None,
1341 },
1342 lsp::InlayHint {
1343 position: lsp::Position::new(0, 3),
1344 label: lsp::InlayHintLabel::String("other hint".to_string()),
1345 kind: None,
1346 text_edits: None,
1347 tooltip: None,
1348 padding_left: None,
1349 padding_right: None,
1350 data: None,
1351 },
1352 ]))
1353 }
1354 })
1355 .next()
1356 .await;
1357 cx.foreground().run_until_parked();
1358
1359 let mut edits_made = 1;
1360 editor.update(cx, |editor, cx| {
1361 assert_eq!(
1362 lsp_request_count.load(Ordering::Relaxed),
1363 1,
1364 "Should query new hints once"
1365 );
1366 assert_eq!(
1367 vec![
1368 "other hint".to_string(),
1369 "parameter hint".to_string(),
1370 "type hint".to_string(),
1371 ],
1372 cached_hint_labels(editor),
1373 "Should get its first hints when opening the editor"
1374 );
1375 assert_eq!(
1376 vec!["other hint".to_string(), "type hint".to_string()],
1377 visible_hint_labels(editor, cx)
1378 );
1379 let inlay_cache = editor.inlay_hint_cache();
1380 assert_eq!(
1381 inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1382 "Cache should use editor settings to get the allowed hint kinds"
1383 );
1384 assert_eq!(
1385 inlay_cache.version, edits_made,
1386 "The editor update the cache version after every cache/view change"
1387 );
1388 });
1389
1390 fake_server
1391 .request::<lsp::request::InlayHintRefreshRequest>(())
1392 .await
1393 .expect("inlay refresh request failed");
1394 cx.foreground().run_until_parked();
1395 editor.update(cx, |editor, cx| {
1396 assert_eq!(
1397 lsp_request_count.load(Ordering::Relaxed),
1398 2,
1399 "Should load new hints twice"
1400 );
1401 assert_eq!(
1402 vec![
1403 "other hint".to_string(),
1404 "parameter hint".to_string(),
1405 "type hint".to_string(),
1406 ],
1407 cached_hint_labels(editor),
1408 "Cached hints should not change due to allowed hint kinds settings update"
1409 );
1410 assert_eq!(
1411 vec!["other hint".to_string(), "type hint".to_string()],
1412 visible_hint_labels(editor, cx)
1413 );
1414 assert_eq!(
1415 editor.inlay_hint_cache().version,
1416 edits_made,
1417 "Should not update cache version due to new loaded hints being the same"
1418 );
1419 });
1420
1421 for (new_allowed_hint_kinds, expected_visible_hints) in [
1422 (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1423 (
1424 HashSet::from_iter([Some(InlayHintKind::Type)]),
1425 vec!["type hint".to_string()],
1426 ),
1427 (
1428 HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1429 vec!["parameter hint".to_string()],
1430 ),
1431 (
1432 HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1433 vec!["other hint".to_string(), "type hint".to_string()],
1434 ),
1435 (
1436 HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1437 vec!["other hint".to_string(), "parameter hint".to_string()],
1438 ),
1439 (
1440 HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1441 vec!["parameter hint".to_string(), "type hint".to_string()],
1442 ),
1443 (
1444 HashSet::from_iter([
1445 None,
1446 Some(InlayHintKind::Type),
1447 Some(InlayHintKind::Parameter),
1448 ]),
1449 vec![
1450 "other hint".to_string(),
1451 "parameter hint".to_string(),
1452 "type hint".to_string(),
1453 ],
1454 ),
1455 ] {
1456 edits_made += 1;
1457 update_test_language_settings(cx, |settings| {
1458 settings.defaults.inlay_hints = Some(InlayHintSettings {
1459 enabled: true,
1460 show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1461 show_parameter_hints: new_allowed_hint_kinds
1462 .contains(&Some(InlayHintKind::Parameter)),
1463 show_other_hints: new_allowed_hint_kinds.contains(&None),
1464 })
1465 });
1466 cx.foreground().run_until_parked();
1467 editor.update(cx, |editor, cx| {
1468 assert_eq!(
1469 lsp_request_count.load(Ordering::Relaxed),
1470 2,
1471 "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1472 );
1473 assert_eq!(
1474 vec![
1475 "other hint".to_string(),
1476 "parameter hint".to_string(),
1477 "type hint".to_string(),
1478 ],
1479 cached_hint_labels(editor),
1480 "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1481 );
1482 assert_eq!(
1483 expected_visible_hints,
1484 visible_hint_labels(editor, cx),
1485 "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1486 );
1487 let inlay_cache = editor.inlay_hint_cache();
1488 assert_eq!(
1489 inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1490 "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1491 );
1492 assert_eq!(
1493 inlay_cache.version, edits_made,
1494 "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1495 );
1496 });
1497 }
1498
1499 edits_made += 1;
1500 let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1501 update_test_language_settings(cx, |settings| {
1502 settings.defaults.inlay_hints = Some(InlayHintSettings {
1503 enabled: false,
1504 show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1505 show_parameter_hints: another_allowed_hint_kinds
1506 .contains(&Some(InlayHintKind::Parameter)),
1507 show_other_hints: another_allowed_hint_kinds.contains(&None),
1508 })
1509 });
1510 cx.foreground().run_until_parked();
1511 editor.update(cx, |editor, cx| {
1512 assert_eq!(
1513 lsp_request_count.load(Ordering::Relaxed),
1514 2,
1515 "Should not load new hints when hints got disabled"
1516 );
1517 assert!(
1518 cached_hint_labels(editor).is_empty(),
1519 "Should clear the cache when hints got disabled"
1520 );
1521 assert!(
1522 visible_hint_labels(editor, cx).is_empty(),
1523 "Should clear visible hints when hints got disabled"
1524 );
1525 let inlay_cache = editor.inlay_hint_cache();
1526 assert_eq!(
1527 inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1528 "Should update its allowed hint kinds even when hints got disabled"
1529 );
1530 assert_eq!(
1531 inlay_cache.version, edits_made,
1532 "The editor should update the cache version after hints got disabled"
1533 );
1534 });
1535
1536 fake_server
1537 .request::<lsp::request::InlayHintRefreshRequest>(())
1538 .await
1539 .expect("inlay refresh request failed");
1540 cx.foreground().run_until_parked();
1541 editor.update(cx, |editor, cx| {
1542 assert_eq!(
1543 lsp_request_count.load(Ordering::Relaxed),
1544 2,
1545 "Should not load new hints when they got disabled"
1546 );
1547 assert!(cached_hint_labels(editor).is_empty());
1548 assert!(visible_hint_labels(editor, cx).is_empty());
1549 assert_eq!(
1550 editor.inlay_hint_cache().version, edits_made,
1551 "The editor should not update the cache version after /refresh query without updates"
1552 );
1553 });
1554
1555 let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1556 edits_made += 1;
1557 update_test_language_settings(cx, |settings| {
1558 settings.defaults.inlay_hints = Some(InlayHintSettings {
1559 enabled: true,
1560 show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1561 show_parameter_hints: final_allowed_hint_kinds
1562 .contains(&Some(InlayHintKind::Parameter)),
1563 show_other_hints: final_allowed_hint_kinds.contains(&None),
1564 })
1565 });
1566 cx.foreground().run_until_parked();
1567 editor.update(cx, |editor, cx| {
1568 assert_eq!(
1569 lsp_request_count.load(Ordering::Relaxed),
1570 3,
1571 "Should query for new hints when they got reenabled"
1572 );
1573 assert_eq!(
1574 vec![
1575 "other hint".to_string(),
1576 "parameter hint".to_string(),
1577 "type hint".to_string(),
1578 ],
1579 cached_hint_labels(editor),
1580 "Should get its cached hints fully repopulated after the hints got reenabled"
1581 );
1582 assert_eq!(
1583 vec!["parameter hint".to_string()],
1584 visible_hint_labels(editor, cx),
1585 "Should get its visible hints repopulated and filtered after the h"
1586 );
1587 let inlay_cache = editor.inlay_hint_cache();
1588 assert_eq!(
1589 inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1590 "Cache should update editor settings when hints got reenabled"
1591 );
1592 assert_eq!(
1593 inlay_cache.version, edits_made,
1594 "Cache should update its version after hints got reenabled"
1595 );
1596 });
1597
1598 fake_server
1599 .request::<lsp::request::InlayHintRefreshRequest>(())
1600 .await
1601 .expect("inlay refresh request failed");
1602 cx.foreground().run_until_parked();
1603 editor.update(cx, |editor, cx| {
1604 assert_eq!(
1605 lsp_request_count.load(Ordering::Relaxed),
1606 4,
1607 "Should query for new hints again"
1608 );
1609 assert_eq!(
1610 vec![
1611 "other hint".to_string(),
1612 "parameter hint".to_string(),
1613 "type hint".to_string(),
1614 ],
1615 cached_hint_labels(editor),
1616 );
1617 assert_eq!(
1618 vec!["parameter hint".to_string()],
1619 visible_hint_labels(editor, cx),
1620 );
1621 assert_eq!(editor.inlay_hint_cache().version, edits_made);
1622 });
1623 }
1624
1625 #[gpui::test]
1626 async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
1627 init_test(cx, |settings| {
1628 settings.defaults.inlay_hints = Some(InlayHintSettings {
1629 enabled: true,
1630 show_type_hints: true,
1631 show_parameter_hints: true,
1632 show_other_hints: true,
1633 })
1634 });
1635
1636 let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1637 let fake_server = Arc::new(fake_server);
1638 let lsp_request_count = Arc::new(AtomicU32::new(0));
1639 let another_lsp_request_count = Arc::clone(&lsp_request_count);
1640 fake_server
1641 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1642 let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1643 async move {
1644 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1645 assert_eq!(
1646 params.text_document.uri,
1647 lsp::Url::from_file_path(file_with_hints).unwrap(),
1648 );
1649 Ok(Some(vec![lsp::InlayHint {
1650 position: lsp::Position::new(0, i),
1651 label: lsp::InlayHintLabel::String(i.to_string()),
1652 kind: None,
1653 text_edits: None,
1654 tooltip: None,
1655 padding_left: None,
1656 padding_right: None,
1657 data: None,
1658 }]))
1659 }
1660 })
1661 .next()
1662 .await;
1663
1664 let mut expected_changes = Vec::new();
1665 for change_after_opening in [
1666 "initial change #1",
1667 "initial change #2",
1668 "initial change #3",
1669 ] {
1670 editor.update(cx, |editor, cx| {
1671 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1672 editor.handle_input(change_after_opening, cx);
1673 });
1674 expected_changes.push(change_after_opening);
1675 }
1676
1677 cx.foreground().run_until_parked();
1678
1679 editor.update(cx, |editor, cx| {
1680 let current_text = editor.text(cx);
1681 for change in &expected_changes {
1682 assert!(
1683 current_text.contains(change),
1684 "Should apply all changes made"
1685 );
1686 }
1687 assert_eq!(
1688 lsp_request_count.load(Ordering::Relaxed),
1689 2,
1690 "Should query new hints twice: for editor init and for the last edit that interrupted all others"
1691 );
1692 let expected_hints = vec!["2".to_string()];
1693 assert_eq!(
1694 expected_hints,
1695 cached_hint_labels(editor),
1696 "Should get hints from the last edit landed only"
1697 );
1698 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1699 assert_eq!(
1700 editor.inlay_hint_cache().version, 1,
1701 "Only one update should be registered in the cache after all cancellations"
1702 );
1703 });
1704
1705 let mut edits = Vec::new();
1706 for async_later_change in [
1707 "another change #1",
1708 "another change #2",
1709 "another change #3",
1710 ] {
1711 expected_changes.push(async_later_change);
1712 let task_editor = editor.clone();
1713 let mut task_cx = cx.clone();
1714 edits.push(cx.foreground().spawn(async move {
1715 task_editor.update(&mut task_cx, |editor, cx| {
1716 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1717 editor.handle_input(async_later_change, cx);
1718 });
1719 }));
1720 }
1721 let _ = futures::future::join_all(edits).await;
1722 cx.foreground().run_until_parked();
1723
1724 editor.update(cx, |editor, cx| {
1725 let current_text = editor.text(cx);
1726 for change in &expected_changes {
1727 assert!(
1728 current_text.contains(change),
1729 "Should apply all changes made"
1730 );
1731 }
1732 assert_eq!(
1733 lsp_request_count.load(Ordering::SeqCst),
1734 3,
1735 "Should query new hints one more time, for the last edit only"
1736 );
1737 let expected_hints = vec!["3".to_string()];
1738 assert_eq!(
1739 expected_hints,
1740 cached_hint_labels(editor),
1741 "Should get hints from the last edit landed only"
1742 );
1743 assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1744 assert_eq!(
1745 editor.inlay_hint_cache().version,
1746 2,
1747 "Should update the cache version once more, for the new change"
1748 );
1749 });
1750 }
1751
1752 #[gpui::test]
1753 async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
1754 init_test(cx, |settings| {
1755 settings.defaults.inlay_hints = Some(InlayHintSettings {
1756 enabled: true,
1757 show_type_hints: true,
1758 show_parameter_hints: true,
1759 show_other_hints: true,
1760 })
1761 });
1762
1763 let mut language = Language::new(
1764 LanguageConfig {
1765 name: "Rust".into(),
1766 path_suffixes: vec!["rs".to_string()],
1767 ..Default::default()
1768 },
1769 Some(tree_sitter_rust::language()),
1770 );
1771 let mut fake_servers = language
1772 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1773 capabilities: lsp::ServerCapabilities {
1774 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1775 ..Default::default()
1776 },
1777 ..Default::default()
1778 }))
1779 .await;
1780 let fs = FakeFs::new(cx.background());
1781 fs.insert_tree(
1782 "/a",
1783 json!({
1784 "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
1785 "other.rs": "// Test file",
1786 }),
1787 )
1788 .await;
1789 let project = Project::test(fs, ["/a".as_ref()], cx).await;
1790 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
1791 let workspace = cx
1792 .add_window(|cx| Workspace::test_new(project.clone(), cx))
1793 .root(cx);
1794 let worktree_id = workspace.update(cx, |workspace, cx| {
1795 workspace.project().read_with(cx, |project, cx| {
1796 project.worktrees(cx).next().unwrap().read(cx).id()
1797 })
1798 });
1799
1800 let _buffer = project
1801 .update(cx, |project, cx| {
1802 project.open_local_buffer("/a/main.rs", cx)
1803 })
1804 .await
1805 .unwrap();
1806 cx.foreground().run_until_parked();
1807 cx.foreground().start_waiting();
1808 let fake_server = fake_servers.next().await.unwrap();
1809 let editor = workspace
1810 .update(cx, |workspace, cx| {
1811 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1812 })
1813 .await
1814 .unwrap()
1815 .downcast::<Editor>()
1816 .unwrap();
1817 let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
1818 let lsp_request_count = Arc::new(AtomicU32::new(0));
1819 let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
1820 let closure_lsp_request_count = Arc::clone(&lsp_request_count);
1821 fake_server
1822 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1823 let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
1824 let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
1825 async move {
1826 assert_eq!(
1827 params.text_document.uri,
1828 lsp::Url::from_file_path("/a/main.rs").unwrap(),
1829 );
1830
1831 task_lsp_request_ranges.lock().push(params.range);
1832 let query_start = params.range.start;
1833 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
1834 Ok(Some(vec![lsp::InlayHint {
1835 position: query_start,
1836 label: lsp::InlayHintLabel::String(i.to_string()),
1837 kind: None,
1838 text_edits: None,
1839 tooltip: None,
1840 padding_left: None,
1841 padding_right: None,
1842 data: None,
1843 }]))
1844 }
1845 })
1846 .next()
1847 .await;
1848 fn editor_visible_range(
1849 editor: &ViewHandle<Editor>,
1850 cx: &mut gpui::TestAppContext,
1851 ) -> Range<Point> {
1852 let ranges = editor.update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx));
1853 assert_eq!(
1854 ranges.len(),
1855 1,
1856 "Single buffer should produce a single excerpt with visible range"
1857 );
1858 let (_, (excerpt_buffer, _, excerpt_visible_range)) =
1859 ranges.into_iter().next().unwrap();
1860 excerpt_buffer.update(cx, |buffer, _| {
1861 let snapshot = buffer.snapshot();
1862 let start = buffer
1863 .anchor_before(excerpt_visible_range.start)
1864 .to_point(&snapshot);
1865 let end = buffer
1866 .anchor_after(excerpt_visible_range.end)
1867 .to_point(&snapshot);
1868 start..end
1869 })
1870 }
1871
1872 let initial_visible_range = editor_visible_range(&editor, cx);
1873 let expected_initial_query_range_end =
1874 lsp::Position::new(initial_visible_range.end.row * 2, 1);
1875 cx.foreground().run_until_parked();
1876 editor.update(cx, |editor, cx| {
1877 let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1878 assert_eq!(ranges.len(), 1,
1879 "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:?}");
1880 let query_range = &ranges[0];
1881 assert_eq!(query_range.start, lsp::Position::new(0, 0), "Should query initially from the beginning of the document");
1882 assert_eq!(query_range.end, expected_initial_query_range_end, "Should query initially for double lines of the visible part of the document");
1883
1884 assert_eq!(lsp_request_count.load(Ordering::Acquire), 1);
1885 let expected_layers = vec!["1".to_string()];
1886 assert_eq!(
1887 expected_layers,
1888 cached_hint_labels(editor),
1889 "Should have hints from both LSP requests made for a big file"
1890 );
1891 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1892 assert_eq!(
1893 editor.inlay_hint_cache().version, 1,
1894 "LSP queries should've bumped the cache version"
1895 );
1896 });
1897
1898 editor.update(cx, |editor, cx| {
1899 editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1900 editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1901 });
1902
1903 let visible_range_after_scrolls = editor_visible_range(&editor, cx);
1904 let visible_line_count =
1905 editor.update(cx, |editor, _| editor.visible_line_count().unwrap());
1906 cx.foreground().run_until_parked();
1907 let selection_in_cached_range = editor.update(cx, |editor, cx| {
1908 let ranges = lsp_request_ranges
1909 .lock()
1910 .drain(..)
1911 .sorted_by_key(|r| r.start)
1912 .collect::<Vec<_>>();
1913 assert_eq!(
1914 ranges.len(),
1915 2,
1916 "Should query 2 ranges after both scrolls, but got: {ranges:?}"
1917 );
1918 let first_scroll = &ranges[0];
1919 let second_scroll = &ranges[1];
1920 assert_eq!(
1921 first_scroll.end, second_scroll.start,
1922 "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
1923 );
1924 assert_eq!(
1925 first_scroll.start, expected_initial_query_range_end,
1926 "First scroll should start the query right after the end of the original scroll",
1927 );
1928 assert_eq!(
1929 second_scroll.end,
1930 lsp::Position::new(
1931 visible_range_after_scrolls.end.row
1932 + visible_line_count.ceil() as u32,
1933 0
1934 ),
1935 "Second scroll should query one more screen down after the end of the visible range"
1936 );
1937
1938 assert_eq!(
1939 lsp_request_count.load(Ordering::Acquire),
1940 3,
1941 "Should query for hints after every scroll"
1942 );
1943 let expected_layers = vec!["1".to_string(), "2".to_string(), "3".to_string()];
1944 assert_eq!(
1945 expected_layers,
1946 cached_hint_labels(editor),
1947 "Should have hints from the new LSP response after the edit"
1948 );
1949 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1950 assert_eq!(
1951 editor.inlay_hint_cache().version,
1952 3,
1953 "Should update the cache for every LSP response with hints added"
1954 );
1955
1956 let mut selection_in_cached_range = visible_range_after_scrolls.end;
1957 selection_in_cached_range.row -= visible_line_count.ceil() as u32;
1958 selection_in_cached_range
1959 });
1960
1961 editor.update(cx, |editor, cx| {
1962 editor.change_selections(Some(Autoscroll::center()), cx, |s| {
1963 s.select_ranges([selection_in_cached_range..selection_in_cached_range])
1964 });
1965 });
1966 cx.foreground().run_until_parked();
1967 editor.update(cx, |_, _| {
1968 let ranges = lsp_request_ranges
1969 .lock()
1970 .drain(..)
1971 .sorted_by_key(|r| r.start)
1972 .collect::<Vec<_>>();
1973 assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
1974 assert_eq!(lsp_request_count.load(Ordering::Acquire), 3);
1975 });
1976
1977 editor.update(cx, |editor, cx| {
1978 editor.handle_input("++++more text++++", cx);
1979 });
1980 cx.foreground().run_until_parked();
1981 editor.update(cx, |editor, cx| {
1982 let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1983 assert_eq!(ranges.len(), 1,
1984 "On edit, should scroll to selection and query a range around it. Instead, got query ranges {ranges:?}");
1985 let query_range = &ranges[0];
1986 assert!(query_range.start.line < selection_in_cached_range.row,
1987 "Hints should be queried with the selected range after the query range start");
1988 assert!(query_range.end.line > selection_in_cached_range.row,
1989 "Hints should be queried with the selected range before the query range end");
1990 assert!(query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
1991 "Hints query range should contain one more screen before");
1992 assert!(query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
1993 "Hints query range should contain one more screen after");
1994
1995 assert_eq!(lsp_request_count.load(Ordering::Acquire), 4, "Should query for hints once after the edit");
1996 let expected_layers = vec!["4".to_string()];
1997 assert_eq!(expected_layers, cached_hint_labels(editor),
1998 "Should have hints from the new LSP response after the edit");
1999 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2000 assert_eq!(editor.inlay_hint_cache().version, 4, "Should update the cache for every LSP response with hints added");
2001 });
2002 }
2003
2004 #[gpui::test]
2005 async fn test_multiple_excerpts_large_multibuffer(
2006 deterministic: Arc<Deterministic>,
2007 cx: &mut gpui::TestAppContext,
2008 ) {
2009 init_test(cx, |settings| {
2010 settings.defaults.inlay_hints = Some(InlayHintSettings {
2011 enabled: true,
2012 show_type_hints: true,
2013 show_parameter_hints: true,
2014 show_other_hints: true,
2015 })
2016 });
2017
2018 let mut language = Language::new(
2019 LanguageConfig {
2020 name: "Rust".into(),
2021 path_suffixes: vec!["rs".to_string()],
2022 ..Default::default()
2023 },
2024 Some(tree_sitter_rust::language()),
2025 );
2026 let mut fake_servers = language
2027 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2028 capabilities: lsp::ServerCapabilities {
2029 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2030 ..Default::default()
2031 },
2032 ..Default::default()
2033 }))
2034 .await;
2035 let language = Arc::new(language);
2036 let fs = FakeFs::new(cx.background());
2037 fs.insert_tree(
2038 "/a",
2039 json!({
2040 "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2041 "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2042 }),
2043 )
2044 .await;
2045 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2046 project.update(cx, |project, _| {
2047 project.languages().add(Arc::clone(&language))
2048 });
2049 let workspace = cx
2050 .add_window(|cx| Workspace::test_new(project.clone(), cx))
2051 .root(cx);
2052 let worktree_id = workspace.update(cx, |workspace, cx| {
2053 workspace.project().read_with(cx, |project, cx| {
2054 project.worktrees(cx).next().unwrap().read(cx).id()
2055 })
2056 });
2057
2058 let buffer_1 = project
2059 .update(cx, |project, cx| {
2060 project.open_buffer((worktree_id, "main.rs"), cx)
2061 })
2062 .await
2063 .unwrap();
2064 let buffer_2 = project
2065 .update(cx, |project, cx| {
2066 project.open_buffer((worktree_id, "other.rs"), cx)
2067 })
2068 .await
2069 .unwrap();
2070 let multibuffer = cx.add_model(|cx| {
2071 let mut multibuffer = MultiBuffer::new(0);
2072 multibuffer.push_excerpts(
2073 buffer_1.clone(),
2074 [
2075 ExcerptRange {
2076 context: Point::new(0, 0)..Point::new(2, 0),
2077 primary: None,
2078 },
2079 ExcerptRange {
2080 context: Point::new(4, 0)..Point::new(11, 0),
2081 primary: None,
2082 },
2083 ExcerptRange {
2084 context: Point::new(22, 0)..Point::new(33, 0),
2085 primary: None,
2086 },
2087 ExcerptRange {
2088 context: Point::new(44, 0)..Point::new(55, 0),
2089 primary: None,
2090 },
2091 ExcerptRange {
2092 context: Point::new(56, 0)..Point::new(66, 0),
2093 primary: None,
2094 },
2095 ExcerptRange {
2096 context: Point::new(67, 0)..Point::new(77, 0),
2097 primary: None,
2098 },
2099 ],
2100 cx,
2101 );
2102 multibuffer.push_excerpts(
2103 buffer_2.clone(),
2104 [
2105 ExcerptRange {
2106 context: Point::new(0, 1)..Point::new(2, 1),
2107 primary: None,
2108 },
2109 ExcerptRange {
2110 context: Point::new(4, 1)..Point::new(11, 1),
2111 primary: None,
2112 },
2113 ExcerptRange {
2114 context: Point::new(22, 1)..Point::new(33, 1),
2115 primary: None,
2116 },
2117 ExcerptRange {
2118 context: Point::new(44, 1)..Point::new(55, 1),
2119 primary: None,
2120 },
2121 ExcerptRange {
2122 context: Point::new(56, 1)..Point::new(66, 1),
2123 primary: None,
2124 },
2125 ExcerptRange {
2126 context: Point::new(67, 1)..Point::new(77, 1),
2127 primary: None,
2128 },
2129 ],
2130 cx,
2131 );
2132 multibuffer
2133 });
2134
2135 deterministic.run_until_parked();
2136 cx.foreground().run_until_parked();
2137 let editor = cx
2138 .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
2139 .root(cx);
2140 let editor_edited = Arc::new(AtomicBool::new(false));
2141 let fake_server = fake_servers.next().await.unwrap();
2142 let closure_editor_edited = Arc::clone(&editor_edited);
2143 fake_server
2144 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2145 let task_editor_edited = Arc::clone(&closure_editor_edited);
2146 async move {
2147 let hint_text = if params.text_document.uri
2148 == lsp::Url::from_file_path("/a/main.rs").unwrap()
2149 {
2150 "main hint"
2151 } else if params.text_document.uri
2152 == lsp::Url::from_file_path("/a/other.rs").unwrap()
2153 {
2154 "other hint"
2155 } else {
2156 panic!("unexpected uri: {:?}", params.text_document.uri);
2157 };
2158
2159 // one hint per excerpt
2160 let positions = [
2161 lsp::Position::new(0, 2),
2162 lsp::Position::new(4, 2),
2163 lsp::Position::new(22, 2),
2164 lsp::Position::new(44, 2),
2165 lsp::Position::new(56, 2),
2166 lsp::Position::new(67, 2),
2167 ];
2168 let out_of_range_hint = lsp::InlayHint {
2169 position: lsp::Position::new(
2170 params.range.start.line + 99,
2171 params.range.start.character + 99,
2172 ),
2173 label: lsp::InlayHintLabel::String(
2174 "out of excerpt range, should be ignored".to_string(),
2175 ),
2176 kind: None,
2177 text_edits: None,
2178 tooltip: None,
2179 padding_left: None,
2180 padding_right: None,
2181 data: None,
2182 };
2183
2184 let edited = task_editor_edited.load(Ordering::Acquire);
2185 Ok(Some(
2186 std::iter::once(out_of_range_hint)
2187 .chain(positions.into_iter().enumerate().map(|(i, position)| {
2188 lsp::InlayHint {
2189 position,
2190 label: lsp::InlayHintLabel::String(format!(
2191 "{hint_text}{} #{i}",
2192 if edited { "(edited)" } else { "" },
2193 )),
2194 kind: None,
2195 text_edits: None,
2196 tooltip: None,
2197 padding_left: None,
2198 padding_right: None,
2199 data: None,
2200 }
2201 }))
2202 .collect(),
2203 ))
2204 }
2205 })
2206 .next()
2207 .await;
2208 cx.foreground().run_until_parked();
2209
2210 editor.update(cx, |editor, cx| {
2211 let expected_layers = vec![
2212 "main hint #0".to_string(),
2213 "main hint #1".to_string(),
2214 "main hint #2".to_string(),
2215 "main hint #3".to_string(),
2216 ];
2217 assert_eq!(
2218 expected_layers,
2219 cached_hint_labels(editor),
2220 "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2221 );
2222 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2223 assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(), "Every visible excerpt hints should bump the verison");
2224 });
2225
2226 editor.update(cx, |editor, cx| {
2227 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2228 s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2229 });
2230 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2231 s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2232 });
2233 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2234 s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2235 });
2236 });
2237 cx.foreground().run_until_parked();
2238 editor.update(cx, |editor, cx| {
2239 let expected_layers = vec![
2240 "main hint #0".to_string(),
2241 "main hint #1".to_string(),
2242 "main hint #2".to_string(),
2243 "main hint #3".to_string(),
2244 "main hint #4".to_string(),
2245 "main hint #5".to_string(),
2246 "other hint #0".to_string(),
2247 "other hint #1".to_string(),
2248 "other hint #2".to_string(),
2249 ];
2250 assert_eq!(expected_layers, cached_hint_labels(editor),
2251 "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2252 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2253 assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(),
2254 "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
2255 });
2256
2257 editor.update(cx, |editor, cx| {
2258 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2259 s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2260 });
2261 });
2262 cx.foreground().run_until_parked();
2263 let last_scroll_update_version = editor.update(cx, |editor, cx| {
2264 let expected_layers = vec![
2265 "main hint #0".to_string(),
2266 "main hint #1".to_string(),
2267 "main hint #2".to_string(),
2268 "main hint #3".to_string(),
2269 "main hint #4".to_string(),
2270 "main hint #5".to_string(),
2271 "other hint #0".to_string(),
2272 "other hint #1".to_string(),
2273 "other hint #2".to_string(),
2274 "other hint #3".to_string(),
2275 "other hint #4".to_string(),
2276 "other hint #5".to_string(),
2277 ];
2278 assert_eq!(expected_layers, cached_hint_labels(editor),
2279 "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2280 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2281 assert_eq!(editor.inlay_hint_cache().version, expected_layers.len());
2282 expected_layers.len()
2283 });
2284
2285 editor.update(cx, |editor, cx| {
2286 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2287 s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2288 });
2289 });
2290 cx.foreground().run_until_parked();
2291 editor.update(cx, |editor, cx| {
2292 let expected_layers = vec![
2293 "main hint #0".to_string(),
2294 "main hint #1".to_string(),
2295 "main hint #2".to_string(),
2296 "main hint #3".to_string(),
2297 "main hint #4".to_string(),
2298 "main hint #5".to_string(),
2299 "other hint #0".to_string(),
2300 "other hint #1".to_string(),
2301 "other hint #2".to_string(),
2302 "other hint #3".to_string(),
2303 "other hint #4".to_string(),
2304 "other hint #5".to_string(),
2305 ];
2306 assert_eq!(expected_layers, cached_hint_labels(editor),
2307 "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2308 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2309 assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
2310 });
2311
2312 editor_edited.store(true, Ordering::Release);
2313 editor.update(cx, |editor, cx| {
2314 editor.change_selections(None, cx, |s| {
2315 s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
2316 });
2317 editor.handle_input("++++more text++++", cx);
2318 });
2319 cx.foreground().run_until_parked();
2320 editor.update(cx, |editor, cx| {
2321 let expected_layers = vec![
2322 "main hint(edited) #0".to_string(),
2323 "main hint(edited) #1".to_string(),
2324 "main hint(edited) #2".to_string(),
2325 "main hint(edited) #3".to_string(),
2326 "main hint(edited) #4".to_string(),
2327 "main hint(edited) #5".to_string(),
2328 "other hint(edited) #0".to_string(),
2329 "other hint(edited) #1".to_string(),
2330 ];
2331 assert_eq!(
2332 expected_layers,
2333 cached_hint_labels(editor),
2334 "After multibuffer edit, editor gets scolled back to the last selection; \
2335all hints should be invalidated and requeried for all of its visible excerpts"
2336 );
2337 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2338 assert_eq!(
2339 editor.inlay_hint_cache().version,
2340 last_scroll_update_version + expected_layers.len(),
2341 "Due to every excerpt having one hint, cache should update per new excerpt received"
2342 );
2343 });
2344 }
2345
2346 #[gpui::test]
2347 async fn test_excerpts_removed(
2348 deterministic: Arc<Deterministic>,
2349 cx: &mut gpui::TestAppContext,
2350 ) {
2351 init_test(cx, |settings| {
2352 settings.defaults.inlay_hints = Some(InlayHintSettings {
2353 enabled: true,
2354 show_type_hints: false,
2355 show_parameter_hints: false,
2356 show_other_hints: false,
2357 })
2358 });
2359
2360 let mut language = Language::new(
2361 LanguageConfig {
2362 name: "Rust".into(),
2363 path_suffixes: vec!["rs".to_string()],
2364 ..Default::default()
2365 },
2366 Some(tree_sitter_rust::language()),
2367 );
2368 let mut fake_servers = language
2369 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2370 capabilities: lsp::ServerCapabilities {
2371 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2372 ..Default::default()
2373 },
2374 ..Default::default()
2375 }))
2376 .await;
2377 let language = Arc::new(language);
2378 let fs = FakeFs::new(cx.background());
2379 fs.insert_tree(
2380 "/a",
2381 json!({
2382 "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2383 "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2384 }),
2385 )
2386 .await;
2387 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2388 project.update(cx, |project, _| {
2389 project.languages().add(Arc::clone(&language))
2390 });
2391 let workspace = cx
2392 .add_window(|cx| Workspace::test_new(project.clone(), cx))
2393 .root(cx);
2394 let worktree_id = workspace.update(cx, |workspace, cx| {
2395 workspace.project().read_with(cx, |project, cx| {
2396 project.worktrees(cx).next().unwrap().read(cx).id()
2397 })
2398 });
2399
2400 let buffer_1 = project
2401 .update(cx, |project, cx| {
2402 project.open_buffer((worktree_id, "main.rs"), cx)
2403 })
2404 .await
2405 .unwrap();
2406 let buffer_2 = project
2407 .update(cx, |project, cx| {
2408 project.open_buffer((worktree_id, "other.rs"), cx)
2409 })
2410 .await
2411 .unwrap();
2412 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
2413 let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2414 let buffer_1_excerpts = multibuffer.push_excerpts(
2415 buffer_1.clone(),
2416 [ExcerptRange {
2417 context: Point::new(0, 0)..Point::new(2, 0),
2418 primary: None,
2419 }],
2420 cx,
2421 );
2422 let buffer_2_excerpts = multibuffer.push_excerpts(
2423 buffer_2.clone(),
2424 [ExcerptRange {
2425 context: Point::new(0, 1)..Point::new(2, 1),
2426 primary: None,
2427 }],
2428 cx,
2429 );
2430 (buffer_1_excerpts, buffer_2_excerpts)
2431 });
2432
2433 assert!(!buffer_1_excerpts.is_empty());
2434 assert!(!buffer_2_excerpts.is_empty());
2435
2436 deterministic.run_until_parked();
2437 cx.foreground().run_until_parked();
2438 let editor = cx
2439 .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
2440 .root(cx);
2441 let editor_edited = Arc::new(AtomicBool::new(false));
2442 let fake_server = fake_servers.next().await.unwrap();
2443 let closure_editor_edited = Arc::clone(&editor_edited);
2444 fake_server
2445 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2446 let task_editor_edited = Arc::clone(&closure_editor_edited);
2447 async move {
2448 let hint_text = if params.text_document.uri
2449 == lsp::Url::from_file_path("/a/main.rs").unwrap()
2450 {
2451 "main hint"
2452 } else if params.text_document.uri
2453 == lsp::Url::from_file_path("/a/other.rs").unwrap()
2454 {
2455 "other hint"
2456 } else {
2457 panic!("unexpected uri: {:?}", params.text_document.uri);
2458 };
2459
2460 let positions = [
2461 lsp::Position::new(0, 2),
2462 lsp::Position::new(4, 2),
2463 lsp::Position::new(22, 2),
2464 lsp::Position::new(44, 2),
2465 lsp::Position::new(56, 2),
2466 lsp::Position::new(67, 2),
2467 ];
2468 let out_of_range_hint = lsp::InlayHint {
2469 position: lsp::Position::new(
2470 params.range.start.line + 99,
2471 params.range.start.character + 99,
2472 ),
2473 label: lsp::InlayHintLabel::String(
2474 "out of excerpt range, should be ignored".to_string(),
2475 ),
2476 kind: None,
2477 text_edits: None,
2478 tooltip: None,
2479 padding_left: None,
2480 padding_right: None,
2481 data: None,
2482 };
2483
2484 let edited = task_editor_edited.load(Ordering::Acquire);
2485 Ok(Some(
2486 std::iter::once(out_of_range_hint)
2487 .chain(positions.into_iter().enumerate().map(|(i, position)| {
2488 lsp::InlayHint {
2489 position,
2490 label: lsp::InlayHintLabel::String(format!(
2491 "{hint_text}{} #{i}",
2492 if edited { "(edited)" } else { "" },
2493 )),
2494 kind: None,
2495 text_edits: None,
2496 tooltip: None,
2497 padding_left: None,
2498 padding_right: None,
2499 data: None,
2500 }
2501 }))
2502 .collect(),
2503 ))
2504 }
2505 })
2506 .next()
2507 .await;
2508 cx.foreground().run_until_parked();
2509
2510 editor.update(cx, |editor, cx| {
2511 assert_eq!(
2512 vec!["main hint #0".to_string(), "other hint #0".to_string()],
2513 cached_hint_labels(editor),
2514 "Cache should update for both excerpts despite hints display was disabled"
2515 );
2516 assert!(
2517 visible_hint_labels(editor, cx).is_empty(),
2518 "All hints are disabled and should not be shown despite being present in the cache"
2519 );
2520 assert_eq!(
2521 editor.inlay_hint_cache().version,
2522 2,
2523 "Cache should update once per excerpt query"
2524 );
2525 });
2526
2527 editor.update(cx, |editor, cx| {
2528 editor.buffer().update(cx, |multibuffer, cx| {
2529 multibuffer.remove_excerpts(buffer_2_excerpts, cx)
2530 })
2531 });
2532 cx.foreground().run_until_parked();
2533 editor.update(cx, |editor, cx| {
2534 assert_eq!(
2535 vec!["main hint #0".to_string()],
2536 cached_hint_labels(editor),
2537 "For the removed excerpt, should clean corresponding cached hints"
2538 );
2539 assert!(
2540 visible_hint_labels(editor, cx).is_empty(),
2541 "All hints are disabled and should not be shown despite being present in the cache"
2542 );
2543 assert_eq!(
2544 editor.inlay_hint_cache().version,
2545 2,
2546 "Excerpt removal should trigger a cache update"
2547 );
2548 });
2549
2550 update_test_language_settings(cx, |settings| {
2551 settings.defaults.inlay_hints = Some(InlayHintSettings {
2552 enabled: true,
2553 show_type_hints: true,
2554 show_parameter_hints: true,
2555 show_other_hints: true,
2556 })
2557 });
2558 cx.foreground().run_until_parked();
2559 editor.update(cx, |editor, cx| {
2560 let expected_hints = vec!["main hint #0".to_string()];
2561 assert_eq!(
2562 expected_hints,
2563 cached_hint_labels(editor),
2564 "Hint display settings change should not change the cache"
2565 );
2566 assert_eq!(
2567 expected_hints,
2568 visible_hint_labels(editor, cx),
2569 "Settings change should make cached hints visible"
2570 );
2571 assert_eq!(
2572 editor.inlay_hint_cache().version,
2573 3,
2574 "Settings change should trigger a cache update"
2575 );
2576 });
2577 }
2578
2579 #[gpui::test]
2580 async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
2581 init_test(cx, |settings| {
2582 settings.defaults.inlay_hints = Some(InlayHintSettings {
2583 enabled: true,
2584 show_type_hints: true,
2585 show_parameter_hints: true,
2586 show_other_hints: true,
2587 })
2588 });
2589
2590 let mut language = Language::new(
2591 LanguageConfig {
2592 name: "Rust".into(),
2593 path_suffixes: vec!["rs".to_string()],
2594 ..Default::default()
2595 },
2596 Some(tree_sitter_rust::language()),
2597 );
2598 let mut fake_servers = language
2599 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2600 capabilities: lsp::ServerCapabilities {
2601 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2602 ..Default::default()
2603 },
2604 ..Default::default()
2605 }))
2606 .await;
2607 let fs = FakeFs::new(cx.background());
2608 fs.insert_tree(
2609 "/a",
2610 json!({
2611 "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "√".repeat(10)).repeat(500)),
2612 "other.rs": "// Test file",
2613 }),
2614 )
2615 .await;
2616 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2617 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2618 let workspace = cx
2619 .add_window(|cx| Workspace::test_new(project.clone(), cx))
2620 .root(cx);
2621 let worktree_id = workspace.update(cx, |workspace, cx| {
2622 workspace.project().read_with(cx, |project, cx| {
2623 project.worktrees(cx).next().unwrap().read(cx).id()
2624 })
2625 });
2626
2627 let _buffer = project
2628 .update(cx, |project, cx| {
2629 project.open_local_buffer("/a/main.rs", cx)
2630 })
2631 .await
2632 .unwrap();
2633 cx.foreground().run_until_parked();
2634 cx.foreground().start_waiting();
2635 let fake_server = fake_servers.next().await.unwrap();
2636 let editor = workspace
2637 .update(cx, |workspace, cx| {
2638 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2639 })
2640 .await
2641 .unwrap()
2642 .downcast::<Editor>()
2643 .unwrap();
2644 let lsp_request_count = Arc::new(AtomicU32::new(0));
2645 let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2646 fake_server
2647 .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2648 let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2649 async move {
2650 assert_eq!(
2651 params.text_document.uri,
2652 lsp::Url::from_file_path("/a/main.rs").unwrap(),
2653 );
2654 let query_start = params.range.start;
2655 let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
2656 Ok(Some(vec![lsp::InlayHint {
2657 position: query_start,
2658 label: lsp::InlayHintLabel::String(i.to_string()),
2659 kind: None,
2660 text_edits: None,
2661 tooltip: None,
2662 padding_left: None,
2663 padding_right: None,
2664 data: None,
2665 }]))
2666 }
2667 })
2668 .next()
2669 .await;
2670
2671 cx.foreground().run_until_parked();
2672 editor.update(cx, |editor, cx| {
2673 editor.change_selections(None, cx, |s| {
2674 s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
2675 })
2676 });
2677 cx.foreground().run_until_parked();
2678 editor.update(cx, |editor, cx| {
2679 let expected_layers = vec!["1".to_string()];
2680 assert_eq!(expected_layers, cached_hint_labels(editor));
2681 assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2682 assert_eq!(editor.inlay_hint_cache().version, 1);
2683 });
2684 }
2685
2686 pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2687 cx.foreground().forbid_parking();
2688
2689 cx.update(|cx| {
2690 cx.set_global(SettingsStore::test(cx));
2691 theme::init((), cx);
2692 client::init_settings(cx);
2693 language::init(cx);
2694 Project::init_settings(cx);
2695 workspace::init_settings(cx);
2696 crate::init(cx);
2697 });
2698
2699 update_test_language_settings(cx, f);
2700 }
2701
2702 async fn prepare_test_objects(
2703 cx: &mut TestAppContext,
2704 ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
2705 let mut language = Language::new(
2706 LanguageConfig {
2707 name: "Rust".into(),
2708 path_suffixes: vec!["rs".to_string()],
2709 ..Default::default()
2710 },
2711 Some(tree_sitter_rust::language()),
2712 );
2713 let mut fake_servers = language
2714 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2715 capabilities: lsp::ServerCapabilities {
2716 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2717 ..Default::default()
2718 },
2719 ..Default::default()
2720 }))
2721 .await;
2722
2723 let fs = FakeFs::new(cx.background());
2724 fs.insert_tree(
2725 "/a",
2726 json!({
2727 "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
2728 "other.rs": "// Test file",
2729 }),
2730 )
2731 .await;
2732
2733 let project = Project::test(fs, ["/a".as_ref()], cx).await;
2734 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2735 let workspace = cx
2736 .add_window(|cx| Workspace::test_new(project.clone(), cx))
2737 .root(cx);
2738 let worktree_id = workspace.update(cx, |workspace, cx| {
2739 workspace.project().read_with(cx, |project, cx| {
2740 project.worktrees(cx).next().unwrap().read(cx).id()
2741 })
2742 });
2743
2744 let _buffer = project
2745 .update(cx, |project, cx| {
2746 project.open_local_buffer("/a/main.rs", cx)
2747 })
2748 .await
2749 .unwrap();
2750 cx.foreground().run_until_parked();
2751 cx.foreground().start_waiting();
2752 let fake_server = fake_servers.next().await.unwrap();
2753 let editor = workspace
2754 .update(cx, |workspace, cx| {
2755 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2756 })
2757 .await
2758 .unwrap()
2759 .downcast::<Editor>()
2760 .unwrap();
2761
2762 ("/a/main.rs", editor, fake_server)
2763 }
2764
2765 fn cached_hint_labels(editor: &Editor) -> Vec<String> {
2766 let mut labels = Vec::new();
2767 for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
2768 let excerpt_hints = excerpt_hints.read();
2769 for (_, inlay) in excerpt_hints.hints.iter() {
2770 match &inlay.label {
2771 project::InlayHintLabel::String(s) => labels.push(s.to_string()),
2772 _ => unreachable!(),
2773 }
2774 }
2775 }
2776
2777 labels.sort();
2778 labels
2779 }
2780
2781 fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
2782 let mut hints = editor
2783 .visible_inlay_hints(cx)
2784 .into_iter()
2785 .map(|hint| hint.text.to_string())
2786 .collect::<Vec<_>>();
2787 hints.sort();
2788 hints
2789 }
2790}