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