inlay_hint_cache.rs

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