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