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.add_window(|cx| Workspace::test_new(project.clone(), cx));
1139        let worktree_id = workspace.update(cx, |workspace, cx| {
1140            workspace.project().read_with(cx, |project, cx| {
1141                project.worktrees(cx).next().unwrap().read(cx).id()
1142            })
1143        });
1144
1145        let mut rs_fake_servers = None;
1146        let mut md_fake_servers = None;
1147        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1148            let mut language = Language::new(
1149                LanguageConfig {
1150                    name: name.into(),
1151                    path_suffixes: vec![path_suffix.to_string()],
1152                    ..Default::default()
1153                },
1154                Some(tree_sitter_rust::language()),
1155            );
1156            let fake_servers = language
1157                .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1158                    name,
1159                    capabilities: lsp::ServerCapabilities {
1160                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1161                        ..Default::default()
1162                    },
1163                    ..Default::default()
1164                }))
1165                .await;
1166            match name {
1167                "Rust" => rs_fake_servers = Some(fake_servers),
1168                "Markdown" => md_fake_servers = Some(fake_servers),
1169                _ => unreachable!(),
1170            }
1171            project.update(cx, |project, _| {
1172                project.languages().add(Arc::new(language));
1173            });
1174        }
1175
1176        let _rs_buffer = project
1177            .update(cx, |project, cx| {
1178                project.open_local_buffer("/a/main.rs", cx)
1179            })
1180            .await
1181            .unwrap();
1182        cx.foreground().run_until_parked();
1183        cx.foreground().start_waiting();
1184        let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1185        let rs_editor = workspace
1186            .update(cx, |workspace, cx| {
1187                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1188            })
1189            .await
1190            .unwrap()
1191            .downcast::<Editor>()
1192            .unwrap();
1193        let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1194        rs_fake_server
1195            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1196                let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
1197                async move {
1198                    assert_eq!(
1199                        params.text_document.uri,
1200                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1201                    );
1202                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1203                    Ok(Some(vec![lsp::InlayHint {
1204                        position: lsp::Position::new(0, i),
1205                        label: lsp::InlayHintLabel::String(i.to_string()),
1206                        kind: None,
1207                        text_edits: None,
1208                        tooltip: None,
1209                        padding_left: None,
1210                        padding_right: None,
1211                        data: None,
1212                    }]))
1213                }
1214            })
1215            .next()
1216            .await;
1217        cx.foreground().run_until_parked();
1218        rs_editor.update(cx, |editor, cx| {
1219            let expected_layers = vec!["0".to_string()];
1220            assert_eq!(
1221                expected_layers,
1222                cached_hint_labels(editor),
1223                "Should get its first hints when opening the editor"
1224            );
1225            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1226            assert_eq!(
1227                editor.inlay_hint_cache().version,
1228                1,
1229                "Rust editor update the cache version after every cache/view change"
1230            );
1231        });
1232
1233        cx.foreground().run_until_parked();
1234        let _md_buffer = project
1235            .update(cx, |project, cx| {
1236                project.open_local_buffer("/a/other.md", cx)
1237            })
1238            .await
1239            .unwrap();
1240        cx.foreground().run_until_parked();
1241        cx.foreground().start_waiting();
1242        let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1243        let md_editor = workspace
1244            .update(cx, |workspace, cx| {
1245                workspace.open_path((worktree_id, "other.md"), None, true, cx)
1246            })
1247            .await
1248            .unwrap()
1249            .downcast::<Editor>()
1250            .unwrap();
1251        let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1252        md_fake_server
1253            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1254                let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
1255                async move {
1256                    assert_eq!(
1257                        params.text_document.uri,
1258                        lsp::Url::from_file_path("/a/other.md").unwrap(),
1259                    );
1260                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1261                    Ok(Some(vec![lsp::InlayHint {
1262                        position: lsp::Position::new(0, i),
1263                        label: lsp::InlayHintLabel::String(i.to_string()),
1264                        kind: None,
1265                        text_edits: None,
1266                        tooltip: None,
1267                        padding_left: None,
1268                        padding_right: None,
1269                        data: None,
1270                    }]))
1271                }
1272            })
1273            .next()
1274            .await;
1275        cx.foreground().run_until_parked();
1276        md_editor.update(cx, |editor, cx| {
1277            let expected_layers = vec!["0".to_string()];
1278            assert_eq!(
1279                expected_layers,
1280                cached_hint_labels(editor),
1281                "Markdown editor should have a separate verison, repeating Rust editor rules"
1282            );
1283            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1284            assert_eq!(editor.inlay_hint_cache().version, 1);
1285        });
1286
1287        rs_editor.update(cx, |editor, cx| {
1288            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1289            editor.handle_input("some rs change", cx);
1290        });
1291        cx.foreground().run_until_parked();
1292        rs_editor.update(cx, |editor, cx| {
1293            let expected_layers = vec!["1".to_string()];
1294            assert_eq!(
1295                expected_layers,
1296                cached_hint_labels(editor),
1297                "Rust inlay cache should change after the edit"
1298            );
1299            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1300            assert_eq!(
1301                editor.inlay_hint_cache().version,
1302                2,
1303                "Every time hint cache changes, cache version should be incremented"
1304            );
1305        });
1306        md_editor.update(cx, |editor, cx| {
1307            let expected_layers = vec!["0".to_string()];
1308            assert_eq!(
1309                expected_layers,
1310                cached_hint_labels(editor),
1311                "Markdown editor should not be affected by Rust editor changes"
1312            );
1313            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1314            assert_eq!(editor.inlay_hint_cache().version, 1);
1315        });
1316
1317        md_editor.update(cx, |editor, cx| {
1318            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1319            editor.handle_input("some md change", cx);
1320        });
1321        cx.foreground().run_until_parked();
1322        md_editor.update(cx, |editor, cx| {
1323            let expected_layers = vec!["1".to_string()];
1324            assert_eq!(
1325                expected_layers,
1326                cached_hint_labels(editor),
1327                "Rust editor should not be affected by Markdown editor changes"
1328            );
1329            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1330            assert_eq!(editor.inlay_hint_cache().version, 2);
1331        });
1332        rs_editor.update(cx, |editor, cx| {
1333            let expected_layers = vec!["1".to_string()];
1334            assert_eq!(
1335                expected_layers,
1336                cached_hint_labels(editor),
1337                "Markdown editor should also change independently"
1338            );
1339            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1340            assert_eq!(editor.inlay_hint_cache().version, 2);
1341        });
1342    }
1343
1344    #[gpui::test]
1345    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1346        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1347        init_test(cx, |settings| {
1348            settings.defaults.inlay_hints = Some(InlayHintSettings {
1349                enabled: true,
1350                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1351                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1352                show_other_hints: allowed_hint_kinds.contains(&None),
1353            })
1354        });
1355
1356        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1357        let lsp_request_count = Arc::new(AtomicU32::new(0));
1358        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1359        fake_server
1360            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1361                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1362                async move {
1363                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1364                    assert_eq!(
1365                        params.text_document.uri,
1366                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1367                    );
1368                    Ok(Some(vec![
1369                        lsp::InlayHint {
1370                            position: lsp::Position::new(0, 1),
1371                            label: lsp::InlayHintLabel::String("type hint".to_string()),
1372                            kind: Some(lsp::InlayHintKind::TYPE),
1373                            text_edits: None,
1374                            tooltip: None,
1375                            padding_left: None,
1376                            padding_right: None,
1377                            data: None,
1378                        },
1379                        lsp::InlayHint {
1380                            position: lsp::Position::new(0, 2),
1381                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1382                            kind: Some(lsp::InlayHintKind::PARAMETER),
1383                            text_edits: None,
1384                            tooltip: None,
1385                            padding_left: None,
1386                            padding_right: None,
1387                            data: None,
1388                        },
1389                        lsp::InlayHint {
1390                            position: lsp::Position::new(0, 3),
1391                            label: lsp::InlayHintLabel::String("other hint".to_string()),
1392                            kind: None,
1393                            text_edits: None,
1394                            tooltip: None,
1395                            padding_left: None,
1396                            padding_right: None,
1397                            data: None,
1398                        },
1399                    ]))
1400                }
1401            })
1402            .next()
1403            .await;
1404        cx.foreground().run_until_parked();
1405
1406        let mut edits_made = 1;
1407        editor.update(cx, |editor, cx| {
1408            assert_eq!(
1409                lsp_request_count.load(Ordering::Relaxed),
1410                1,
1411                "Should query new hints once"
1412            );
1413            assert_eq!(
1414                vec![
1415                    "other hint".to_string(),
1416                    "parameter hint".to_string(),
1417                    "type hint".to_string(),
1418                ],
1419                cached_hint_labels(editor),
1420                "Should get its first hints when opening the editor"
1421            );
1422            assert_eq!(
1423                vec!["other hint".to_string(), "type hint".to_string()],
1424                visible_hint_labels(editor, cx)
1425            );
1426            let inlay_cache = editor.inlay_hint_cache();
1427            assert_eq!(
1428                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1429                "Cache should use editor settings to get the allowed hint kinds"
1430            );
1431            assert_eq!(
1432                inlay_cache.version, edits_made,
1433                "The editor update the cache version after every cache/view change"
1434            );
1435        });
1436
1437        fake_server
1438            .request::<lsp::request::InlayHintRefreshRequest>(())
1439            .await
1440            .expect("inlay refresh request failed");
1441        cx.foreground().run_until_parked();
1442        editor.update(cx, |editor, cx| {
1443            assert_eq!(
1444                lsp_request_count.load(Ordering::Relaxed),
1445                2,
1446                "Should load new hints twice"
1447            );
1448            assert_eq!(
1449                vec![
1450                    "other hint".to_string(),
1451                    "parameter hint".to_string(),
1452                    "type hint".to_string(),
1453                ],
1454                cached_hint_labels(editor),
1455                "Cached hints should not change due to allowed hint kinds settings update"
1456            );
1457            assert_eq!(
1458                vec!["other hint".to_string(), "type hint".to_string()],
1459                visible_hint_labels(editor, cx)
1460            );
1461            assert_eq!(
1462                editor.inlay_hint_cache().version,
1463                edits_made,
1464                "Should not update cache version due to new loaded hints being the same"
1465            );
1466        });
1467
1468        for (new_allowed_hint_kinds, expected_visible_hints) in [
1469            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1470            (
1471                HashSet::from_iter([Some(InlayHintKind::Type)]),
1472                vec!["type hint".to_string()],
1473            ),
1474            (
1475                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1476                vec!["parameter hint".to_string()],
1477            ),
1478            (
1479                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1480                vec!["other hint".to_string(), "type hint".to_string()],
1481            ),
1482            (
1483                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1484                vec!["other hint".to_string(), "parameter hint".to_string()],
1485            ),
1486            (
1487                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1488                vec!["parameter hint".to_string(), "type hint".to_string()],
1489            ),
1490            (
1491                HashSet::from_iter([
1492                    None,
1493                    Some(InlayHintKind::Type),
1494                    Some(InlayHintKind::Parameter),
1495                ]),
1496                vec![
1497                    "other hint".to_string(),
1498                    "parameter hint".to_string(),
1499                    "type hint".to_string(),
1500                ],
1501            ),
1502        ] {
1503            edits_made += 1;
1504            update_test_language_settings(cx, |settings| {
1505                settings.defaults.inlay_hints = Some(InlayHintSettings {
1506                    enabled: true,
1507                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1508                    show_parameter_hints: new_allowed_hint_kinds
1509                        .contains(&Some(InlayHintKind::Parameter)),
1510                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1511                })
1512            });
1513            cx.foreground().run_until_parked();
1514            editor.update(cx, |editor, cx| {
1515                assert_eq!(
1516                    lsp_request_count.load(Ordering::Relaxed),
1517                    2,
1518                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1519                );
1520                assert_eq!(
1521                    vec![
1522                        "other hint".to_string(),
1523                        "parameter hint".to_string(),
1524                        "type hint".to_string(),
1525                    ],
1526                    cached_hint_labels(editor),
1527                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1528                );
1529                assert_eq!(
1530                    expected_visible_hints,
1531                    visible_hint_labels(editor, cx),
1532                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1533                );
1534                let inlay_cache = editor.inlay_hint_cache();
1535                assert_eq!(
1536                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1537                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1538                );
1539                assert_eq!(
1540                    inlay_cache.version, edits_made,
1541                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1542                );
1543            });
1544        }
1545
1546        edits_made += 1;
1547        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1548        update_test_language_settings(cx, |settings| {
1549            settings.defaults.inlay_hints = Some(InlayHintSettings {
1550                enabled: false,
1551                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1552                show_parameter_hints: another_allowed_hint_kinds
1553                    .contains(&Some(InlayHintKind::Parameter)),
1554                show_other_hints: another_allowed_hint_kinds.contains(&None),
1555            })
1556        });
1557        cx.foreground().run_until_parked();
1558        editor.update(cx, |editor, cx| {
1559            assert_eq!(
1560                lsp_request_count.load(Ordering::Relaxed),
1561                2,
1562                "Should not load new hints when hints got disabled"
1563            );
1564            assert!(
1565                cached_hint_labels(editor).is_empty(),
1566                "Should clear the cache when hints got disabled"
1567            );
1568            assert!(
1569                visible_hint_labels(editor, cx).is_empty(),
1570                "Should clear visible hints when hints got disabled"
1571            );
1572            let inlay_cache = editor.inlay_hint_cache();
1573            assert_eq!(
1574                inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1575                "Should update its allowed hint kinds even when hints got disabled"
1576            );
1577            assert_eq!(
1578                inlay_cache.version, edits_made,
1579                "The editor should update the cache version after hints got disabled"
1580            );
1581        });
1582
1583        fake_server
1584            .request::<lsp::request::InlayHintRefreshRequest>(())
1585            .await
1586            .expect("inlay refresh request failed");
1587        cx.foreground().run_until_parked();
1588        editor.update(cx, |editor, cx| {
1589            assert_eq!(
1590                lsp_request_count.load(Ordering::Relaxed),
1591                2,
1592                "Should not load new hints when they got disabled"
1593            );
1594            assert!(cached_hint_labels(editor).is_empty());
1595            assert!(visible_hint_labels(editor, cx).is_empty());
1596            assert_eq!(
1597                editor.inlay_hint_cache().version, edits_made,
1598                "The editor should not update the cache version after /refresh query without updates"
1599            );
1600        });
1601
1602        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1603        edits_made += 1;
1604        update_test_language_settings(cx, |settings| {
1605            settings.defaults.inlay_hints = Some(InlayHintSettings {
1606                enabled: true,
1607                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1608                show_parameter_hints: final_allowed_hint_kinds
1609                    .contains(&Some(InlayHintKind::Parameter)),
1610                show_other_hints: final_allowed_hint_kinds.contains(&None),
1611            })
1612        });
1613        cx.foreground().run_until_parked();
1614        editor.update(cx, |editor, cx| {
1615            assert_eq!(
1616                lsp_request_count.load(Ordering::Relaxed),
1617                3,
1618                "Should query for new hints when they got reenabled"
1619            );
1620            assert_eq!(
1621                vec![
1622                    "other hint".to_string(),
1623                    "parameter hint".to_string(),
1624                    "type hint".to_string(),
1625                ],
1626                cached_hint_labels(editor),
1627                "Should get its cached hints fully repopulated after the hints got reenabled"
1628            );
1629            assert_eq!(
1630                vec!["parameter hint".to_string()],
1631                visible_hint_labels(editor, cx),
1632                "Should get its visible hints repopulated and filtered after the h"
1633            );
1634            let inlay_cache = editor.inlay_hint_cache();
1635            assert_eq!(
1636                inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1637                "Cache should update editor settings when hints got reenabled"
1638            );
1639            assert_eq!(
1640                inlay_cache.version, edits_made,
1641                "Cache should update its version after hints got reenabled"
1642            );
1643        });
1644
1645        fake_server
1646            .request::<lsp::request::InlayHintRefreshRequest>(())
1647            .await
1648            .expect("inlay refresh request failed");
1649        cx.foreground().run_until_parked();
1650        editor.update(cx, |editor, cx| {
1651            assert_eq!(
1652                lsp_request_count.load(Ordering::Relaxed),
1653                4,
1654                "Should query for new hints again"
1655            );
1656            assert_eq!(
1657                vec![
1658                    "other hint".to_string(),
1659                    "parameter hint".to_string(),
1660                    "type hint".to_string(),
1661                ],
1662                cached_hint_labels(editor),
1663            );
1664            assert_eq!(
1665                vec!["parameter hint".to_string()],
1666                visible_hint_labels(editor, cx),
1667            );
1668            assert_eq!(editor.inlay_hint_cache().version, edits_made);
1669        });
1670    }
1671
1672    #[gpui::test]
1673    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
1674        init_test(cx, |settings| {
1675            settings.defaults.inlay_hints = Some(InlayHintSettings {
1676                enabled: true,
1677                show_type_hints: true,
1678                show_parameter_hints: true,
1679                show_other_hints: true,
1680            })
1681        });
1682
1683        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1684        let fake_server = Arc::new(fake_server);
1685        let lsp_request_count = Arc::new(AtomicU32::new(0));
1686        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1687        fake_server
1688            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1689                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1690                async move {
1691                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1692                    assert_eq!(
1693                        params.text_document.uri,
1694                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1695                    );
1696                    Ok(Some(vec![lsp::InlayHint {
1697                        position: lsp::Position::new(0, i),
1698                        label: lsp::InlayHintLabel::String(i.to_string()),
1699                        kind: None,
1700                        text_edits: None,
1701                        tooltip: None,
1702                        padding_left: None,
1703                        padding_right: None,
1704                        data: None,
1705                    }]))
1706                }
1707            })
1708            .next()
1709            .await;
1710
1711        let mut expected_changes = Vec::new();
1712        for change_after_opening in [
1713            "initial change #1",
1714            "initial change #2",
1715            "initial change #3",
1716        ] {
1717            editor.update(cx, |editor, cx| {
1718                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1719                editor.handle_input(change_after_opening, cx);
1720            });
1721            expected_changes.push(change_after_opening);
1722        }
1723
1724        cx.foreground().run_until_parked();
1725
1726        editor.update(cx, |editor, cx| {
1727            let current_text = editor.text(cx);
1728            for change in &expected_changes {
1729                assert!(
1730                    current_text.contains(change),
1731                    "Should apply all changes made"
1732                );
1733            }
1734            assert_eq!(
1735                lsp_request_count.load(Ordering::Relaxed),
1736                2,
1737                "Should query new hints twice: for editor init and for the last edit that interrupted all others"
1738            );
1739            let expected_hints = vec!["2".to_string()];
1740            assert_eq!(
1741                expected_hints,
1742                cached_hint_labels(editor),
1743                "Should get hints from the last edit landed only"
1744            );
1745            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1746            assert_eq!(
1747                editor.inlay_hint_cache().version, 1,
1748                "Only one update should be registered in the cache after all cancellations"
1749            );
1750        });
1751
1752        let mut edits = Vec::new();
1753        for async_later_change in [
1754            "another change #1",
1755            "another change #2",
1756            "another change #3",
1757        ] {
1758            expected_changes.push(async_later_change);
1759            let task_editor = editor.clone();
1760            let mut task_cx = cx.clone();
1761            edits.push(cx.foreground().spawn(async move {
1762                task_editor.update(&mut task_cx, |editor, cx| {
1763                    editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1764                    editor.handle_input(async_later_change, cx);
1765                });
1766            }));
1767        }
1768        let _ = futures::future::join_all(edits).await;
1769        cx.foreground().run_until_parked();
1770
1771        editor.update(cx, |editor, cx| {
1772            let current_text = editor.text(cx);
1773            for change in &expected_changes {
1774                assert!(
1775                    current_text.contains(change),
1776                    "Should apply all changes made"
1777                );
1778            }
1779            assert_eq!(
1780                lsp_request_count.load(Ordering::SeqCst),
1781                3,
1782                "Should query new hints one more time, for the last edit only"
1783            );
1784            let expected_hints = vec!["3".to_string()];
1785            assert_eq!(
1786                expected_hints,
1787                cached_hint_labels(editor),
1788                "Should get hints from the last edit landed only"
1789            );
1790            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1791            assert_eq!(
1792                editor.inlay_hint_cache().version,
1793                2,
1794                "Should update the cache version once more, for the new change"
1795            );
1796        });
1797    }
1798
1799    #[gpui::test]
1800    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
1801        init_test(cx, |settings| {
1802            settings.defaults.inlay_hints = Some(InlayHintSettings {
1803                enabled: true,
1804                show_type_hints: true,
1805                show_parameter_hints: true,
1806                show_other_hints: true,
1807            })
1808        });
1809
1810        let mut language = Language::new(
1811            LanguageConfig {
1812                name: "Rust".into(),
1813                path_suffixes: vec!["rs".to_string()],
1814                ..Default::default()
1815            },
1816            Some(tree_sitter_rust::language()),
1817        );
1818        let mut fake_servers = language
1819            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1820                capabilities: lsp::ServerCapabilities {
1821                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1822                    ..Default::default()
1823                },
1824                ..Default::default()
1825            }))
1826            .await;
1827        let fs = FakeFs::new(cx.background());
1828        fs.insert_tree(
1829            "/a",
1830            json!({
1831                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
1832                "other.rs": "// Test file",
1833            }),
1834        )
1835        .await;
1836        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1837        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
1838        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1839        let worktree_id = workspace.update(cx, |workspace, cx| {
1840            workspace.project().read_with(cx, |project, cx| {
1841                project.worktrees(cx).next().unwrap().read(cx).id()
1842            })
1843        });
1844
1845        let _buffer = project
1846            .update(cx, |project, cx| {
1847                project.open_local_buffer("/a/main.rs", cx)
1848            })
1849            .await
1850            .unwrap();
1851        cx.foreground().run_until_parked();
1852        cx.foreground().start_waiting();
1853        let fake_server = fake_servers.next().await.unwrap();
1854        let editor = workspace
1855            .update(cx, |workspace, cx| {
1856                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1857            })
1858            .await
1859            .unwrap()
1860            .downcast::<Editor>()
1861            .unwrap();
1862        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
1863        let lsp_request_count = Arc::new(AtomicU32::new(0));
1864        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
1865        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
1866        fake_server
1867            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1868                let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
1869                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
1870                async move {
1871                    assert_eq!(
1872                        params.text_document.uri,
1873                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1874                    );
1875
1876                    task_lsp_request_ranges.lock().push(params.range);
1877                    let query_start = params.range.start;
1878                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1879                    Ok(Some(vec![lsp::InlayHint {
1880                        position: query_start,
1881                        label: lsp::InlayHintLabel::String(i.to_string()),
1882                        kind: None,
1883                        text_edits: None,
1884                        tooltip: None,
1885                        padding_left: None,
1886                        padding_right: None,
1887                        data: None,
1888                    }]))
1889                }
1890            })
1891            .next()
1892            .await;
1893        cx.foreground().run_until_parked();
1894        editor.update(cx, |editor, cx| {
1895            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1896            ranges.sort_by_key(|range| range.start);
1897            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");
1898            assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
1899            assert_eq!(ranges[0].end.line, ranges[1].start.line, "Both requests should be on the same line");
1900            assert_eq!(ranges[0].end.character + 1, ranges[1].start.character, "Both request should be concequent");
1901
1902            assert_eq!(lsp_request_count.load(Ordering::SeqCst), 2,
1903                "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints");
1904            let expected_layers = vec!["1".to_string(), "2".to_string()];
1905            assert_eq!(
1906                expected_layers,
1907                cached_hint_labels(editor),
1908                "Should have hints from both LSP requests made for a big file"
1909            );
1910            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1911            assert_eq!(
1912                editor.inlay_hint_cache().version, 2,
1913                "Both LSP queries should've bumped the cache version"
1914            );
1915        });
1916
1917        editor.update(cx, |editor, cx| {
1918            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1919            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1920            editor.change_selections(None, cx, |s| s.select_ranges([600..600]));
1921            editor.handle_input("++++more text++++", cx);
1922        });
1923
1924        cx.foreground().run_until_parked();
1925        editor.update(cx, |editor, cx| {
1926            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1927            ranges.sort_by_key(|range| range.start);
1928            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");
1929            assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document");
1930            assert_eq!(ranges[0].end.line + 1, ranges[1].start.line, "Neighbour requests got on different lines due to the line end");
1931            assert_ne!(ranges[0].end.character, 0, "First query was in the end of the line, not in the beginning");
1932            assert_eq!(ranges[1].start.character, 0, "Second query got pushed into a new line and starts from the beginning");
1933            assert_eq!(ranges[1].end.line, ranges[2].start.line, "Neighbour requests should be on the same line");
1934            assert_eq!(ranges[1].end.character + 1, ranges[2].start.character, "Neighbour request should be concequent");
1935
1936            assert_eq!(lsp_request_count.load(Ordering::SeqCst), 5,
1937                "When scroll not at the edge of a big document, visible part + 2 other parts should be queried for hints");
1938            let expected_layers = vec!["3".to_string(), "4".to_string(), "5".to_string()];
1939            assert_eq!(expected_layers, cached_hint_labels(editor),
1940                "Should have hints from the new LSP response after edit");
1941            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1942            assert_eq!(editor.inlay_hint_cache().version, 5, "Should update the cache for every LSP response with hints added");
1943        });
1944    }
1945
1946    #[gpui::test]
1947    async fn test_multiple_excerpts_large_multibuffer(
1948        deterministic: Arc<Deterministic>,
1949        cx: &mut gpui::TestAppContext,
1950    ) {
1951        init_test(cx, |settings| {
1952            settings.defaults.inlay_hints = Some(InlayHintSettings {
1953                enabled: true,
1954                show_type_hints: true,
1955                show_parameter_hints: true,
1956                show_other_hints: true,
1957            })
1958        });
1959
1960        let mut language = Language::new(
1961            LanguageConfig {
1962                name: "Rust".into(),
1963                path_suffixes: vec!["rs".to_string()],
1964                ..Default::default()
1965            },
1966            Some(tree_sitter_rust::language()),
1967        );
1968        let mut fake_servers = language
1969            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1970                capabilities: lsp::ServerCapabilities {
1971                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1972                    ..Default::default()
1973                },
1974                ..Default::default()
1975            }))
1976            .await;
1977        let language = Arc::new(language);
1978        let fs = FakeFs::new(cx.background());
1979        fs.insert_tree(
1980            "/a",
1981            json!({
1982                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
1983                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
1984            }),
1985        )
1986        .await;
1987        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1988        project.update(cx, |project, _| {
1989            project.languages().add(Arc::clone(&language))
1990        });
1991        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
1992        let worktree_id = workspace.update(cx, |workspace, cx| {
1993            workspace.project().read_with(cx, |project, cx| {
1994                project.worktrees(cx).next().unwrap().read(cx).id()
1995            })
1996        });
1997
1998        let buffer_1 = project
1999            .update(cx, |project, cx| {
2000                project.open_buffer((worktree_id, "main.rs"), cx)
2001            })
2002            .await
2003            .unwrap();
2004        let buffer_2 = project
2005            .update(cx, |project, cx| {
2006                project.open_buffer((worktree_id, "other.rs"), cx)
2007            })
2008            .await
2009            .unwrap();
2010        let multibuffer = cx.add_model(|cx| {
2011            let mut multibuffer = MultiBuffer::new(0);
2012            multibuffer.push_excerpts(
2013                buffer_1.clone(),
2014                [
2015                    ExcerptRange {
2016                        context: Point::new(0, 0)..Point::new(2, 0),
2017                        primary: None,
2018                    },
2019                    ExcerptRange {
2020                        context: Point::new(4, 0)..Point::new(11, 0),
2021                        primary: None,
2022                    },
2023                    ExcerptRange {
2024                        context: Point::new(22, 0)..Point::new(33, 0),
2025                        primary: None,
2026                    },
2027                    ExcerptRange {
2028                        context: Point::new(44, 0)..Point::new(55, 0),
2029                        primary: None,
2030                    },
2031                    ExcerptRange {
2032                        context: Point::new(56, 0)..Point::new(66, 0),
2033                        primary: None,
2034                    },
2035                    ExcerptRange {
2036                        context: Point::new(67, 0)..Point::new(77, 0),
2037                        primary: None,
2038                    },
2039                ],
2040                cx,
2041            );
2042            multibuffer.push_excerpts(
2043                buffer_2.clone(),
2044                [
2045                    ExcerptRange {
2046                        context: Point::new(0, 1)..Point::new(2, 1),
2047                        primary: None,
2048                    },
2049                    ExcerptRange {
2050                        context: Point::new(4, 1)..Point::new(11, 1),
2051                        primary: None,
2052                    },
2053                    ExcerptRange {
2054                        context: Point::new(22, 1)..Point::new(33, 1),
2055                        primary: None,
2056                    },
2057                    ExcerptRange {
2058                        context: Point::new(44, 1)..Point::new(55, 1),
2059                        primary: None,
2060                    },
2061                    ExcerptRange {
2062                        context: Point::new(56, 1)..Point::new(66, 1),
2063                        primary: None,
2064                    },
2065                    ExcerptRange {
2066                        context: Point::new(67, 1)..Point::new(77, 1),
2067                        primary: None,
2068                    },
2069                ],
2070                cx,
2071            );
2072            multibuffer
2073        });
2074
2075        deterministic.run_until_parked();
2076        cx.foreground().run_until_parked();
2077        let (_, editor) =
2078            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
2079        let editor_edited = Arc::new(AtomicBool::new(false));
2080        let fake_server = fake_servers.next().await.unwrap();
2081        let closure_editor_edited = Arc::clone(&editor_edited);
2082        fake_server
2083            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2084                let task_editor_edited = Arc::clone(&closure_editor_edited);
2085                async move {
2086                    let hint_text = if params.text_document.uri
2087                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2088                    {
2089                        "main hint"
2090                    } else if params.text_document.uri
2091                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2092                    {
2093                        "other hint"
2094                    } else {
2095                        panic!("unexpected uri: {:?}", params.text_document.uri);
2096                    };
2097
2098                    // one hint per excerpt
2099                    let positions = [
2100                        lsp::Position::new(0, 2),
2101                        lsp::Position::new(4, 2),
2102                        lsp::Position::new(22, 2),
2103                        lsp::Position::new(44, 2),
2104                        lsp::Position::new(56, 2),
2105                        lsp::Position::new(67, 2),
2106                    ];
2107                    let out_of_range_hint = lsp::InlayHint {
2108                        position: lsp::Position::new(
2109                            params.range.start.line + 99,
2110                            params.range.start.character + 99,
2111                        ),
2112                        label: lsp::InlayHintLabel::String(
2113                            "out of excerpt range, should be ignored".to_string(),
2114                        ),
2115                        kind: None,
2116                        text_edits: None,
2117                        tooltip: None,
2118                        padding_left: None,
2119                        padding_right: None,
2120                        data: None,
2121                    };
2122
2123                    let edited = task_editor_edited.load(Ordering::Acquire);
2124                    Ok(Some(
2125                        std::iter::once(out_of_range_hint)
2126                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2127                                lsp::InlayHint {
2128                                    position,
2129                                    label: lsp::InlayHintLabel::String(format!(
2130                                        "{hint_text}{} #{i}",
2131                                        if edited { "(edited)" } else { "" },
2132                                    )),
2133                                    kind: None,
2134                                    text_edits: None,
2135                                    tooltip: None,
2136                                    padding_left: None,
2137                                    padding_right: None,
2138                                    data: None,
2139                                }
2140                            }))
2141                            .collect(),
2142                    ))
2143                }
2144            })
2145            .next()
2146            .await;
2147        cx.foreground().run_until_parked();
2148
2149        editor.update(cx, |editor, cx| {
2150            let expected_layers = vec![
2151                "main hint #0".to_string(),
2152                "main hint #1".to_string(),
2153                "main hint #2".to_string(),
2154                "main hint #3".to_string(),
2155            ];
2156            assert_eq!(
2157                expected_layers,
2158                cached_hint_labels(editor),
2159                "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2160            );
2161            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2162            assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(), "Every visible excerpt hints should bump the verison");
2163        });
2164
2165        editor.update(cx, |editor, cx| {
2166            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2167                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2168            });
2169            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2170                s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2171            });
2172            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2173                s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
2174            });
2175        });
2176        cx.foreground().run_until_parked();
2177        editor.update(cx, |editor, cx| {
2178            let expected_layers = vec![
2179                "main hint #0".to_string(),
2180                "main hint #1".to_string(),
2181                "main hint #2".to_string(),
2182                "main hint #3".to_string(),
2183                "main hint #4".to_string(),
2184                "main hint #5".to_string(),
2185                "other hint #0".to_string(),
2186                "other hint #1".to_string(),
2187                "other hint #2".to_string(),
2188            ];
2189            assert_eq!(expected_layers, cached_hint_labels(editor),
2190                "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2191            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2192            assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(),
2193                "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
2194        });
2195
2196        editor.update(cx, |editor, cx| {
2197            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2198                s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2199            });
2200        });
2201        cx.foreground().run_until_parked();
2202        let last_scroll_update_version = editor.update(cx, |editor, cx| {
2203            let expected_layers = vec![
2204                "main hint #0".to_string(),
2205                "main hint #1".to_string(),
2206                "main hint #2".to_string(),
2207                "main hint #3".to_string(),
2208                "main hint #4".to_string(),
2209                "main hint #5".to_string(),
2210                "other hint #0".to_string(),
2211                "other hint #1".to_string(),
2212                "other hint #2".to_string(),
2213                "other hint #3".to_string(),
2214                "other hint #4".to_string(),
2215                "other hint #5".to_string(),
2216            ];
2217            assert_eq!(expected_layers, cached_hint_labels(editor),
2218                "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2219            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2220            assert_eq!(editor.inlay_hint_cache().version, expected_layers.len());
2221            expected_layers.len()
2222        });
2223
2224        editor.update(cx, |editor, cx| {
2225            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2226                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2227            });
2228        });
2229        cx.foreground().run_until_parked();
2230        editor.update(cx, |editor, cx| {
2231            let expected_layers = vec![
2232                "main hint #0".to_string(),
2233                "main hint #1".to_string(),
2234                "main hint #2".to_string(),
2235                "main hint #3".to_string(),
2236                "main hint #4".to_string(),
2237                "main hint #5".to_string(),
2238                "other hint #0".to_string(),
2239                "other hint #1".to_string(),
2240                "other hint #2".to_string(),
2241                "other hint #3".to_string(),
2242                "other hint #4".to_string(),
2243                "other hint #5".to_string(),
2244            ];
2245            assert_eq!(expected_layers, cached_hint_labels(editor),
2246                "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2247            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2248            assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
2249        });
2250
2251        editor_edited.store(true, Ordering::Release);
2252        editor.update(cx, |editor, cx| {
2253            editor.change_selections(None, cx, |s| {
2254                s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
2255            });
2256            editor.handle_input("++++more text++++", cx);
2257        });
2258        cx.foreground().run_until_parked();
2259        editor.update(cx, |editor, cx| {
2260            let expected_layers = vec![
2261                "main hint(edited) #0".to_string(),
2262                "main hint(edited) #1".to_string(),
2263                "main hint(edited) #2".to_string(),
2264                "main hint(edited) #3".to_string(),
2265                "main hint(edited) #4".to_string(),
2266                "main hint(edited) #5".to_string(),
2267                "other hint(edited) #0".to_string(),
2268                "other hint(edited) #1".to_string(),
2269            ];
2270            assert_eq!(
2271                expected_layers,
2272                cached_hint_labels(editor),
2273                "After multibuffer edit, editor gets scolled back to the last selection; \
2274all hints should be invalidated and requeried for all of its visible excerpts"
2275            );
2276            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2277            assert_eq!(
2278                editor.inlay_hint_cache().version,
2279                last_scroll_update_version + expected_layers.len() + 1,
2280                "Due to every excerpt having one hint, cache should update per new excerpt received + 1 for outdated hints removal"
2281            );
2282        });
2283    }
2284
2285    #[gpui::test]
2286    async fn test_excerpts_removed(
2287        deterministic: Arc<Deterministic>,
2288        cx: &mut gpui::TestAppContext,
2289    ) {
2290        init_test(cx, |settings| {
2291            settings.defaults.inlay_hints = Some(InlayHintSettings {
2292                enabled: true,
2293                show_type_hints: false,
2294                show_parameter_hints: false,
2295                show_other_hints: false,
2296            })
2297        });
2298
2299        let mut language = Language::new(
2300            LanguageConfig {
2301                name: "Rust".into(),
2302                path_suffixes: vec!["rs".to_string()],
2303                ..Default::default()
2304            },
2305            Some(tree_sitter_rust::language()),
2306        );
2307        let mut fake_servers = language
2308            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2309                capabilities: lsp::ServerCapabilities {
2310                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2311                    ..Default::default()
2312                },
2313                ..Default::default()
2314            }))
2315            .await;
2316        let language = Arc::new(language);
2317        let fs = FakeFs::new(cx.background());
2318        fs.insert_tree(
2319            "/a",
2320            json!({
2321                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2322                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2323            }),
2324        )
2325        .await;
2326        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2327        project.update(cx, |project, _| {
2328            project.languages().add(Arc::clone(&language))
2329        });
2330        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2331        let worktree_id = workspace.update(cx, |workspace, cx| {
2332            workspace.project().read_with(cx, |project, cx| {
2333                project.worktrees(cx).next().unwrap().read(cx).id()
2334            })
2335        });
2336
2337        let buffer_1 = project
2338            .update(cx, |project, cx| {
2339                project.open_buffer((worktree_id, "main.rs"), cx)
2340            })
2341            .await
2342            .unwrap();
2343        let buffer_2 = project
2344            .update(cx, |project, cx| {
2345                project.open_buffer((worktree_id, "other.rs"), cx)
2346            })
2347            .await
2348            .unwrap();
2349        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
2350        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2351            let buffer_1_excerpts = multibuffer.push_excerpts(
2352                buffer_1.clone(),
2353                [ExcerptRange {
2354                    context: Point::new(0, 0)..Point::new(2, 0),
2355                    primary: None,
2356                }],
2357                cx,
2358            );
2359            let buffer_2_excerpts = multibuffer.push_excerpts(
2360                buffer_2.clone(),
2361                [ExcerptRange {
2362                    context: Point::new(0, 1)..Point::new(2, 1),
2363                    primary: None,
2364                }],
2365                cx,
2366            );
2367            (buffer_1_excerpts, buffer_2_excerpts)
2368        });
2369
2370        assert!(!buffer_1_excerpts.is_empty());
2371        assert!(!buffer_2_excerpts.is_empty());
2372
2373        deterministic.run_until_parked();
2374        cx.foreground().run_until_parked();
2375        let (_, editor) =
2376            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
2377        let editor_edited = Arc::new(AtomicBool::new(false));
2378        let fake_server = fake_servers.next().await.unwrap();
2379        let closure_editor_edited = Arc::clone(&editor_edited);
2380        fake_server
2381            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2382                let task_editor_edited = Arc::clone(&closure_editor_edited);
2383                async move {
2384                    let hint_text = if params.text_document.uri
2385                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2386                    {
2387                        "main hint"
2388                    } else if params.text_document.uri
2389                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2390                    {
2391                        "other hint"
2392                    } else {
2393                        panic!("unexpected uri: {:?}", params.text_document.uri);
2394                    };
2395
2396                    let positions = [
2397                        lsp::Position::new(0, 2),
2398                        lsp::Position::new(4, 2),
2399                        lsp::Position::new(22, 2),
2400                        lsp::Position::new(44, 2),
2401                        lsp::Position::new(56, 2),
2402                        lsp::Position::new(67, 2),
2403                    ];
2404                    let out_of_range_hint = lsp::InlayHint {
2405                        position: lsp::Position::new(
2406                            params.range.start.line + 99,
2407                            params.range.start.character + 99,
2408                        ),
2409                        label: lsp::InlayHintLabel::String(
2410                            "out of excerpt range, should be ignored".to_string(),
2411                        ),
2412                        kind: None,
2413                        text_edits: None,
2414                        tooltip: None,
2415                        padding_left: None,
2416                        padding_right: None,
2417                        data: None,
2418                    };
2419
2420                    let edited = task_editor_edited.load(Ordering::Acquire);
2421                    Ok(Some(
2422                        std::iter::once(out_of_range_hint)
2423                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2424                                lsp::InlayHint {
2425                                    position,
2426                                    label: lsp::InlayHintLabel::String(format!(
2427                                        "{hint_text}{} #{i}",
2428                                        if edited { "(edited)" } else { "" },
2429                                    )),
2430                                    kind: None,
2431                                    text_edits: None,
2432                                    tooltip: None,
2433                                    padding_left: None,
2434                                    padding_right: None,
2435                                    data: None,
2436                                }
2437                            }))
2438                            .collect(),
2439                    ))
2440                }
2441            })
2442            .next()
2443            .await;
2444        cx.foreground().run_until_parked();
2445
2446        editor.update(cx, |editor, cx| {
2447            assert_eq!(
2448                vec!["main hint #0".to_string(), "other hint #0".to_string()],
2449                cached_hint_labels(editor),
2450                "Cache should update for both excerpts despite hints display was disabled"
2451            );
2452            assert!(
2453                visible_hint_labels(editor, cx).is_empty(),
2454                "All hints are disabled and should not be shown despite being present in the cache"
2455            );
2456            assert_eq!(
2457                editor.inlay_hint_cache().version,
2458                2,
2459                "Cache should update once per excerpt query"
2460            );
2461        });
2462
2463        editor.update(cx, |editor, cx| {
2464            editor.buffer().update(cx, |multibuffer, cx| {
2465                multibuffer.remove_excerpts(buffer_2_excerpts, cx)
2466            })
2467        });
2468        cx.foreground().run_until_parked();
2469        editor.update(cx, |editor, cx| {
2470            assert_eq!(
2471                vec!["main hint #0".to_string()],
2472                cached_hint_labels(editor),
2473                "For the removed excerpt, should clean corresponding cached hints"
2474            );
2475            assert!(
2476                visible_hint_labels(editor, cx).is_empty(),
2477                "All hints are disabled and should not be shown despite being present in the cache"
2478            );
2479            assert_eq!(
2480                editor.inlay_hint_cache().version,
2481                3,
2482                "Excerpt removal should trigger cache update"
2483            );
2484        });
2485
2486        update_test_language_settings(cx, |settings| {
2487            settings.defaults.inlay_hints = Some(InlayHintSettings {
2488                enabled: true,
2489                show_type_hints: true,
2490                show_parameter_hints: true,
2491                show_other_hints: true,
2492            })
2493        });
2494        cx.foreground().run_until_parked();
2495        editor.update(cx, |editor, cx| {
2496            let expected_hints = vec!["main hint #0".to_string()];
2497            assert_eq!(
2498                expected_hints,
2499                cached_hint_labels(editor),
2500                "Hint display settings change should not change the cache"
2501            );
2502            assert_eq!(
2503                expected_hints,
2504                visible_hint_labels(editor, cx),
2505                "Settings change should make cached hints visible"
2506            );
2507            assert_eq!(
2508                editor.inlay_hint_cache().version,
2509                4,
2510                "Settings change should trigger cache update"
2511            );
2512        });
2513    }
2514
2515    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2516        cx.foreground().forbid_parking();
2517
2518        cx.update(|cx| {
2519            cx.set_global(SettingsStore::test(cx));
2520            theme::init((), cx);
2521            client::init_settings(cx);
2522            language::init(cx);
2523            Project::init_settings(cx);
2524            workspace::init_settings(cx);
2525            crate::init(cx);
2526        });
2527
2528        update_test_language_settings(cx, f);
2529    }
2530
2531    async fn prepare_test_objects(
2532        cx: &mut TestAppContext,
2533    ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
2534        let mut language = Language::new(
2535            LanguageConfig {
2536                name: "Rust".into(),
2537                path_suffixes: vec!["rs".to_string()],
2538                ..Default::default()
2539            },
2540            Some(tree_sitter_rust::language()),
2541        );
2542        let mut fake_servers = language
2543            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2544                capabilities: lsp::ServerCapabilities {
2545                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2546                    ..Default::default()
2547                },
2548                ..Default::default()
2549            }))
2550            .await;
2551
2552        let fs = FakeFs::new(cx.background());
2553        fs.insert_tree(
2554            "/a",
2555            json!({
2556                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
2557                "other.rs": "// Test file",
2558            }),
2559        )
2560        .await;
2561
2562        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2563        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2564        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
2565        let worktree_id = workspace.update(cx, |workspace, cx| {
2566            workspace.project().read_with(cx, |project, cx| {
2567                project.worktrees(cx).next().unwrap().read(cx).id()
2568            })
2569        });
2570
2571        let _buffer = project
2572            .update(cx, |project, cx| {
2573                project.open_local_buffer("/a/main.rs", cx)
2574            })
2575            .await
2576            .unwrap();
2577        cx.foreground().run_until_parked();
2578        cx.foreground().start_waiting();
2579        let fake_server = fake_servers.next().await.unwrap();
2580        let editor = workspace
2581            .update(cx, |workspace, cx| {
2582                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2583            })
2584            .await
2585            .unwrap()
2586            .downcast::<Editor>()
2587            .unwrap();
2588
2589        ("/a/main.rs", editor, fake_server)
2590    }
2591
2592    fn cached_hint_labels(editor: &Editor) -> Vec<String> {
2593        let mut labels = Vec::new();
2594        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
2595            let excerpt_hints = excerpt_hints.read();
2596            for (_, inlay) in excerpt_hints.hints.iter() {
2597                match &inlay.label {
2598                    project::InlayHintLabel::String(s) => labels.push(s.to_string()),
2599                    _ => unreachable!(),
2600                }
2601            }
2602        }
2603
2604        labels.sort();
2605        labels
2606    }
2607
2608    fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
2609        let mut hints = editor
2610            .visible_inlay_hints(cx)
2611            .into_iter()
2612            .map(|hint| hint.text.to_string())
2613            .collect::<Vec<_>>();
2614        hints.sort();
2615        hints
2616    }
2617}