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