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