inlay_hint_cache.rs

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