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