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