inlay_hint_cache.rs

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