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    ExcerptEdited,
  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::ExcerptEdited
  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>, 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>, 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, excerpt_visible_range)) in excerpts_to_query {
 352        if !excerpt_visible_range.is_empty() {
 353            let buffer = buffer_handle.read(cx);
 354            let buffer_snapshot = buffer.snapshot();
 355            let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
 356            if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
 357                let new_task_buffer_version = buffer_snapshot.version();
 358                let cached_excerpt_hints = cached_excerpt_hints.read();
 359                let cached_buffer_version = &cached_excerpt_hints.buffer_version;
 360                if cached_excerpt_hints.version > update_cache_version
 361                    || cached_buffer_version.changed_since(new_task_buffer_version)
 362                {
 363                    return;
 364                }
 365                if !new_task_buffer_version.changed_since(&cached_buffer_version)
 366                    && !matches!(invalidate, InvalidationStrategy::RefreshRequested)
 367                {
 368                    return;
 369                }
 370            };
 371
 372            let buffer_id = buffer.remote_id();
 373            let excerpt_visible_range_start = buffer.anchor_before(excerpt_visible_range.start);
 374            let excerpt_visible_range_end = buffer.anchor_after(excerpt_visible_range.end);
 375
 376            let (multi_buffer_snapshot, full_excerpt_range) =
 377                editor.buffer.update(cx, |multi_buffer, cx| {
 378                    let multi_buffer_snapshot = multi_buffer.snapshot(cx);
 379                    (
 380                        multi_buffer_snapshot,
 381                        multi_buffer
 382                            .excerpts_for_buffer(&buffer_handle, cx)
 383                            .into_iter()
 384                            .find(|(id, _)| id == &excerpt_id)
 385                            .map(|(_, range)| range.context),
 386                    )
 387                });
 388
 389            if let Some(full_excerpt_range) = full_excerpt_range {
 390                let query = ExcerptQuery {
 391                    buffer_id,
 392                    excerpt_id,
 393                    dimensions: ExcerptDimensions {
 394                        excerpt_range_start: full_excerpt_range.start,
 395                        excerpt_range_end: full_excerpt_range.end,
 396                        excerpt_visible_range_start,
 397                        excerpt_visible_range_end,
 398                    },
 399                    cache_version: update_cache_version,
 400                    invalidate,
 401                };
 402
 403                let new_update_task = |is_refresh_after_regular_task| {
 404                    new_update_task(
 405                        query,
 406                        multi_buffer_snapshot,
 407                        buffer_snapshot,
 408                        Arc::clone(&visible_hints),
 409                        cached_excerpt_hints,
 410                        is_refresh_after_regular_task,
 411                        cx,
 412                    )
 413                };
 414                match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
 415                    hash_map::Entry::Occupied(mut o) => {
 416                        let update_task = o.get_mut();
 417                        match (update_task.invalidate, invalidate) {
 418                            (_, InvalidationStrategy::None) => {}
 419                            (
 420                                InvalidationStrategy::ExcerptEdited,
 421                                InvalidationStrategy::RefreshRequested,
 422                            ) if !update_task.task.is_running_rx.is_closed() => {
 423                                update_task.pending_refresh = Some(query);
 424                            }
 425                            _ => {
 426                                o.insert(UpdateTask {
 427                                    invalidate,
 428                                    cache_version: query.cache_version,
 429                                    task: new_update_task(false),
 430                                    pending_refresh: None,
 431                                });
 432                            }
 433                        }
 434                    }
 435                    hash_map::Entry::Vacant(v) => {
 436                        v.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        }
 446    }
 447}
 448
 449fn new_update_task(
 450    query: ExcerptQuery,
 451    multi_buffer_snapshot: MultiBufferSnapshot,
 452    buffer_snapshot: BufferSnapshot,
 453    visible_hints: Arc<Vec<Inlay>>,
 454    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
 455    is_refresh_after_regular_task: bool,
 456    cx: &mut ViewContext<'_, '_, Editor>,
 457) -> RunningTask {
 458    let hints_fetch_ranges = query.hints_fetch_ranges(&buffer_snapshot);
 459    let (is_running_tx, is_running_rx) = smol::channel::bounded(1);
 460    let _task = cx.spawn(|editor, mut cx| async move {
 461        let _is_running_tx = is_running_tx;
 462        let create_update_task = |range| {
 463            fetch_and_update_hints(
 464                editor.clone(),
 465                multi_buffer_snapshot.clone(),
 466                buffer_snapshot.clone(),
 467                Arc::clone(&visible_hints),
 468                cached_excerpt_hints.as_ref().map(Arc::clone),
 469                query,
 470                range,
 471                cx.clone(),
 472            )
 473        };
 474
 475        if is_refresh_after_regular_task {
 476            let visible_range_has_updates =
 477                match create_update_task(hints_fetch_ranges.visible_range).await {
 478                    Ok(updated) => updated,
 479                    Err(e) => {
 480                        error!("inlay hint visible range update task failed: {e:#}");
 481                        return;
 482                    }
 483                };
 484
 485            if visible_range_has_updates {
 486                let other_update_results = futures::future::join_all(
 487                    hints_fetch_ranges
 488                        .other_ranges
 489                        .into_iter()
 490                        .map(create_update_task),
 491                )
 492                .await;
 493
 494                for result in other_update_results {
 495                    if let Err(e) = result {
 496                        error!("inlay hint update task failed: {e:#}");
 497                    }
 498                }
 499            }
 500        } else {
 501            let task_update_results = futures::future::join_all(
 502                std::iter::once(hints_fetch_ranges.visible_range)
 503                    .chain(hints_fetch_ranges.other_ranges.into_iter())
 504                    .map(create_update_task),
 505            )
 506            .await;
 507
 508            for result in task_update_results {
 509                if let Err(e) = result {
 510                    error!("inlay hint update task failed: {e:#}");
 511                }
 512            }
 513        }
 514
 515        editor
 516            .update(&mut cx, |editor, cx| {
 517                let pending_refresh_query = editor
 518                    .inlay_hint_cache
 519                    .update_tasks
 520                    .get_mut(&query.excerpt_id)
 521                    .and_then(|task| task.pending_refresh.take());
 522
 523                if let Some(pending_refresh_query) = pending_refresh_query {
 524                    let refresh_multi_buffer = editor.buffer().read(cx);
 525                    let refresh_multi_buffer_snapshot = refresh_multi_buffer.snapshot(cx);
 526                    let refresh_visible_hints = Arc::new(editor.visible_inlay_hints(cx));
 527                    let refresh_cached_excerpt_hints = editor
 528                        .inlay_hint_cache
 529                        .hints
 530                        .get(&pending_refresh_query.excerpt_id)
 531                        .map(Arc::clone);
 532                    if let Some(buffer) =
 533                        refresh_multi_buffer.buffer(pending_refresh_query.buffer_id)
 534                    {
 535                        drop(refresh_multi_buffer);
 536                        editor.inlay_hint_cache.update_tasks.insert(
 537                            pending_refresh_query.excerpt_id,
 538                            UpdateTask {
 539                                invalidate: InvalidationStrategy::RefreshRequested,
 540                                cache_version: editor.inlay_hint_cache.version,
 541                                task: new_update_task(
 542                                    pending_refresh_query,
 543                                    refresh_multi_buffer_snapshot,
 544                                    buffer.read(cx).snapshot(),
 545                                    refresh_visible_hints,
 546                                    refresh_cached_excerpt_hints,
 547                                    true,
 548                                    cx,
 549                                ),
 550                                pending_refresh: None,
 551                            },
 552                        );
 553                    }
 554                }
 555            })
 556            .ok();
 557    });
 558
 559    RunningTask {
 560        _task,
 561        is_running_rx,
 562    }
 563}
 564
 565async fn fetch_and_update_hints(
 566    editor: gpui::WeakViewHandle<Editor>,
 567    multi_buffer_snapshot: MultiBufferSnapshot,
 568    buffer_snapshot: BufferSnapshot,
 569    visible_hints: Arc<Vec<Inlay>>,
 570    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
 571    query: ExcerptQuery,
 572    fetch_range: Range<language::Anchor>,
 573    mut cx: gpui::AsyncAppContext,
 574) -> anyhow::Result<bool> {
 575    let inlay_hints_fetch_task = editor
 576        .update(&mut cx, |editor, cx| {
 577            editor
 578                .buffer()
 579                .read(cx)
 580                .buffer(query.buffer_id)
 581                .and_then(|buffer| {
 582                    let project = editor.project.as_ref()?;
 583                    Some(project.update(cx, |project, cx| {
 584                        project.inlay_hints(buffer, fetch_range.clone(), cx)
 585                    }))
 586                })
 587        })
 588        .ok()
 589        .flatten();
 590    let mut update_happened = false;
 591    let Some(inlay_hints_fetch_task) = inlay_hints_fetch_task else { return Ok(update_happened) };
 592    let new_hints = inlay_hints_fetch_task
 593        .await
 594        .context("inlay hint fetch task")?;
 595    let background_task_buffer_snapshot = buffer_snapshot.clone();
 596    let backround_fetch_range = fetch_range.clone();
 597    let new_update = cx
 598        .background()
 599        .spawn(async move {
 600            calculate_hint_updates(
 601                query,
 602                backround_fetch_range,
 603                new_hints,
 604                &background_task_buffer_snapshot,
 605                cached_excerpt_hints,
 606                &visible_hints,
 607            )
 608        })
 609        .await;
 610
 611    editor
 612        .update(&mut cx, |editor, cx| {
 613            if let Some(new_update) = new_update {
 614                update_happened = !new_update.add_to_cache.is_empty()
 615                    || !new_update.remove_from_cache.is_empty()
 616                    || !new_update.remove_from_visible.is_empty();
 617
 618                let cached_excerpt_hints = editor
 619                    .inlay_hint_cache
 620                    .hints
 621                    .entry(new_update.excerpt_id)
 622                    .or_insert_with(|| {
 623                        Arc::new(RwLock::new(CachedExcerptHints {
 624                            version: query.cache_version,
 625                            buffer_version: buffer_snapshot.version().clone(),
 626                            buffer_id: query.buffer_id,
 627                            hints: Vec::new(),
 628                        }))
 629                    });
 630                let mut cached_excerpt_hints = cached_excerpt_hints.write();
 631                match query.cache_version.cmp(&cached_excerpt_hints.version) {
 632                    cmp::Ordering::Less => return,
 633                    cmp::Ordering::Greater | cmp::Ordering::Equal => {
 634                        cached_excerpt_hints.version = query.cache_version;
 635                    }
 636                }
 637                cached_excerpt_hints
 638                    .hints
 639                    .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
 640                cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
 641                editor.inlay_hint_cache.version += 1;
 642
 643                let mut splice = InlaySplice {
 644                    to_remove: new_update.remove_from_visible,
 645                    to_insert: Vec::new(),
 646                };
 647
 648                for new_hint in new_update.add_to_cache {
 649                    let new_hint_position = multi_buffer_snapshot
 650                        .anchor_in_excerpt(query.excerpt_id, new_hint.position);
 651                    let new_inlay_id = InlayId::Hint(post_inc(&mut editor.next_inlay_id));
 652                    if editor
 653                        .inlay_hint_cache
 654                        .allowed_hint_kinds
 655                        .contains(&new_hint.kind)
 656                    {
 657                        splice
 658                            .to_insert
 659                            .push((new_hint_position, new_inlay_id, new_hint.clone()));
 660                    }
 661
 662                    cached_excerpt_hints.hints.push((new_inlay_id, new_hint));
 663                }
 664
 665                cached_excerpt_hints
 666                    .hints
 667                    .sort_by(|(_, hint_a), (_, hint_b)| {
 668                        hint_a.position.cmp(&hint_b.position, &buffer_snapshot)
 669                    });
 670                drop(cached_excerpt_hints);
 671
 672                if query.invalidate.should_invalidate() {
 673                    let mut outdated_excerpt_caches = HashSet::default();
 674                    for (excerpt_id, excerpt_hints) in editor.inlay_hint_cache().hints.iter() {
 675                        let excerpt_hints = excerpt_hints.read();
 676                        if excerpt_hints.buffer_id == query.buffer_id
 677                            && excerpt_id != &query.excerpt_id
 678                            && buffer_snapshot
 679                                .version()
 680                                .changed_since(&excerpt_hints.buffer_version)
 681                        {
 682                            outdated_excerpt_caches.insert(*excerpt_id);
 683                            splice
 684                                .to_remove
 685                                .extend(excerpt_hints.hints.iter().map(|(id, _)| id));
 686                        }
 687                    }
 688                    editor
 689                        .inlay_hint_cache
 690                        .hints
 691                        .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
 692                }
 693
 694                let InlaySplice {
 695                    to_remove,
 696                    to_insert,
 697                } = splice;
 698                if !to_remove.is_empty() || !to_insert.is_empty() {
 699                    editor.splice_inlay_hints(to_remove, to_insert, cx)
 700                }
 701            }
 702        })
 703        .ok();
 704
 705    Ok(update_happened)
 706}
 707
 708fn calculate_hint_updates(
 709    query: ExcerptQuery,
 710    fetch_range: Range<language::Anchor>,
 711    new_excerpt_hints: Vec<InlayHint>,
 712    buffer_snapshot: &BufferSnapshot,
 713    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
 714    visible_hints: &[Inlay],
 715) -> Option<ExcerptHintsUpdate> {
 716    let mut add_to_cache: HashSet<InlayHint> = HashSet::default();
 717    let mut excerpt_hints_to_persist = HashMap::default();
 718    for new_hint in new_excerpt_hints {
 719        if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) {
 720            continue;
 721        }
 722        let missing_from_cache = match &cached_excerpt_hints {
 723            Some(cached_excerpt_hints) => {
 724                let cached_excerpt_hints = cached_excerpt_hints.read();
 725                match cached_excerpt_hints.hints.binary_search_by(|probe| {
 726                    probe.1.position.cmp(&new_hint.position, buffer_snapshot)
 727                }) {
 728                    Ok(ix) => {
 729                        let (cached_inlay_id, cached_hint) = &cached_excerpt_hints.hints[ix];
 730                        if cached_hint == &new_hint {
 731                            excerpt_hints_to_persist.insert(*cached_inlay_id, cached_hint.kind);
 732                            false
 733                        } else {
 734                            true
 735                        }
 736                    }
 737                    Err(_) => true,
 738                }
 739            }
 740            None => true,
 741        };
 742        if missing_from_cache {
 743            add_to_cache.insert(new_hint);
 744        }
 745    }
 746
 747    let mut remove_from_visible = Vec::new();
 748    let mut remove_from_cache = HashSet::default();
 749    if query.invalidate.should_invalidate() {
 750        remove_from_visible.extend(
 751            visible_hints
 752                .iter()
 753                .filter(|hint| hint.position.excerpt_id == query.excerpt_id)
 754                .filter(|hint| {
 755                    contains_position(&fetch_range, hint.position.text_anchor, buffer_snapshot)
 756                })
 757                .filter(|hint| {
 758                    fetch_range
 759                        .start
 760                        .cmp(&hint.position.text_anchor, buffer_snapshot)
 761                        .is_le()
 762                        && fetch_range
 763                            .end
 764                            .cmp(&hint.position.text_anchor, buffer_snapshot)
 765                            .is_ge()
 766                })
 767                .map(|inlay_hint| inlay_hint.id)
 768                .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
 769        );
 770
 771        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
 772            let cached_excerpt_hints = cached_excerpt_hints.read();
 773            remove_from_cache.extend(
 774                cached_excerpt_hints
 775                    .hints
 776                    .iter()
 777                    .filter(|(cached_inlay_id, _)| {
 778                        !excerpt_hints_to_persist.contains_key(cached_inlay_id)
 779                    })
 780                    .filter(|(_, cached_hint)| {
 781                        fetch_range
 782                            .start
 783                            .cmp(&cached_hint.position, buffer_snapshot)
 784                            .is_le()
 785                            && fetch_range
 786                                .end
 787                                .cmp(&cached_hint.position, buffer_snapshot)
 788                                .is_ge()
 789                    })
 790                    .map(|(cached_inlay_id, _)| *cached_inlay_id),
 791            );
 792        }
 793    }
 794
 795    if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
 796        None
 797    } else {
 798        Some(ExcerptHintsUpdate {
 799            excerpt_id: query.excerpt_id,
 800            remove_from_visible,
 801            remove_from_cache,
 802            add_to_cache,
 803        })
 804    }
 805}
 806
 807fn contains_position(
 808    range: &Range<language::Anchor>,
 809    position: language::Anchor,
 810    buffer_snapshot: &BufferSnapshot,
 811) -> bool {
 812    range.start.cmp(&position, buffer_snapshot).is_le()
 813        && range.end.cmp(&position, buffer_snapshot).is_ge()
 814}
 815
 816#[cfg(test)]
 817mod tests {
 818    use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
 819
 820    use crate::{
 821        scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
 822        serde_json::json,
 823        ExcerptRange, InlayHintSettings,
 824    };
 825    use futures::StreamExt;
 826    use gpui::{executor::Deterministic, TestAppContext, ViewHandle};
 827    use language::{
 828        language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
 829    };
 830    use lsp::FakeLanguageServer;
 831    use parking_lot::Mutex;
 832    use project::{FakeFs, Project};
 833    use settings::SettingsStore;
 834    use text::Point;
 835    use workspace::Workspace;
 836
 837    use crate::editor_tests::update_test_settings;
 838
 839    use super::*;
 840
 841    #[gpui::test]
 842    async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
 843        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
 844        init_test(cx, |settings| {
 845            settings.defaults.inlay_hints = Some(InlayHintSettings {
 846                enabled: true,
 847                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
 848                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
 849                show_other_hints: allowed_hint_kinds.contains(&None),
 850            })
 851        });
 852
 853        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
 854        let lsp_request_count = Arc::new(AtomicU32::new(0));
 855        fake_server
 856            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
 857                let task_lsp_request_count = Arc::clone(&lsp_request_count);
 858                async move {
 859                    assert_eq!(
 860                        params.text_document.uri,
 861                        lsp::Url::from_file_path(file_with_hints).unwrap(),
 862                    );
 863                    let current_call_id =
 864                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
 865                    let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
 866                    for _ in 0..2 {
 867                        let mut i = current_call_id;
 868                        loop {
 869                            new_hints.push(lsp::InlayHint {
 870                                position: lsp::Position::new(0, i),
 871                                label: lsp::InlayHintLabel::String(i.to_string()),
 872                                kind: None,
 873                                text_edits: None,
 874                                tooltip: None,
 875                                padding_left: None,
 876                                padding_right: None,
 877                                data: None,
 878                            });
 879                            if i == 0 {
 880                                break;
 881                            }
 882                            i -= 1;
 883                        }
 884                    }
 885
 886                    Ok(Some(new_hints))
 887                }
 888            })
 889            .next()
 890            .await;
 891        cx.foreground().run_until_parked();
 892
 893        let mut edits_made = 1;
 894        editor.update(cx, |editor, cx| {
 895            let expected_layers = vec!["0".to_string()];
 896            assert_eq!(
 897                expected_layers,
 898                cached_hint_labels(editor),
 899                "Should get its first hints when opening the editor"
 900            );
 901            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 902            let inlay_cache = editor.inlay_hint_cache();
 903            assert_eq!(
 904                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 905                "Cache should use editor settings to get the allowed hint kinds"
 906            );
 907            assert_eq!(
 908                inlay_cache.version, edits_made,
 909                "The editor update the cache version after every cache/view change"
 910            );
 911        });
 912
 913        editor.update(cx, |editor, cx| {
 914            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
 915            editor.handle_input("some change", cx);
 916            edits_made += 1;
 917        });
 918        cx.foreground().run_until_parked();
 919        editor.update(cx, |editor, cx| {
 920            let expected_layers = vec!["0".to_string(), "1".to_string()];
 921            assert_eq!(
 922                expected_layers,
 923                cached_hint_labels(editor),
 924                "Should get new hints after an edit"
 925            );
 926            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 927            let inlay_cache = editor.inlay_hint_cache();
 928            assert_eq!(
 929                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 930                "Cache should use editor settings to get the allowed hint kinds"
 931            );
 932            assert_eq!(
 933                inlay_cache.version, edits_made,
 934                "The editor update the cache version after every cache/view change"
 935            );
 936        });
 937
 938        fake_server
 939            .request::<lsp::request::InlayHintRefreshRequest>(())
 940            .await
 941            .expect("inlay refresh request failed");
 942        edits_made += 1;
 943        cx.foreground().run_until_parked();
 944        editor.update(cx, |editor, cx| {
 945            let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()];
 946            assert_eq!(
 947                expected_layers,
 948                cached_hint_labels(editor),
 949                "Should get new hints after hint refresh/ request"
 950            );
 951            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 952            let inlay_cache = editor.inlay_hint_cache();
 953            assert_eq!(
 954                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 955                "Cache should use editor settings to get the allowed hint kinds"
 956            );
 957            assert_eq!(
 958                inlay_cache.version, edits_made,
 959                "The editor update the cache version after every cache/view change"
 960            );
 961        });
 962    }
 963
 964    #[gpui::test]
 965    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
 966        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
 967        init_test(cx, |settings| {
 968            settings.defaults.inlay_hints = Some(InlayHintSettings {
 969                enabled: true,
 970                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
 971                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
 972                show_other_hints: allowed_hint_kinds.contains(&None),
 973            })
 974        });
 975
 976        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
 977        let lsp_request_count = Arc::new(AtomicU32::new(0));
 978        let another_lsp_request_count = Arc::clone(&lsp_request_count);
 979        fake_server
 980            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
 981                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
 982                async move {
 983                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
 984                    assert_eq!(
 985                        params.text_document.uri,
 986                        lsp::Url::from_file_path(file_with_hints).unwrap(),
 987                    );
 988                    Ok(Some(vec![
 989                        lsp::InlayHint {
 990                            position: lsp::Position::new(0, 1),
 991                            label: lsp::InlayHintLabel::String("type hint".to_string()),
 992                            kind: Some(lsp::InlayHintKind::TYPE),
 993                            text_edits: None,
 994                            tooltip: None,
 995                            padding_left: None,
 996                            padding_right: None,
 997                            data: None,
 998                        },
 999                        lsp::InlayHint {
1000                            position: lsp::Position::new(0, 2),
1001                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1002                            kind: Some(lsp::InlayHintKind::PARAMETER),
1003                            text_edits: None,
1004                            tooltip: None,
1005                            padding_left: None,
1006                            padding_right: None,
1007                            data: None,
1008                        },
1009                        lsp::InlayHint {
1010                            position: lsp::Position::new(0, 3),
1011                            label: lsp::InlayHintLabel::String("other hint".to_string()),
1012                            kind: None,
1013                            text_edits: None,
1014                            tooltip: None,
1015                            padding_left: None,
1016                            padding_right: None,
1017                            data: None,
1018                        },
1019                    ]))
1020                }
1021            })
1022            .next()
1023            .await;
1024        cx.foreground().run_until_parked();
1025
1026        let mut edits_made = 1;
1027        editor.update(cx, |editor, cx| {
1028            assert_eq!(
1029                lsp_request_count.load(Ordering::Relaxed),
1030                1,
1031                "Should query new hints once"
1032            );
1033            assert_eq!(
1034                vec![
1035                    "other hint".to_string(),
1036                    "parameter hint".to_string(),
1037                    "type hint".to_string(),
1038                ],
1039                cached_hint_labels(editor),
1040                "Should get its first hints when opening the editor"
1041            );
1042            assert_eq!(
1043                vec!["other hint".to_string(), "type hint".to_string()],
1044                visible_hint_labels(editor, cx)
1045            );
1046            let inlay_cache = editor.inlay_hint_cache();
1047            assert_eq!(
1048                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1049                "Cache should use editor settings to get the allowed hint kinds"
1050            );
1051            assert_eq!(
1052                inlay_cache.version, edits_made,
1053                "The editor update the cache version after every cache/view change"
1054            );
1055        });
1056
1057        fake_server
1058            .request::<lsp::request::InlayHintRefreshRequest>(())
1059            .await
1060            .expect("inlay refresh request failed");
1061        cx.foreground().run_until_parked();
1062        editor.update(cx, |editor, cx| {
1063            assert_eq!(
1064                lsp_request_count.load(Ordering::Relaxed),
1065                2,
1066                "Should load new hints twice"
1067            );
1068            assert_eq!(
1069                vec![
1070                    "other hint".to_string(),
1071                    "parameter hint".to_string(),
1072                    "type hint".to_string(),
1073                ],
1074                cached_hint_labels(editor),
1075                "Cached hints should not change due to allowed hint kinds settings update"
1076            );
1077            assert_eq!(
1078                vec!["other hint".to_string(), "type hint".to_string()],
1079                visible_hint_labels(editor, cx)
1080            );
1081            let inlay_cache = editor.inlay_hint_cache();
1082            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1083            assert_eq!(
1084                inlay_cache.version, edits_made,
1085                "Should not update cache version due to new loaded hints being the same"
1086            );
1087        });
1088
1089        for (new_allowed_hint_kinds, expected_visible_hints) in [
1090            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1091            (
1092                HashSet::from_iter([Some(InlayHintKind::Type)]),
1093                vec!["type hint".to_string()],
1094            ),
1095            (
1096                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1097                vec!["parameter hint".to_string()],
1098            ),
1099            (
1100                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1101                vec!["other hint".to_string(), "type hint".to_string()],
1102            ),
1103            (
1104                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1105                vec!["other hint".to_string(), "parameter hint".to_string()],
1106            ),
1107            (
1108                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1109                vec!["parameter hint".to_string(), "type hint".to_string()],
1110            ),
1111            (
1112                HashSet::from_iter([
1113                    None,
1114                    Some(InlayHintKind::Type),
1115                    Some(InlayHintKind::Parameter),
1116                ]),
1117                vec![
1118                    "other hint".to_string(),
1119                    "parameter hint".to_string(),
1120                    "type hint".to_string(),
1121                ],
1122            ),
1123        ] {
1124            edits_made += 1;
1125            update_test_settings(cx, |settings| {
1126                settings.defaults.inlay_hints = Some(InlayHintSettings {
1127                    enabled: true,
1128                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1129                    show_parameter_hints: new_allowed_hint_kinds
1130                        .contains(&Some(InlayHintKind::Parameter)),
1131                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1132                })
1133            });
1134            cx.foreground().run_until_parked();
1135            editor.update(cx, |editor, cx| {
1136                assert_eq!(
1137                    lsp_request_count.load(Ordering::Relaxed),
1138                    2,
1139                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1140                );
1141                assert_eq!(
1142                    vec![
1143                        "other hint".to_string(),
1144                        "parameter hint".to_string(),
1145                        "type hint".to_string(),
1146                    ],
1147                    cached_hint_labels(editor),
1148                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1149                );
1150                assert_eq!(
1151                    expected_visible_hints,
1152                    visible_hint_labels(editor, cx),
1153                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1154                );
1155                let inlay_cache = editor.inlay_hint_cache();
1156                assert_eq!(
1157                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1158                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1159                );
1160                assert_eq!(
1161                    inlay_cache.version, edits_made,
1162                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1163                );
1164            });
1165        }
1166
1167        edits_made += 1;
1168        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1169        update_test_settings(cx, |settings| {
1170            settings.defaults.inlay_hints = Some(InlayHintSettings {
1171                enabled: false,
1172                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1173                show_parameter_hints: another_allowed_hint_kinds
1174                    .contains(&Some(InlayHintKind::Parameter)),
1175                show_other_hints: another_allowed_hint_kinds.contains(&None),
1176            })
1177        });
1178        cx.foreground().run_until_parked();
1179        editor.update(cx, |editor, cx| {
1180            assert_eq!(
1181                lsp_request_count.load(Ordering::Relaxed),
1182                2,
1183                "Should not load new hints when hints got disabled"
1184            );
1185            assert!(
1186                cached_hint_labels(editor).is_empty(),
1187                "Should clear the cache when hints got disabled"
1188            );
1189            assert!(
1190                visible_hint_labels(editor, cx).is_empty(),
1191                "Should clear visible hints when hints got disabled"
1192            );
1193            let inlay_cache = editor.inlay_hint_cache();
1194            assert_eq!(
1195                inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1196                "Should update its allowed hint kinds even when hints got disabled"
1197            );
1198            assert_eq!(
1199                inlay_cache.version, edits_made,
1200                "The editor should update the cache version after hints got disabled"
1201            );
1202        });
1203
1204        fake_server
1205            .request::<lsp::request::InlayHintRefreshRequest>(())
1206            .await
1207            .expect("inlay refresh request failed");
1208        cx.foreground().run_until_parked();
1209        editor.update(cx, |editor, cx| {
1210            assert_eq!(
1211                lsp_request_count.load(Ordering::Relaxed),
1212                2,
1213                "Should not load new hints when they got disabled"
1214            );
1215            assert!(cached_hint_labels(editor).is_empty());
1216            assert!(visible_hint_labels(editor, cx).is_empty());
1217            let inlay_cache = editor.inlay_hint_cache();
1218            assert_eq!(inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds);
1219            assert_eq!(
1220                inlay_cache.version, edits_made,
1221                "The editor should not update the cache version after /refresh query without updates"
1222            );
1223        });
1224
1225        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1226        edits_made += 1;
1227        update_test_settings(cx, |settings| {
1228            settings.defaults.inlay_hints = Some(InlayHintSettings {
1229                enabled: true,
1230                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1231                show_parameter_hints: final_allowed_hint_kinds
1232                    .contains(&Some(InlayHintKind::Parameter)),
1233                show_other_hints: final_allowed_hint_kinds.contains(&None),
1234            })
1235        });
1236        cx.foreground().run_until_parked();
1237        editor.update(cx, |editor, cx| {
1238            assert_eq!(
1239                lsp_request_count.load(Ordering::Relaxed),
1240                3,
1241                "Should query for new hints when they got reenabled"
1242            );
1243            assert_eq!(
1244                vec![
1245                    "other hint".to_string(),
1246                    "parameter hint".to_string(),
1247                    "type hint".to_string(),
1248                ],
1249                cached_hint_labels(editor),
1250                "Should get its cached hints fully repopulated after the hints got reenabled"
1251            );
1252            assert_eq!(
1253                vec!["parameter hint".to_string()],
1254                visible_hint_labels(editor, cx),
1255                "Should get its visible hints repopulated and filtered after the h"
1256            );
1257            let inlay_cache = editor.inlay_hint_cache();
1258            assert_eq!(
1259                inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1260                "Cache should update editor settings when hints got reenabled"
1261            );
1262            assert_eq!(
1263                inlay_cache.version, edits_made,
1264                "Cache should update its version after hints got reenabled"
1265            );
1266        });
1267
1268        fake_server
1269            .request::<lsp::request::InlayHintRefreshRequest>(())
1270            .await
1271            .expect("inlay refresh request failed");
1272        cx.foreground().run_until_parked();
1273        editor.update(cx, |editor, cx| {
1274            assert_eq!(
1275                lsp_request_count.load(Ordering::Relaxed),
1276                4,
1277                "Should query for new hints again"
1278            );
1279            assert_eq!(
1280                vec![
1281                    "other hint".to_string(),
1282                    "parameter hint".to_string(),
1283                    "type hint".to_string(),
1284                ],
1285                cached_hint_labels(editor),
1286            );
1287            assert_eq!(
1288                vec!["parameter hint".to_string()],
1289                visible_hint_labels(editor, cx),
1290            );
1291            let inlay_cache = editor.inlay_hint_cache();
1292            assert_eq!(inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds);
1293            assert_eq!(inlay_cache.version, edits_made);
1294        });
1295    }
1296
1297    #[gpui::test]
1298    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
1299        let allowed_hint_kinds = HashSet::from_iter([None]);
1300        init_test(cx, |settings| {
1301            settings.defaults.inlay_hints = Some(InlayHintSettings {
1302                enabled: true,
1303                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1304                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1305                show_other_hints: allowed_hint_kinds.contains(&None),
1306            })
1307        });
1308
1309        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1310        let fake_server = Arc::new(fake_server);
1311        let lsp_request_count = Arc::new(AtomicU32::new(0));
1312        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1313        fake_server
1314            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1315                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1316                async move {
1317                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1318                    assert_eq!(
1319                        params.text_document.uri,
1320                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1321                    );
1322                    Ok(Some(vec![lsp::InlayHint {
1323                        position: lsp::Position::new(0, i),
1324                        label: lsp::InlayHintLabel::String(i.to_string()),
1325                        kind: None,
1326                        text_edits: None,
1327                        tooltip: None,
1328                        padding_left: None,
1329                        padding_right: None,
1330                        data: None,
1331                    }]))
1332                }
1333            })
1334            .next()
1335            .await;
1336
1337        let mut expected_changes = Vec::new();
1338        for change_after_opening in [
1339            "initial change #1",
1340            "initial change #2",
1341            "initial change #3",
1342        ] {
1343            editor.update(cx, |editor, cx| {
1344                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1345                editor.handle_input(change_after_opening, cx);
1346            });
1347            expected_changes.push(change_after_opening);
1348        }
1349
1350        cx.foreground().run_until_parked();
1351
1352        editor.update(cx, |editor, cx| {
1353            let current_text = editor.text(cx);
1354            for change in &expected_changes {
1355                assert!(
1356                    current_text.contains(change),
1357                    "Should apply all changes made"
1358                );
1359            }
1360            assert_eq!(
1361                lsp_request_count.load(Ordering::Relaxed),
1362                2,
1363                "Should query new hints twice: for editor init and for the last edit that interrupted all others"
1364            );
1365            let expected_hints = vec!["2".to_string()];
1366            assert_eq!(
1367                expected_hints,
1368                cached_hint_labels(editor),
1369                "Should get hints from the last edit landed only"
1370            );
1371            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1372            let inlay_cache = editor.inlay_hint_cache();
1373            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1374            assert_eq!(
1375                inlay_cache.version, 1,
1376                "Only one update should be registered in the cache after all cancellations"
1377            );
1378        });
1379
1380        let mut edits = Vec::new();
1381        for async_later_change in [
1382            "another change #1",
1383            "another change #2",
1384            "another change #3",
1385        ] {
1386            expected_changes.push(async_later_change);
1387            let task_editor = editor.clone();
1388            let mut task_cx = cx.clone();
1389            edits.push(cx.foreground().spawn(async move {
1390                task_editor.update(&mut task_cx, |editor, cx| {
1391                    editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1392                    editor.handle_input(async_later_change, cx);
1393                });
1394            }));
1395        }
1396        let _ = futures::future::join_all(edits).await;
1397        cx.foreground().run_until_parked();
1398
1399        editor.update(cx, |editor, cx| {
1400            let current_text = editor.text(cx);
1401            for change in &expected_changes {
1402                assert!(
1403                    current_text.contains(change),
1404                    "Should apply all changes made"
1405                );
1406            }
1407            assert_eq!(
1408                lsp_request_count.load(Ordering::SeqCst),
1409                3,
1410                "Should query new hints one more time, for the last edit only"
1411            );
1412            let expected_hints = vec!["3".to_string()];
1413            assert_eq!(
1414                expected_hints,
1415                cached_hint_labels(editor),
1416                "Should get hints from the last edit landed only"
1417            );
1418            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1419            let inlay_cache = editor.inlay_hint_cache();
1420            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1421            assert_eq!(
1422                inlay_cache.version, 2,
1423                "Should update the cache version once more, for the new change"
1424            );
1425        });
1426    }
1427
1428    #[gpui::test]
1429    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
1430        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1431        init_test(cx, |settings| {
1432            settings.defaults.inlay_hints = Some(InlayHintSettings {
1433                enabled: true,
1434                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1435                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1436                show_other_hints: allowed_hint_kinds.contains(&None),
1437            })
1438        });
1439
1440        let mut language = Language::new(
1441            LanguageConfig {
1442                name: "Rust".into(),
1443                path_suffixes: vec!["rs".to_string()],
1444                ..Default::default()
1445            },
1446            Some(tree_sitter_rust::language()),
1447        );
1448        let mut fake_servers = language
1449            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1450                capabilities: lsp::ServerCapabilities {
1451                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1452                    ..Default::default()
1453                },
1454                ..Default::default()
1455            }))
1456            .await;
1457        let fs = FakeFs::new(cx.background());
1458        fs.insert_tree(
1459            "/a",
1460            json!({
1461                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
1462                "other.rs": "// Test file",
1463            }),
1464        )
1465        .await;
1466        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1467        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
1468        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1469        let worktree_id = workspace.update(cx, |workspace, cx| {
1470            workspace.project().read_with(cx, |project, cx| {
1471                project.worktrees(cx).next().unwrap().read(cx).id()
1472            })
1473        });
1474
1475        let _buffer = project
1476            .update(cx, |project, cx| {
1477                project.open_local_buffer("/a/main.rs", cx)
1478            })
1479            .await
1480            .unwrap();
1481        cx.foreground().run_until_parked();
1482        cx.foreground().start_waiting();
1483        let fake_server = fake_servers.next().await.unwrap();
1484        let editor = workspace
1485            .update(cx, |workspace, cx| {
1486                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1487            })
1488            .await
1489            .unwrap()
1490            .downcast::<Editor>()
1491            .unwrap();
1492        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
1493        let lsp_request_count = Arc::new(AtomicU32::new(0));
1494        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
1495        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
1496        fake_server
1497            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1498                let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
1499                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
1500                async move {
1501                    assert_eq!(
1502                        params.text_document.uri,
1503                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1504                    );
1505
1506                    task_lsp_request_ranges.lock().push(params.range);
1507                    let query_start = params.range.start;
1508                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1509                    Ok(Some(vec![lsp::InlayHint {
1510                        position: query_start,
1511                        label: lsp::InlayHintLabel::String(i.to_string()),
1512                        kind: None,
1513                        text_edits: None,
1514                        tooltip: None,
1515                        padding_left: None,
1516                        padding_right: None,
1517                        data: None,
1518                    }]))
1519                }
1520            })
1521            .next()
1522            .await;
1523        cx.foreground().run_until_parked();
1524        editor.update(cx, |editor, cx| {
1525            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1526            ranges.sort_by_key(|range| range.start);
1527            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");
1528            assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
1529            assert_eq!(ranges[0].end.line, ranges[1].start.line, "Both requests should be on the same line");
1530            assert_eq!(ranges[0].end.character + 1, ranges[1].start.character, "Both request should be concequent");
1531
1532            assert_eq!(lsp_request_count.load(Ordering::SeqCst), 2,
1533                "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints");
1534            let expected_layers = vec!["1".to_string(), "2".to_string()];
1535            assert_eq!(
1536                expected_layers,
1537                cached_hint_labels(editor),
1538                "Should have hints from both LSP requests made for a big file"
1539            );
1540            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1541            let inlay_cache = editor.inlay_hint_cache();
1542            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1543            assert_eq!(
1544                inlay_cache.version, 2,
1545                "Both LSP queries should've bumped the cache version"
1546            );
1547        });
1548
1549        editor.update(cx, |editor, cx| {
1550            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1551            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1552            editor.change_selections(None, cx, |s| s.select_ranges([600..600]));
1553            editor.handle_input("++++more text++++", cx);
1554        });
1555
1556        cx.foreground().run_until_parked();
1557        editor.update(cx, |editor, cx| {
1558            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1559            ranges.sort_by_key(|range| range.start);
1560            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");
1561            assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
1562            assert_eq!(ranges[0].end.line + 1, ranges[1].start.line, "Neighbour requests got on different lines due to the line end");
1563            assert_ne!(ranges[0].end.character, 0, "First query was in the end of the line, not in the beginning");
1564            assert_eq!(ranges[1].start.character, 0, "Second query got pushed into a new line and starts from the beginning");
1565            assert_eq!(ranges[1].end.line, ranges[2].start.line, "Neighbour requests should be on the same line");
1566            assert_eq!(ranges[1].end.character + 1, ranges[2].start.character, "Neighbour request should be concequent");
1567
1568            assert_eq!(lsp_request_count.load(Ordering::SeqCst), 5,
1569                "When scroll not at the edge of a big document, visible part + 2 other parts should be queried for hints");
1570            let expected_layers = vec!["3".to_string(), "4".to_string(), "5".to_string()];
1571            assert_eq!(expected_layers, cached_hint_labels(editor),
1572                "Should have hints from the new LSP response after edit");
1573            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1574            let inlay_cache = editor.inlay_hint_cache();
1575            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1576            assert_eq!(inlay_cache.version, 5, "Should update the cache for every LSP response with hints added");
1577        });
1578    }
1579
1580    #[gpui::test]
1581    async fn test_multiple_excerpts_large_multibuffer(
1582        deterministic: Arc<Deterministic>,
1583        cx: &mut gpui::TestAppContext,
1584    ) {
1585        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1586        init_test(cx, |settings| {
1587            settings.defaults.inlay_hints = Some(InlayHintSettings {
1588                enabled: true,
1589                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1590                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1591                show_other_hints: allowed_hint_kinds.contains(&None),
1592            })
1593        });
1594
1595        let mut language = Language::new(
1596            LanguageConfig {
1597                name: "Rust".into(),
1598                path_suffixes: vec!["rs".to_string()],
1599                ..Default::default()
1600            },
1601            Some(tree_sitter_rust::language()),
1602        );
1603        let mut fake_servers = language
1604            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1605                capabilities: lsp::ServerCapabilities {
1606                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1607                    ..Default::default()
1608                },
1609                ..Default::default()
1610            }))
1611            .await;
1612        let language = Arc::new(language);
1613        let fs = FakeFs::new(cx.background());
1614        fs.insert_tree(
1615            "/a",
1616            json!({
1617                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
1618                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
1619            }),
1620        )
1621        .await;
1622        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1623        project.update(cx, |project, _| {
1624            project.languages().add(Arc::clone(&language))
1625        });
1626        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1627        let worktree_id = workspace.update(cx, |workspace, cx| {
1628            workspace.project().read_with(cx, |project, cx| {
1629                project.worktrees(cx).next().unwrap().read(cx).id()
1630            })
1631        });
1632
1633        let buffer_1 = project
1634            .update(cx, |project, cx| {
1635                project.open_buffer((worktree_id, "main.rs"), cx)
1636            })
1637            .await
1638            .unwrap();
1639        let buffer_2 = project
1640            .update(cx, |project, cx| {
1641                project.open_buffer((worktree_id, "other.rs"), cx)
1642            })
1643            .await
1644            .unwrap();
1645        let multibuffer = cx.add_model(|cx| {
1646            let mut multibuffer = MultiBuffer::new(0);
1647            multibuffer.push_excerpts(
1648                buffer_1.clone(),
1649                [
1650                    ExcerptRange {
1651                        context: Point::new(0, 0)..Point::new(2, 0),
1652                        primary: None,
1653                    },
1654                    ExcerptRange {
1655                        context: Point::new(4, 0)..Point::new(11, 0),
1656                        primary: None,
1657                    },
1658                    ExcerptRange {
1659                        context: Point::new(22, 0)..Point::new(33, 0),
1660                        primary: None,
1661                    },
1662                    ExcerptRange {
1663                        context: Point::new(44, 0)..Point::new(55, 0),
1664                        primary: None,
1665                    },
1666                    ExcerptRange {
1667                        context: Point::new(56, 0)..Point::new(66, 0),
1668                        primary: None,
1669                    },
1670                    ExcerptRange {
1671                        context: Point::new(67, 0)..Point::new(77, 0),
1672                        primary: None,
1673                    },
1674                ],
1675                cx,
1676            );
1677            multibuffer.push_excerpts(
1678                buffer_2.clone(),
1679                [
1680                    ExcerptRange {
1681                        context: Point::new(0, 1)..Point::new(2, 1),
1682                        primary: None,
1683                    },
1684                    ExcerptRange {
1685                        context: Point::new(4, 1)..Point::new(11, 1),
1686                        primary: None,
1687                    },
1688                    ExcerptRange {
1689                        context: Point::new(22, 1)..Point::new(33, 1),
1690                        primary: None,
1691                    },
1692                    ExcerptRange {
1693                        context: Point::new(44, 1)..Point::new(55, 1),
1694                        primary: None,
1695                    },
1696                    ExcerptRange {
1697                        context: Point::new(56, 1)..Point::new(66, 1),
1698                        primary: None,
1699                    },
1700                    ExcerptRange {
1701                        context: Point::new(67, 1)..Point::new(77, 1),
1702                        primary: None,
1703                    },
1704                ],
1705                cx,
1706            );
1707            multibuffer
1708        });
1709
1710        deterministic.run_until_parked();
1711        cx.foreground().run_until_parked();
1712        let (_, editor) =
1713            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
1714        let editor_edited = Arc::new(AtomicBool::new(false));
1715        let fake_server = fake_servers.next().await.unwrap();
1716        let closure_editor_edited = Arc::clone(&editor_edited);
1717        fake_server
1718            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1719                let task_editor_edited = Arc::clone(&closure_editor_edited);
1720                async move {
1721                    let hint_text = if params.text_document.uri
1722                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
1723                    {
1724                        "main hint"
1725                    } else if params.text_document.uri
1726                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
1727                    {
1728                        "other hint"
1729                    } else {
1730                        panic!("unexpected uri: {:?}", params.text_document.uri);
1731                    };
1732
1733                    let positions = [
1734                        lsp::Position::new(0, 2),
1735                        lsp::Position::new(4, 2),
1736                        lsp::Position::new(22, 2),
1737                        lsp::Position::new(44, 2),
1738                        lsp::Position::new(56, 2),
1739                        lsp::Position::new(67, 2),
1740                    ];
1741                    let out_of_range_hint = lsp::InlayHint {
1742                        position: lsp::Position::new(
1743                            params.range.start.line + 99,
1744                            params.range.start.character + 99,
1745                        ),
1746                        label: lsp::InlayHintLabel::String(
1747                            "out of excerpt range, should be ignored".to_string(),
1748                        ),
1749                        kind: None,
1750                        text_edits: None,
1751                        tooltip: None,
1752                        padding_left: None,
1753                        padding_right: None,
1754                        data: None,
1755                    };
1756
1757                    let edited = task_editor_edited.load(Ordering::Acquire);
1758                    Ok(Some(
1759                        std::iter::once(out_of_range_hint)
1760                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
1761                                lsp::InlayHint {
1762                                    position,
1763                                    label: lsp::InlayHintLabel::String(format!(
1764                                        "{hint_text}{} #{i}",
1765                                        if edited { "(edited)" } else { "" },
1766                                    )),
1767                                    kind: None,
1768                                    text_edits: None,
1769                                    tooltip: None,
1770                                    padding_left: None,
1771                                    padding_right: None,
1772                                    data: None,
1773                                }
1774                            }))
1775                            .collect(),
1776                    ))
1777                }
1778            })
1779            .next()
1780            .await;
1781        cx.foreground().run_until_parked();
1782
1783        editor.update(cx, |editor, cx| {
1784            let expected_layers = vec![
1785                "main hint #0".to_string(),
1786                "main hint #1".to_string(),
1787                "main hint #2".to_string(),
1788                "main hint #3".to_string(),
1789            ];
1790            assert_eq!(
1791                expected_layers,
1792                cached_hint_labels(editor),
1793                "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
1794            );
1795            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1796            let inlay_cache = editor.inlay_hint_cache();
1797            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1798            assert_eq!(inlay_cache.version, 4, "Every visible excerpt hints should bump the verison");
1799        });
1800
1801        editor.update(cx, |editor, cx| {
1802            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1803                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
1804            });
1805            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1806                s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
1807            });
1808            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1809                s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
1810            });
1811        });
1812        cx.foreground().run_until_parked();
1813        editor.update(cx, |editor, cx| {
1814            let expected_layers = vec![
1815                "main hint #0".to_string(),
1816                "main hint #1".to_string(),
1817                "main hint #2".to_string(),
1818                "main hint #3".to_string(),
1819                "main hint #4".to_string(),
1820                "main hint #5".to_string(),
1821                "other hint #0".to_string(),
1822                "other hint #1".to_string(),
1823                "other hint #2".to_string(),
1824            ];
1825            assert_eq!(expected_layers, cached_hint_labels(editor),
1826                "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
1827            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1828            let inlay_cache = editor.inlay_hint_cache();
1829            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1830            assert_eq!(inlay_cache.version, 9);
1831        });
1832
1833        editor.update(cx, |editor, cx| {
1834            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1835                s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
1836            });
1837        });
1838        cx.foreground().run_until_parked();
1839        editor.update(cx, |editor, cx| {
1840            let expected_layers = vec![
1841                "main hint #0".to_string(),
1842                "main hint #1".to_string(),
1843                "main hint #2".to_string(),
1844                "main hint #3".to_string(),
1845                "main hint #4".to_string(),
1846                "main hint #5".to_string(),
1847                "other hint #0".to_string(),
1848                "other hint #1".to_string(),
1849                "other hint #2".to_string(),
1850                "other hint #3".to_string(),
1851                "other hint #4".to_string(),
1852                "other hint #5".to_string(),
1853            ];
1854            assert_eq!(expected_layers, cached_hint_labels(editor),
1855                "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
1856            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1857            let inlay_cache = editor.inlay_hint_cache();
1858            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1859            assert_eq!(inlay_cache.version, 12);
1860        });
1861
1862        editor.update(cx, |editor, cx| {
1863            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1864                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
1865            });
1866        });
1867        cx.foreground().run_until_parked();
1868        editor.update(cx, |editor, cx| {
1869            let expected_layers = vec![
1870                "main hint #0".to_string(),
1871                "main hint #1".to_string(),
1872                "main hint #2".to_string(),
1873                "main hint #3".to_string(),
1874                "main hint #4".to_string(),
1875                "main hint #5".to_string(),
1876                "other hint #0".to_string(),
1877                "other hint #1".to_string(),
1878                "other hint #2".to_string(),
1879                "other hint #3".to_string(),
1880                "other hint #4".to_string(),
1881                "other hint #5".to_string(),
1882            ];
1883            assert_eq!(expected_layers, cached_hint_labels(editor),
1884                "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
1885            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1886            let inlay_cache = editor.inlay_hint_cache();
1887            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1888            assert_eq!(inlay_cache.version, 12, "No updates should happen during scrolling already scolled buffer");
1889        });
1890
1891        editor_edited.store(true, Ordering::Release);
1892        editor.update(cx, |editor, cx| {
1893            editor.handle_input("++++more text++++", cx);
1894        });
1895        cx.foreground().run_until_parked();
1896        editor.update(cx, |editor, cx| {
1897            let expected_layers = vec![
1898                "main hint(edited) #0".to_string(),
1899                "main hint(edited) #1".to_string(),
1900                "main hint(edited) #2".to_string(),
1901                "main hint(edited) #3".to_string(),
1902                "other hint #0".to_string(),
1903                "other hint #1".to_string(),
1904                "other hint #2".to_string(),
1905                "other hint #3".to_string(),
1906                "other hint #4".to_string(),
1907                "other hint #5".to_string(),
1908            ];
1909            assert_eq!(expected_layers, cached_hint_labels(editor),
1910                "After multibuffer was edited, hints for the edited buffer (1st) should be invalidated and requeried for all of its visible excerpts, \
1911unedited (2nd) buffer should have the same hint");
1912            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1913            let inlay_cache = editor.inlay_hint_cache();
1914            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1915            assert_eq!(inlay_cache.version, 16);
1916        });
1917    }
1918
1919    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
1920        cx.foreground().forbid_parking();
1921
1922        cx.update(|cx| {
1923            cx.set_global(SettingsStore::test(cx));
1924            theme::init((), cx);
1925            client::init_settings(cx);
1926            language::init(cx);
1927            Project::init_settings(cx);
1928            workspace::init_settings(cx);
1929            crate::init(cx);
1930        });
1931
1932        update_test_settings(cx, f);
1933    }
1934
1935    async fn prepare_test_objects(
1936        cx: &mut TestAppContext,
1937    ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
1938        let mut language = Language::new(
1939            LanguageConfig {
1940                name: "Rust".into(),
1941                path_suffixes: vec!["rs".to_string()],
1942                ..Default::default()
1943            },
1944            Some(tree_sitter_rust::language()),
1945        );
1946        let mut fake_servers = language
1947            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1948                capabilities: lsp::ServerCapabilities {
1949                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1950                    ..Default::default()
1951                },
1952                ..Default::default()
1953            }))
1954            .await;
1955
1956        let fs = FakeFs::new(cx.background());
1957        fs.insert_tree(
1958            "/a",
1959            json!({
1960                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1961                "other.rs": "// Test file",
1962            }),
1963        )
1964        .await;
1965
1966        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1967        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
1968        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1969        let worktree_id = workspace.update(cx, |workspace, cx| {
1970            workspace.project().read_with(cx, |project, cx| {
1971                project.worktrees(cx).next().unwrap().read(cx).id()
1972            })
1973        });
1974
1975        let _buffer = project
1976            .update(cx, |project, cx| {
1977                project.open_local_buffer("/a/main.rs", cx)
1978            })
1979            .await
1980            .unwrap();
1981        cx.foreground().run_until_parked();
1982        cx.foreground().start_waiting();
1983        let fake_server = fake_servers.next().await.unwrap();
1984        let editor = workspace
1985            .update(cx, |workspace, cx| {
1986                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1987            })
1988            .await
1989            .unwrap()
1990            .downcast::<Editor>()
1991            .unwrap();
1992
1993        ("/a/main.rs", editor, fake_server)
1994    }
1995
1996    fn cached_hint_labels(editor: &Editor) -> Vec<String> {
1997        let mut labels = Vec::new();
1998        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
1999            let excerpt_hints = excerpt_hints.read();
2000            for (_, inlay) in excerpt_hints.hints.iter() {
2001                match &inlay.label {
2002                    project::InlayHintLabel::String(s) => labels.push(s.to_string()),
2003                    _ => unreachable!(),
2004                }
2005            }
2006        }
2007
2008        labels.sort();
2009        labels
2010    }
2011
2012    fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
2013        let mut hints = editor
2014            .visible_inlay_hints(cx)
2015            .into_iter()
2016            .map(|hint| hint.text.to_string())
2017            .collect::<Vec<_>>();
2018        hints.sort();
2019        hints
2020    }
2021}