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