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