inlay_hint_cache.rs

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