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,
 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_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
 979        init_test(cx, |settings| {
 980            settings.defaults.inlay_hints = Some(InlayHintSettings {
 981                enabled: true,
 982                show_type_hints: true,
 983                show_parameter_hints: true,
 984                show_other_hints: true,
 985            })
 986        });
 987
 988        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
 989        let lsp_request_count = Arc::new(AtomicU32::new(0));
 990        fake_server
 991            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
 992                let task_lsp_request_count = Arc::clone(&lsp_request_count);
 993                async move {
 994                    assert_eq!(
 995                        params.text_document.uri,
 996                        lsp::Url::from_file_path(file_with_hints).unwrap(),
 997                    );
 998                    let current_call_id =
 999                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1000                    Ok(Some(vec![lsp::InlayHint {
1001                        position: lsp::Position::new(0, current_call_id),
1002                        label: lsp::InlayHintLabel::String(current_call_id.to_string()),
1003                        kind: None,
1004                        text_edits: None,
1005                        tooltip: None,
1006                        padding_left: None,
1007                        padding_right: None,
1008                        data: None,
1009                    }]))
1010                }
1011            })
1012            .next()
1013            .await;
1014        cx.foreground().run_until_parked();
1015
1016        let mut edits_made = 1;
1017        editor.update(cx, |editor, cx| {
1018            let expected_layers = vec!["0".to_string()];
1019            assert_eq!(
1020                expected_layers,
1021                cached_hint_labels(editor),
1022                "Should get its first hints when opening the editor"
1023            );
1024            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1025            let inlay_cache = editor.inlay_hint_cache();
1026            assert_eq!(
1027                inlay_cache.version, edits_made,
1028                "The editor update the cache version after every cache/view change"
1029            );
1030        });
1031
1032        let progress_token = "test_progress_token";
1033        fake_server
1034            .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1035                token: lsp::ProgressToken::String(progress_token.to_string()),
1036            })
1037            .await
1038            .expect("work done progress create request failed");
1039        cx.foreground().run_until_parked();
1040        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1041            token: lsp::ProgressToken::String(progress_token.to_string()),
1042            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
1043                lsp::WorkDoneProgressBegin::default(),
1044            )),
1045        });
1046        cx.foreground().run_until_parked();
1047
1048        editor.update(cx, |editor, cx| {
1049            let expected_layers = vec!["0".to_string()];
1050            assert_eq!(
1051                expected_layers,
1052                cached_hint_labels(editor),
1053                "Should not update hints while the work task is running"
1054            );
1055            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1056            let inlay_cache = editor.inlay_hint_cache();
1057            assert_eq!(
1058                inlay_cache.version, edits_made,
1059                "Should not update the cache while the work task is running"
1060            );
1061        });
1062
1063        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1064            token: lsp::ProgressToken::String(progress_token.to_string()),
1065            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
1066                lsp::WorkDoneProgressEnd::default(),
1067            )),
1068        });
1069        cx.foreground().run_until_parked();
1070
1071        edits_made += 1;
1072        editor.update(cx, |editor, cx| {
1073            let expected_layers = vec!["1".to_string()];
1074            assert_eq!(
1075                expected_layers,
1076                cached_hint_labels(editor),
1077                "New hints should be queried after the work task is done"
1078            );
1079            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1080            let inlay_cache = editor.inlay_hint_cache();
1081            assert_eq!(
1082                inlay_cache.version, edits_made,
1083                "Cache version should udpate once after the work task is done"
1084            );
1085        });
1086    }
1087
1088    #[gpui::test]
1089    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1090        init_test(cx, |settings| {
1091            settings.defaults.inlay_hints = Some(InlayHintSettings {
1092                enabled: true,
1093                show_type_hints: true,
1094                show_parameter_hints: true,
1095                show_other_hints: true,
1096            })
1097        });
1098
1099        let fs = FakeFs::new(cx.background());
1100        fs.insert_tree(
1101                    "/a",
1102                    json!({
1103                        "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1104                        "other.md": "Test md file with some text",
1105                    }),
1106                )
1107                .await;
1108        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1109        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1110        let worktree_id = workspace.update(cx, |workspace, cx| {
1111            workspace.project().read_with(cx, |project, cx| {
1112                project.worktrees(cx).next().unwrap().read(cx).id()
1113            })
1114        });
1115
1116        let mut rs_fake_servers = None;
1117        let mut md_fake_servers = None;
1118        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1119            let mut language = Language::new(
1120                LanguageConfig {
1121                    name: name.into(),
1122                    path_suffixes: vec![path_suffix.to_string()],
1123                    ..Default::default()
1124                },
1125                Some(tree_sitter_rust::language()),
1126            );
1127            let fake_servers = language
1128                .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1129                    name,
1130                    capabilities: lsp::ServerCapabilities {
1131                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1132                        ..Default::default()
1133                    },
1134                    ..Default::default()
1135                }))
1136                .await;
1137            match name {
1138                "Rust" => rs_fake_servers = Some(fake_servers),
1139                "Markdown" => md_fake_servers = Some(fake_servers),
1140                _ => unreachable!(),
1141            }
1142            project.update(cx, |project, _| {
1143                project.languages().add(Arc::new(language));
1144            });
1145        }
1146
1147        let _rs_buffer = project
1148            .update(cx, |project, cx| {
1149                project.open_local_buffer("/a/main.rs", cx)
1150            })
1151            .await
1152            .unwrap();
1153        cx.foreground().run_until_parked();
1154        cx.foreground().start_waiting();
1155        let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1156        let rs_editor = workspace
1157            .update(cx, |workspace, cx| {
1158                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1159            })
1160            .await
1161            .unwrap()
1162            .downcast::<Editor>()
1163            .unwrap();
1164        let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1165        rs_fake_server
1166            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1167                let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
1168                async move {
1169                    assert_eq!(
1170                        params.text_document.uri,
1171                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1172                    );
1173                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1174                    Ok(Some(vec![lsp::InlayHint {
1175                        position: lsp::Position::new(0, i),
1176                        label: lsp::InlayHintLabel::String(i.to_string()),
1177                        kind: None,
1178                        text_edits: None,
1179                        tooltip: None,
1180                        padding_left: None,
1181                        padding_right: None,
1182                        data: None,
1183                    }]))
1184                }
1185            })
1186            .next()
1187            .await;
1188        cx.foreground().run_until_parked();
1189        rs_editor.update(cx, |editor, cx| {
1190            let expected_layers = vec!["0".to_string()];
1191            assert_eq!(
1192                expected_layers,
1193                cached_hint_labels(editor),
1194                "Should get its first hints when opening the editor"
1195            );
1196            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1197            let inlay_cache = editor.inlay_hint_cache();
1198            assert_eq!(
1199                inlay_cache.version, 1,
1200                "Rust editor update the cache version after every cache/view change"
1201            );
1202        });
1203
1204        cx.foreground().run_until_parked();
1205        let _md_buffer = project
1206            .update(cx, |project, cx| {
1207                project.open_local_buffer("/a/other.md", cx)
1208            })
1209            .await
1210            .unwrap();
1211        cx.foreground().run_until_parked();
1212        cx.foreground().start_waiting();
1213        let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1214        let md_editor = workspace
1215            .update(cx, |workspace, cx| {
1216                workspace.open_path((worktree_id, "other.md"), None, true, cx)
1217            })
1218            .await
1219            .unwrap()
1220            .downcast::<Editor>()
1221            .unwrap();
1222        let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1223        md_fake_server
1224            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1225                let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
1226                async move {
1227                    assert_eq!(
1228                        params.text_document.uri,
1229                        lsp::Url::from_file_path("/a/other.md").unwrap(),
1230                    );
1231                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1232                    Ok(Some(vec![lsp::InlayHint {
1233                        position: lsp::Position::new(0, i),
1234                        label: lsp::InlayHintLabel::String(i.to_string()),
1235                        kind: None,
1236                        text_edits: None,
1237                        tooltip: None,
1238                        padding_left: None,
1239                        padding_right: None,
1240                        data: None,
1241                    }]))
1242                }
1243            })
1244            .next()
1245            .await;
1246        cx.foreground().run_until_parked();
1247        md_editor.update(cx, |editor, cx| {
1248            let expected_layers = vec!["0".to_string()];
1249            assert_eq!(
1250                expected_layers,
1251                cached_hint_labels(editor),
1252                "Markdown editor should have a separate verison, repeating Rust editor rules"
1253            );
1254            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1255            let inlay_cache = editor.inlay_hint_cache();
1256            assert_eq!(inlay_cache.version, 1);
1257        });
1258
1259        rs_editor.update(cx, |editor, cx| {
1260            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1261            editor.handle_input("some rs change", cx);
1262        });
1263        cx.foreground().run_until_parked();
1264        rs_editor.update(cx, |editor, cx| {
1265            let expected_layers = vec!["1".to_string()];
1266            assert_eq!(
1267                expected_layers,
1268                cached_hint_labels(editor),
1269                "Rust inlay cache should change after the edit"
1270            );
1271            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1272            let inlay_cache = editor.inlay_hint_cache();
1273            assert_eq!(
1274                inlay_cache.version, 2,
1275                "Every time hint cache changes, cache version should be incremented"
1276            );
1277        });
1278        md_editor.update(cx, |editor, cx| {
1279            let expected_layers = vec!["0".to_string()];
1280            assert_eq!(
1281                expected_layers,
1282                cached_hint_labels(editor),
1283                "Markdown editor should not be affected by Rust editor changes"
1284            );
1285            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1286            let inlay_cache = editor.inlay_hint_cache();
1287            assert_eq!(inlay_cache.version, 1);
1288        });
1289
1290        md_editor.update(cx, |editor, cx| {
1291            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1292            editor.handle_input("some md change", cx);
1293        });
1294        cx.foreground().run_until_parked();
1295        md_editor.update(cx, |editor, cx| {
1296            let expected_layers = vec!["1".to_string()];
1297            assert_eq!(
1298                expected_layers,
1299                cached_hint_labels(editor),
1300                "Rust editor should not be affected by Markdown editor changes"
1301            );
1302            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1303            let inlay_cache = editor.inlay_hint_cache();
1304            assert_eq!(inlay_cache.version, 2);
1305        });
1306        rs_editor.update(cx, |editor, cx| {
1307            let expected_layers = vec!["1".to_string()];
1308            assert_eq!(
1309                expected_layers,
1310                cached_hint_labels(editor),
1311                "Markdown editor should also change independently"
1312            );
1313            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1314            let inlay_cache = editor.inlay_hint_cache();
1315            assert_eq!(inlay_cache.version, 2);
1316        });
1317    }
1318
1319    #[gpui::test]
1320    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1321        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1322        init_test(cx, |settings| {
1323            settings.defaults.inlay_hints = Some(InlayHintSettings {
1324                enabled: true,
1325                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1326                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1327                show_other_hints: allowed_hint_kinds.contains(&None),
1328            })
1329        });
1330
1331        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1332        let lsp_request_count = Arc::new(AtomicU32::new(0));
1333        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1334        fake_server
1335            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1336                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1337                async move {
1338                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1339                    assert_eq!(
1340                        params.text_document.uri,
1341                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1342                    );
1343                    Ok(Some(vec![
1344                        lsp::InlayHint {
1345                            position: lsp::Position::new(0, 1),
1346                            label: lsp::InlayHintLabel::String("type hint".to_string()),
1347                            kind: Some(lsp::InlayHintKind::TYPE),
1348                            text_edits: None,
1349                            tooltip: None,
1350                            padding_left: None,
1351                            padding_right: None,
1352                            data: None,
1353                        },
1354                        lsp::InlayHint {
1355                            position: lsp::Position::new(0, 2),
1356                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1357                            kind: Some(lsp::InlayHintKind::PARAMETER),
1358                            text_edits: None,
1359                            tooltip: None,
1360                            padding_left: None,
1361                            padding_right: None,
1362                            data: None,
1363                        },
1364                        lsp::InlayHint {
1365                            position: lsp::Position::new(0, 3),
1366                            label: lsp::InlayHintLabel::String("other hint".to_string()),
1367                            kind: None,
1368                            text_edits: None,
1369                            tooltip: None,
1370                            padding_left: None,
1371                            padding_right: None,
1372                            data: None,
1373                        },
1374                    ]))
1375                }
1376            })
1377            .next()
1378            .await;
1379        cx.foreground().run_until_parked();
1380
1381        let mut edits_made = 1;
1382        editor.update(cx, |editor, cx| {
1383            assert_eq!(
1384                lsp_request_count.load(Ordering::Relaxed),
1385                1,
1386                "Should query new hints once"
1387            );
1388            assert_eq!(
1389                vec![
1390                    "other hint".to_string(),
1391                    "parameter hint".to_string(),
1392                    "type hint".to_string(),
1393                ],
1394                cached_hint_labels(editor),
1395                "Should get its first hints when opening the editor"
1396            );
1397            assert_eq!(
1398                vec!["other hint".to_string(), "type hint".to_string()],
1399                visible_hint_labels(editor, cx)
1400            );
1401            let inlay_cache = editor.inlay_hint_cache();
1402            assert_eq!(
1403                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1404                "Cache should use editor settings to get the allowed hint kinds"
1405            );
1406            assert_eq!(
1407                inlay_cache.version, edits_made,
1408                "The editor update the cache version after every cache/view change"
1409            );
1410        });
1411
1412        fake_server
1413            .request::<lsp::request::InlayHintRefreshRequest>(())
1414            .await
1415            .expect("inlay refresh request failed");
1416        cx.foreground().run_until_parked();
1417        editor.update(cx, |editor, cx| {
1418            assert_eq!(
1419                lsp_request_count.load(Ordering::Relaxed),
1420                2,
1421                "Should load new hints twice"
1422            );
1423            assert_eq!(
1424                vec![
1425                    "other hint".to_string(),
1426                    "parameter hint".to_string(),
1427                    "type hint".to_string(),
1428                ],
1429                cached_hint_labels(editor),
1430                "Cached hints should not change due to allowed hint kinds settings update"
1431            );
1432            assert_eq!(
1433                vec!["other hint".to_string(), "type hint".to_string()],
1434                visible_hint_labels(editor, cx)
1435            );
1436            let inlay_cache = editor.inlay_hint_cache();
1437            assert_eq!(
1438                inlay_cache.version, edits_made,
1439                "Should not update cache version due to new loaded hints being the same"
1440            );
1441        });
1442
1443        for (new_allowed_hint_kinds, expected_visible_hints) in [
1444            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1445            (
1446                HashSet::from_iter([Some(InlayHintKind::Type)]),
1447                vec!["type hint".to_string()],
1448            ),
1449            (
1450                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1451                vec!["parameter hint".to_string()],
1452            ),
1453            (
1454                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1455                vec!["other hint".to_string(), "type hint".to_string()],
1456            ),
1457            (
1458                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1459                vec!["other hint".to_string(), "parameter hint".to_string()],
1460            ),
1461            (
1462                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1463                vec!["parameter hint".to_string(), "type hint".to_string()],
1464            ),
1465            (
1466                HashSet::from_iter([
1467                    None,
1468                    Some(InlayHintKind::Type),
1469                    Some(InlayHintKind::Parameter),
1470                ]),
1471                vec![
1472                    "other hint".to_string(),
1473                    "parameter hint".to_string(),
1474                    "type hint".to_string(),
1475                ],
1476            ),
1477        ] {
1478            edits_made += 1;
1479            update_test_settings(cx, |settings| {
1480                settings.defaults.inlay_hints = Some(InlayHintSettings {
1481                    enabled: true,
1482                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1483                    show_parameter_hints: new_allowed_hint_kinds
1484                        .contains(&Some(InlayHintKind::Parameter)),
1485                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1486                })
1487            });
1488            cx.foreground().run_until_parked();
1489            editor.update(cx, |editor, cx| {
1490                assert_eq!(
1491                    lsp_request_count.load(Ordering::Relaxed),
1492                    2,
1493                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1494                );
1495                assert_eq!(
1496                    vec![
1497                        "other hint".to_string(),
1498                        "parameter hint".to_string(),
1499                        "type hint".to_string(),
1500                    ],
1501                    cached_hint_labels(editor),
1502                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1503                );
1504                assert_eq!(
1505                    expected_visible_hints,
1506                    visible_hint_labels(editor, cx),
1507                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1508                );
1509                let inlay_cache = editor.inlay_hint_cache();
1510                assert_eq!(
1511                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1512                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1513                );
1514                assert_eq!(
1515                    inlay_cache.version, edits_made,
1516                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1517                );
1518            });
1519        }
1520
1521        edits_made += 1;
1522        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1523        update_test_settings(cx, |settings| {
1524            settings.defaults.inlay_hints = Some(InlayHintSettings {
1525                enabled: false,
1526                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1527                show_parameter_hints: another_allowed_hint_kinds
1528                    .contains(&Some(InlayHintKind::Parameter)),
1529                show_other_hints: another_allowed_hint_kinds.contains(&None),
1530            })
1531        });
1532        cx.foreground().run_until_parked();
1533        editor.update(cx, |editor, cx| {
1534            assert_eq!(
1535                lsp_request_count.load(Ordering::Relaxed),
1536                2,
1537                "Should not load new hints when hints got disabled"
1538            );
1539            assert!(
1540                cached_hint_labels(editor).is_empty(),
1541                "Should clear the cache when hints got disabled"
1542            );
1543            assert!(
1544                visible_hint_labels(editor, cx).is_empty(),
1545                "Should clear visible hints when hints got disabled"
1546            );
1547            let inlay_cache = editor.inlay_hint_cache();
1548            assert_eq!(
1549                inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1550                "Should update its allowed hint kinds even when hints got disabled"
1551            );
1552            assert_eq!(
1553                inlay_cache.version, edits_made,
1554                "The editor should update the cache version after hints got disabled"
1555            );
1556        });
1557
1558        fake_server
1559            .request::<lsp::request::InlayHintRefreshRequest>(())
1560            .await
1561            .expect("inlay refresh request failed");
1562        cx.foreground().run_until_parked();
1563        editor.update(cx, |editor, cx| {
1564            assert_eq!(
1565                lsp_request_count.load(Ordering::Relaxed),
1566                2,
1567                "Should not load new hints when they got disabled"
1568            );
1569            assert!(cached_hint_labels(editor).is_empty());
1570            assert!(visible_hint_labels(editor, cx).is_empty());
1571            let inlay_cache = editor.inlay_hint_cache();
1572            assert_eq!(
1573                inlay_cache.version, edits_made,
1574                "The editor should not update the cache version after /refresh query without updates"
1575            );
1576        });
1577
1578        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1579        edits_made += 1;
1580        update_test_settings(cx, |settings| {
1581            settings.defaults.inlay_hints = Some(InlayHintSettings {
1582                enabled: true,
1583                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1584                show_parameter_hints: final_allowed_hint_kinds
1585                    .contains(&Some(InlayHintKind::Parameter)),
1586                show_other_hints: final_allowed_hint_kinds.contains(&None),
1587            })
1588        });
1589        cx.foreground().run_until_parked();
1590        editor.update(cx, |editor, cx| {
1591            assert_eq!(
1592                lsp_request_count.load(Ordering::Relaxed),
1593                3,
1594                "Should query for new hints when they got reenabled"
1595            );
1596            assert_eq!(
1597                vec![
1598                    "other hint".to_string(),
1599                    "parameter hint".to_string(),
1600                    "type hint".to_string(),
1601                ],
1602                cached_hint_labels(editor),
1603                "Should get its cached hints fully repopulated after the hints got reenabled"
1604            );
1605            assert_eq!(
1606                vec!["parameter hint".to_string()],
1607                visible_hint_labels(editor, cx),
1608                "Should get its visible hints repopulated and filtered after the h"
1609            );
1610            let inlay_cache = editor.inlay_hint_cache();
1611            assert_eq!(
1612                inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1613                "Cache should update editor settings when hints got reenabled"
1614            );
1615            assert_eq!(
1616                inlay_cache.version, edits_made,
1617                "Cache should update its version after hints got reenabled"
1618            );
1619        });
1620
1621        fake_server
1622            .request::<lsp::request::InlayHintRefreshRequest>(())
1623            .await
1624            .expect("inlay refresh request failed");
1625        cx.foreground().run_until_parked();
1626        editor.update(cx, |editor, cx| {
1627            assert_eq!(
1628                lsp_request_count.load(Ordering::Relaxed),
1629                4,
1630                "Should query for new hints again"
1631            );
1632            assert_eq!(
1633                vec![
1634                    "other hint".to_string(),
1635                    "parameter hint".to_string(),
1636                    "type hint".to_string(),
1637                ],
1638                cached_hint_labels(editor),
1639            );
1640            assert_eq!(
1641                vec!["parameter hint".to_string()],
1642                visible_hint_labels(editor, cx),
1643            );
1644            let inlay_cache = editor.inlay_hint_cache();
1645            assert_eq!(inlay_cache.version, edits_made);
1646        });
1647    }
1648
1649    #[gpui::test]
1650    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
1651        init_test(cx, |settings| {
1652            settings.defaults.inlay_hints = Some(InlayHintSettings {
1653                enabled: true,
1654                show_type_hints: true,
1655                show_parameter_hints: true,
1656                show_other_hints: true,
1657            })
1658        });
1659
1660        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1661        let fake_server = Arc::new(fake_server);
1662        let lsp_request_count = Arc::new(AtomicU32::new(0));
1663        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1664        fake_server
1665            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1666                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1667                async move {
1668                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1669                    assert_eq!(
1670                        params.text_document.uri,
1671                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1672                    );
1673                    Ok(Some(vec![lsp::InlayHint {
1674                        position: lsp::Position::new(0, i),
1675                        label: lsp::InlayHintLabel::String(i.to_string()),
1676                        kind: None,
1677                        text_edits: None,
1678                        tooltip: None,
1679                        padding_left: None,
1680                        padding_right: None,
1681                        data: None,
1682                    }]))
1683                }
1684            })
1685            .next()
1686            .await;
1687
1688        let mut expected_changes = Vec::new();
1689        for change_after_opening in [
1690            "initial change #1",
1691            "initial change #2",
1692            "initial change #3",
1693        ] {
1694            editor.update(cx, |editor, cx| {
1695                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1696                editor.handle_input(change_after_opening, cx);
1697            });
1698            expected_changes.push(change_after_opening);
1699        }
1700
1701        cx.foreground().run_until_parked();
1702
1703        editor.update(cx, |editor, cx| {
1704            let current_text = editor.text(cx);
1705            for change in &expected_changes {
1706                assert!(
1707                    current_text.contains(change),
1708                    "Should apply all changes made"
1709                );
1710            }
1711            assert_eq!(
1712                lsp_request_count.load(Ordering::Relaxed),
1713                2,
1714                "Should query new hints twice: for editor init and for the last edit that interrupted all others"
1715            );
1716            let expected_hints = vec!["2".to_string()];
1717            assert_eq!(
1718                expected_hints,
1719                cached_hint_labels(editor),
1720                "Should get hints from the last edit landed only"
1721            );
1722            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1723            let inlay_cache = editor.inlay_hint_cache();
1724            assert_eq!(
1725                inlay_cache.version, 1,
1726                "Only one update should be registered in the cache after all cancellations"
1727            );
1728        });
1729
1730        let mut edits = Vec::new();
1731        for async_later_change in [
1732            "another change #1",
1733            "another change #2",
1734            "another change #3",
1735        ] {
1736            expected_changes.push(async_later_change);
1737            let task_editor = editor.clone();
1738            let mut task_cx = cx.clone();
1739            edits.push(cx.foreground().spawn(async move {
1740                task_editor.update(&mut task_cx, |editor, cx| {
1741                    editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1742                    editor.handle_input(async_later_change, cx);
1743                });
1744            }));
1745        }
1746        let _ = futures::future::join_all(edits).await;
1747        cx.foreground().run_until_parked();
1748
1749        editor.update(cx, |editor, cx| {
1750            let current_text = editor.text(cx);
1751            for change in &expected_changes {
1752                assert!(
1753                    current_text.contains(change),
1754                    "Should apply all changes made"
1755                );
1756            }
1757            assert_eq!(
1758                lsp_request_count.load(Ordering::SeqCst),
1759                3,
1760                "Should query new hints one more time, for the last edit only"
1761            );
1762            let expected_hints = vec!["3".to_string()];
1763            assert_eq!(
1764                expected_hints,
1765                cached_hint_labels(editor),
1766                "Should get hints from the last edit landed only"
1767            );
1768            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1769            let inlay_cache = editor.inlay_hint_cache();
1770            assert_eq!(
1771                inlay_cache.version, 2,
1772                "Should update the cache version once more, for the new change"
1773            );
1774        });
1775    }
1776
1777    #[gpui::test]
1778    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
1779        init_test(cx, |settings| {
1780            settings.defaults.inlay_hints = Some(InlayHintSettings {
1781                enabled: true,
1782                show_type_hints: true,
1783                show_parameter_hints: true,
1784                show_other_hints: true,
1785            })
1786        });
1787
1788        let mut language = Language::new(
1789            LanguageConfig {
1790                name: "Rust".into(),
1791                path_suffixes: vec!["rs".to_string()],
1792                ..Default::default()
1793            },
1794            Some(tree_sitter_rust::language()),
1795        );
1796        let mut fake_servers = language
1797            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1798                capabilities: lsp::ServerCapabilities {
1799                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1800                    ..Default::default()
1801                },
1802                ..Default::default()
1803            }))
1804            .await;
1805        let fs = FakeFs::new(cx.background());
1806        fs.insert_tree(
1807            "/a",
1808            json!({
1809                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
1810                "other.rs": "// Test file",
1811            }),
1812        )
1813        .await;
1814        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1815        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
1816        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1817        let worktree_id = workspace.update(cx, |workspace, cx| {
1818            workspace.project().read_with(cx, |project, cx| {
1819                project.worktrees(cx).next().unwrap().read(cx).id()
1820            })
1821        });
1822
1823        let _buffer = project
1824            .update(cx, |project, cx| {
1825                project.open_local_buffer("/a/main.rs", cx)
1826            })
1827            .await
1828            .unwrap();
1829        cx.foreground().run_until_parked();
1830        cx.foreground().start_waiting();
1831        let fake_server = fake_servers.next().await.unwrap();
1832        let editor = workspace
1833            .update(cx, |workspace, cx| {
1834                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1835            })
1836            .await
1837            .unwrap()
1838            .downcast::<Editor>()
1839            .unwrap();
1840        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
1841        let lsp_request_count = Arc::new(AtomicU32::new(0));
1842        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
1843        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
1844        fake_server
1845            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1846                let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
1847                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
1848                async move {
1849                    assert_eq!(
1850                        params.text_document.uri,
1851                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1852                    );
1853
1854                    task_lsp_request_ranges.lock().push(params.range);
1855                    let query_start = params.range.start;
1856                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1857                    Ok(Some(vec![lsp::InlayHint {
1858                        position: query_start,
1859                        label: lsp::InlayHintLabel::String(i.to_string()),
1860                        kind: None,
1861                        text_edits: None,
1862                        tooltip: None,
1863                        padding_left: None,
1864                        padding_right: None,
1865                        data: None,
1866                    }]))
1867                }
1868            })
1869            .next()
1870            .await;
1871        cx.foreground().run_until_parked();
1872        editor.update(cx, |editor, cx| {
1873            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1874            ranges.sort_by_key(|range| range.start);
1875            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");
1876            assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
1877            assert_eq!(ranges[0].end.line, ranges[1].start.line, "Both requests should be on the same line");
1878            assert_eq!(ranges[0].end.character + 1, ranges[1].start.character, "Both request should be concequent");
1879
1880            assert_eq!(lsp_request_count.load(Ordering::SeqCst), 2,
1881                "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints");
1882            let expected_layers = vec!["1".to_string(), "2".to_string()];
1883            assert_eq!(
1884                expected_layers,
1885                cached_hint_labels(editor),
1886                "Should have hints from both LSP requests made for a big file"
1887            );
1888            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1889            let inlay_cache = editor.inlay_hint_cache();
1890            assert_eq!(
1891                inlay_cache.version, 2,
1892                "Both LSP queries should've bumped the cache version"
1893            );
1894        });
1895
1896        editor.update(cx, |editor, cx| {
1897            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1898            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1899            editor.change_selections(None, cx, |s| s.select_ranges([600..600]));
1900            editor.handle_input("++++more text++++", cx);
1901        });
1902
1903        cx.foreground().run_until_parked();
1904        editor.update(cx, |editor, cx| {
1905            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1906            ranges.sort_by_key(|range| range.start);
1907            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");
1908            assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
1909            assert_eq!(ranges[0].end.line + 1, ranges[1].start.line, "Neighbour requests got on different lines due to the line end");
1910            assert_ne!(ranges[0].end.character, 0, "First query was in the end of the line, not in the beginning");
1911            assert_eq!(ranges[1].start.character, 0, "Second query got pushed into a new line and starts from the beginning");
1912            assert_eq!(ranges[1].end.line, ranges[2].start.line, "Neighbour requests should be on the same line");
1913            assert_eq!(ranges[1].end.character + 1, ranges[2].start.character, "Neighbour request should be concequent");
1914
1915            assert_eq!(lsp_request_count.load(Ordering::SeqCst), 5,
1916                "When scroll not at the edge of a big document, visible part + 2 other parts should be queried for hints");
1917            let expected_layers = vec!["3".to_string(), "4".to_string(), "5".to_string()];
1918            assert_eq!(expected_layers, cached_hint_labels(editor),
1919                "Should have hints from the new LSP response after edit");
1920            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1921            let inlay_cache = editor.inlay_hint_cache();
1922            assert_eq!(inlay_cache.version, 5, "Should update the cache for every LSP response with hints added");
1923        });
1924    }
1925
1926    #[gpui::test]
1927    async fn test_multiple_excerpts_large_multibuffer(
1928        deterministic: Arc<Deterministic>,
1929        cx: &mut gpui::TestAppContext,
1930    ) {
1931        init_test(cx, |settings| {
1932            settings.defaults.inlay_hints = Some(InlayHintSettings {
1933                enabled: true,
1934                show_type_hints: true,
1935                show_parameter_hints: true,
1936                show_other_hints: true,
1937            })
1938        });
1939
1940        let mut language = Language::new(
1941            LanguageConfig {
1942                name: "Rust".into(),
1943                path_suffixes: vec!["rs".to_string()],
1944                ..Default::default()
1945            },
1946            Some(tree_sitter_rust::language()),
1947        );
1948        let mut fake_servers = language
1949            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1950                capabilities: lsp::ServerCapabilities {
1951                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1952                    ..Default::default()
1953                },
1954                ..Default::default()
1955            }))
1956            .await;
1957        let language = Arc::new(language);
1958        let fs = FakeFs::new(cx.background());
1959        fs.insert_tree(
1960            "/a",
1961            json!({
1962                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
1963                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
1964            }),
1965        )
1966        .await;
1967        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1968        project.update(cx, |project, _| {
1969            project.languages().add(Arc::clone(&language))
1970        });
1971        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1972        let worktree_id = workspace.update(cx, |workspace, cx| {
1973            workspace.project().read_with(cx, |project, cx| {
1974                project.worktrees(cx).next().unwrap().read(cx).id()
1975            })
1976        });
1977
1978        let buffer_1 = project
1979            .update(cx, |project, cx| {
1980                project.open_buffer((worktree_id, "main.rs"), cx)
1981            })
1982            .await
1983            .unwrap();
1984        let buffer_2 = project
1985            .update(cx, |project, cx| {
1986                project.open_buffer((worktree_id, "other.rs"), cx)
1987            })
1988            .await
1989            .unwrap();
1990        let multibuffer = cx.add_model(|cx| {
1991            let mut multibuffer = MultiBuffer::new(0);
1992            multibuffer.push_excerpts(
1993                buffer_1.clone(),
1994                [
1995                    ExcerptRange {
1996                        context: Point::new(0, 0)..Point::new(2, 0),
1997                        primary: None,
1998                    },
1999                    ExcerptRange {
2000                        context: Point::new(4, 0)..Point::new(11, 0),
2001                        primary: None,
2002                    },
2003                    ExcerptRange {
2004                        context: Point::new(22, 0)..Point::new(33, 0),
2005                        primary: None,
2006                    },
2007                    ExcerptRange {
2008                        context: Point::new(44, 0)..Point::new(55, 0),
2009                        primary: None,
2010                    },
2011                    ExcerptRange {
2012                        context: Point::new(56, 0)..Point::new(66, 0),
2013                        primary: None,
2014                    },
2015                    ExcerptRange {
2016                        context: Point::new(67, 0)..Point::new(77, 0),
2017                        primary: None,
2018                    },
2019                ],
2020                cx,
2021            );
2022            multibuffer.push_excerpts(
2023                buffer_2.clone(),
2024                [
2025                    ExcerptRange {
2026                        context: Point::new(0, 1)..Point::new(2, 1),
2027                        primary: None,
2028                    },
2029                    ExcerptRange {
2030                        context: Point::new(4, 1)..Point::new(11, 1),
2031                        primary: None,
2032                    },
2033                    ExcerptRange {
2034                        context: Point::new(22, 1)..Point::new(33, 1),
2035                        primary: None,
2036                    },
2037                    ExcerptRange {
2038                        context: Point::new(44, 1)..Point::new(55, 1),
2039                        primary: None,
2040                    },
2041                    ExcerptRange {
2042                        context: Point::new(56, 1)..Point::new(66, 1),
2043                        primary: None,
2044                    },
2045                    ExcerptRange {
2046                        context: Point::new(67, 1)..Point::new(77, 1),
2047                        primary: None,
2048                    },
2049                ],
2050                cx,
2051            );
2052            multibuffer
2053        });
2054
2055        deterministic.run_until_parked();
2056        cx.foreground().run_until_parked();
2057        let (_, editor) =
2058            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
2059        let editor_edited = Arc::new(AtomicBool::new(false));
2060        let fake_server = fake_servers.next().await.unwrap();
2061        let closure_editor_edited = Arc::clone(&editor_edited);
2062        fake_server
2063            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2064                let task_editor_edited = Arc::clone(&closure_editor_edited);
2065                async move {
2066                    let hint_text = if params.text_document.uri
2067                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2068                    {
2069                        "main hint"
2070                    } else if params.text_document.uri
2071                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2072                    {
2073                        "other hint"
2074                    } else {
2075                        panic!("unexpected uri: {:?}", params.text_document.uri);
2076                    };
2077
2078                    let positions = [
2079                        lsp::Position::new(0, 2),
2080                        lsp::Position::new(4, 2),
2081                        lsp::Position::new(22, 2),
2082                        lsp::Position::new(44, 2),
2083                        lsp::Position::new(56, 2),
2084                        lsp::Position::new(67, 2),
2085                    ];
2086                    let out_of_range_hint = lsp::InlayHint {
2087                        position: lsp::Position::new(
2088                            params.range.start.line + 99,
2089                            params.range.start.character + 99,
2090                        ),
2091                        label: lsp::InlayHintLabel::String(
2092                            "out of excerpt range, should be ignored".to_string(),
2093                        ),
2094                        kind: None,
2095                        text_edits: None,
2096                        tooltip: None,
2097                        padding_left: None,
2098                        padding_right: None,
2099                        data: None,
2100                    };
2101
2102                    let edited = task_editor_edited.load(Ordering::Acquire);
2103                    Ok(Some(
2104                        std::iter::once(out_of_range_hint)
2105                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2106                                lsp::InlayHint {
2107                                    position,
2108                                    label: lsp::InlayHintLabel::String(format!(
2109                                        "{hint_text}{} #{i}",
2110                                        if edited { "(edited)" } else { "" },
2111                                    )),
2112                                    kind: None,
2113                                    text_edits: None,
2114                                    tooltip: None,
2115                                    padding_left: None,
2116                                    padding_right: None,
2117                                    data: None,
2118                                }
2119                            }))
2120                            .collect(),
2121                    ))
2122                }
2123            })
2124            .next()
2125            .await;
2126        cx.foreground().run_until_parked();
2127
2128        editor.update(cx, |editor, cx| {
2129            let expected_layers = vec![
2130                "main hint #0".to_string(),
2131                "main hint #1".to_string(),
2132                "main hint #2".to_string(),
2133                "main hint #3".to_string(),
2134            ];
2135            assert_eq!(
2136                expected_layers,
2137                cached_hint_labels(editor),
2138                "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2139            );
2140            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2141            let inlay_cache = editor.inlay_hint_cache();
2142            assert_eq!(inlay_cache.version, 4, "Every visible excerpt hints should bump the verison");
2143        });
2144
2145        editor.update(cx, |editor, cx| {
2146            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2147                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2148            });
2149            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2150                s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2151            });
2152            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2153                s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
2154            });
2155        });
2156        cx.foreground().run_until_parked();
2157        editor.update(cx, |editor, cx| {
2158            let expected_layers = vec![
2159                "main hint #0".to_string(),
2160                "main hint #1".to_string(),
2161                "main hint #2".to_string(),
2162                "main hint #3".to_string(),
2163                "main hint #4".to_string(),
2164                "main hint #5".to_string(),
2165                "other hint #0".to_string(),
2166                "other hint #1".to_string(),
2167                "other hint #2".to_string(),
2168            ];
2169            assert_eq!(expected_layers, cached_hint_labels(editor),
2170                "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2171            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2172            let inlay_cache = editor.inlay_hint_cache();
2173            assert_eq!(inlay_cache.version, 9);
2174        });
2175
2176        editor.update(cx, |editor, cx| {
2177            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2178                s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2179            });
2180        });
2181        cx.foreground().run_until_parked();
2182        editor.update(cx, |editor, cx| {
2183            let expected_layers = vec![
2184                "main hint #0".to_string(),
2185                "main hint #1".to_string(),
2186                "main hint #2".to_string(),
2187                "main hint #3".to_string(),
2188                "main hint #4".to_string(),
2189                "main hint #5".to_string(),
2190                "other hint #0".to_string(),
2191                "other hint #1".to_string(),
2192                "other hint #2".to_string(),
2193                "other hint #3".to_string(),
2194                "other hint #4".to_string(),
2195                "other hint #5".to_string(),
2196            ];
2197            assert_eq!(expected_layers, cached_hint_labels(editor),
2198                "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2199            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2200            let inlay_cache = editor.inlay_hint_cache();
2201            assert_eq!(inlay_cache.version, 12);
2202        });
2203
2204        editor.update(cx, |editor, cx| {
2205            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2206                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2207            });
2208        });
2209        cx.foreground().run_until_parked();
2210        editor.update(cx, |editor, cx| {
2211            let expected_layers = vec![
2212                "main hint #0".to_string(),
2213                "main hint #1".to_string(),
2214                "main hint #2".to_string(),
2215                "main hint #3".to_string(),
2216                "main hint #4".to_string(),
2217                "main hint #5".to_string(),
2218                "other hint #0".to_string(),
2219                "other hint #1".to_string(),
2220                "other hint #2".to_string(),
2221                "other hint #3".to_string(),
2222                "other hint #4".to_string(),
2223                "other hint #5".to_string(),
2224            ];
2225            assert_eq!(expected_layers, cached_hint_labels(editor),
2226                "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2227            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2228            let inlay_cache = editor.inlay_hint_cache();
2229            assert_eq!(inlay_cache.version, 12, "No updates should happen during scrolling already scolled buffer");
2230        });
2231
2232        editor_edited.store(true, Ordering::Release);
2233        editor.update(cx, |editor, cx| {
2234            editor.handle_input("++++more text++++", cx);
2235        });
2236        cx.foreground().run_until_parked();
2237        editor.update(cx, |editor, cx| {
2238            let expected_layers = vec![
2239                "main hint(edited) #0".to_string(),
2240                "main hint(edited) #1".to_string(),
2241                "main hint(edited) #2".to_string(),
2242                "main hint(edited) #3".to_string(),
2243                "other hint #0".to_string(),
2244                "other hint #1".to_string(),
2245                "other hint #2".to_string(),
2246                "other hint #3".to_string(),
2247                "other hint #4".to_string(),
2248                "other hint #5".to_string(),
2249            ];
2250            assert_eq!(expected_layers, cached_hint_labels(editor),
2251                "After multibuffer was edited, hints for the edited buffer (1st) should be invalidated and requeried for all of its visible excerpts, \
2252unedited (2nd) buffer should have the same hint");
2253            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2254            let inlay_cache = editor.inlay_hint_cache();
2255            assert_eq!(inlay_cache.version, 16);
2256        });
2257    }
2258
2259    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2260        cx.foreground().forbid_parking();
2261
2262        cx.update(|cx| {
2263            cx.set_global(SettingsStore::test(cx));
2264            theme::init((), cx);
2265            client::init_settings(cx);
2266            language::init(cx);
2267            Project::init_settings(cx);
2268            workspace::init_settings(cx);
2269            crate::init(cx);
2270        });
2271
2272        update_test_settings(cx, f);
2273    }
2274
2275    async fn prepare_test_objects(
2276        cx: &mut TestAppContext,
2277    ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
2278        let mut language = Language::new(
2279            LanguageConfig {
2280                name: "Rust".into(),
2281                path_suffixes: vec!["rs".to_string()],
2282                ..Default::default()
2283            },
2284            Some(tree_sitter_rust::language()),
2285        );
2286        let mut fake_servers = language
2287            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2288                capabilities: lsp::ServerCapabilities {
2289                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2290                    ..Default::default()
2291                },
2292                ..Default::default()
2293            }))
2294            .await;
2295
2296        let fs = FakeFs::new(cx.background());
2297        fs.insert_tree(
2298            "/a",
2299            json!({
2300                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
2301                "other.rs": "// Test file",
2302            }),
2303        )
2304        .await;
2305
2306        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2307        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2308        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2309        let worktree_id = workspace.update(cx, |workspace, cx| {
2310            workspace.project().read_with(cx, |project, cx| {
2311                project.worktrees(cx).next().unwrap().read(cx).id()
2312            })
2313        });
2314
2315        let _buffer = project
2316            .update(cx, |project, cx| {
2317                project.open_local_buffer("/a/main.rs", cx)
2318            })
2319            .await
2320            .unwrap();
2321        cx.foreground().run_until_parked();
2322        cx.foreground().start_waiting();
2323        let fake_server = fake_servers.next().await.unwrap();
2324        let editor = workspace
2325            .update(cx, |workspace, cx| {
2326                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2327            })
2328            .await
2329            .unwrap()
2330            .downcast::<Editor>()
2331            .unwrap();
2332
2333        ("/a/main.rs", editor, fake_server)
2334    }
2335
2336    fn cached_hint_labels(editor: &Editor) -> Vec<String> {
2337        let mut labels = Vec::new();
2338        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
2339            let excerpt_hints = excerpt_hints.read();
2340            for (_, inlay) in excerpt_hints.hints.iter() {
2341                match &inlay.label {
2342                    project::InlayHintLabel::String(s) => labels.push(s.to_string()),
2343                    _ => unreachable!(),
2344                }
2345            }
2346        }
2347
2348        labels.sort();
2349        labels
2350    }
2351
2352    fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
2353        let mut hints = editor
2354            .visible_inlay_hints(cx)
2355            .into_iter()
2356            .map(|hint| hint.text.to_string())
2357            .collect::<Vec<_>>();
2358        hints.sort();
2359        hints
2360    }
2361}