inlay_hint_cache.rs

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