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