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