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
  30struct UpdateTask {
  31    current: (InvalidationStrategy, SpawnedTask),
  32    pending_refresh: Option<SpawnedTask>,
  33}
  34
  35struct SpawnedTask {
  36    version: usize,
  37    is_running_rx: smol::channel::Receiver<()>,
  38    _task: Task<()>,
  39}
  40
  41#[derive(Debug)]
  42pub struct CachedExcerptHints {
  43    version: usize,
  44    buffer_version: Global,
  45    pub hints: Vec<(InlayId, InlayHint)>,
  46}
  47
  48#[derive(Debug, Clone, Copy)]
  49struct ExcerptQuery {
  50    buffer_id: u64,
  51    excerpt_id: ExcerptId,
  52    dimensions: ExcerptDimensions,
  53    cache_version: usize,
  54    invalidate: InvalidationStrategy,
  55}
  56
  57#[derive(Debug, Clone, Copy)]
  58struct ExcerptDimensions {
  59    excerpt_range_start: language::Anchor,
  60    excerpt_range_end: language::Anchor,
  61    excerpt_visible_range_start: language::Anchor,
  62    excerpt_visible_range_end: language::Anchor,
  63}
  64
  65impl ExcerptQuery {
  66    fn hints_fetch_ranges(&self, buffer: &BufferSnapshot) -> HintFetchRanges {
  67        let visible_range =
  68            self.dimensions.excerpt_visible_range_start..self.dimensions.excerpt_visible_range_end;
  69        let mut other_ranges = Vec::new();
  70        if self
  71            .dimensions
  72            .excerpt_range_start
  73            .cmp(&self.dimensions.excerpt_visible_range_start, buffer)
  74            .is_lt()
  75        {
  76            let mut end = self.dimensions.excerpt_visible_range_start;
  77            end.offset -= 1;
  78            other_ranges.push(self.dimensions.excerpt_range_start..end);
  79        }
  80        if self
  81            .dimensions
  82            .excerpt_range_end
  83            .cmp(&self.dimensions.excerpt_visible_range_end, buffer)
  84            .is_gt()
  85        {
  86            let mut start = self.dimensions.excerpt_visible_range_end;
  87            start.offset += 1;
  88            other_ranges.push(start..self.dimensions.excerpt_range_end);
  89        }
  90
  91        HintFetchRanges {
  92            visible_range,
  93            other_ranges: other_ranges.into_iter().map(|range| range).collect(),
  94        }
  95    }
  96}
  97
  98impl UpdateTask {
  99    fn new(invalidation_strategy: InvalidationStrategy, spawned_task: SpawnedTask) -> Self {
 100        Self {
 101            current: (invalidation_strategy, spawned_task),
 102            pending_refresh: None,
 103        }
 104    }
 105
 106    fn is_running(&self) -> bool {
 107        !self.current.1.is_running_rx.is_closed()
 108            || self
 109                .pending_refresh
 110                .as_ref()
 111                .map_or(false, |task| !task.is_running_rx.is_closed())
 112    }
 113
 114    fn cache_version(&self) -> usize {
 115        self.current.1.version
 116    }
 117
 118    fn invalidation_strategy(&self) -> InvalidationStrategy {
 119        self.current.0
 120    }
 121}
 122
 123#[derive(Debug, Clone, Copy)]
 124pub enum InvalidationStrategy {
 125    Forced,
 126    OnConflict,
 127    None,
 128}
 129
 130#[derive(Debug, Default)]
 131pub struct InlaySplice {
 132    pub to_remove: Vec<InlayId>,
 133    pub to_insert: Vec<(Anchor, InlayId, InlayHint)>,
 134}
 135
 136#[derive(Debug)]
 137struct ExcerptHintsUpdate {
 138    excerpt_id: ExcerptId,
 139    cache_version: usize,
 140    remove_from_visible: Vec<InlayId>,
 141    remove_from_cache: HashSet<InlayId>,
 142    add_to_cache: HashSet<InlayHint>,
 143}
 144
 145impl InlayHintCache {
 146    pub fn new(inlay_hint_settings: InlayHintSettings) -> Self {
 147        Self {
 148            allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
 149            enabled: inlay_hint_settings.enabled,
 150            hints: HashMap::default(),
 151            update_tasks: HashMap::default(),
 152            version: 0,
 153        }
 154    }
 155
 156    pub fn update_settings(
 157        &mut self,
 158        multi_buffer: &ModelHandle<MultiBuffer>,
 159        new_hint_settings: InlayHintSettings,
 160        visible_hints: Vec<Inlay>,
 161        cx: &mut ViewContext<Editor>,
 162    ) -> ControlFlow<Option<InlaySplice>> {
 163        let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
 164        match (self.enabled, new_hint_settings.enabled) {
 165            (false, false) => {
 166                self.allowed_hint_kinds = new_allowed_hint_kinds;
 167                ControlFlow::Break(None)
 168            }
 169            (true, true) => {
 170                if new_allowed_hint_kinds == self.allowed_hint_kinds {
 171                    ControlFlow::Break(None)
 172                } else {
 173                    let new_splice = self.new_allowed_hint_kinds_splice(
 174                        multi_buffer,
 175                        &visible_hints,
 176                        &new_allowed_hint_kinds,
 177                        cx,
 178                    );
 179                    if new_splice.is_some() {
 180                        self.version += 1;
 181                        self.update_tasks.clear();
 182                        self.allowed_hint_kinds = new_allowed_hint_kinds;
 183                    }
 184                    ControlFlow::Break(new_splice)
 185                }
 186            }
 187            (true, false) => {
 188                self.enabled = new_hint_settings.enabled;
 189                self.allowed_hint_kinds = new_allowed_hint_kinds;
 190                if self.hints.is_empty() {
 191                    ControlFlow::Break(None)
 192                } else {
 193                    self.clear();
 194                    ControlFlow::Break(Some(InlaySplice {
 195                        to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(),
 196                        to_insert: Vec::new(),
 197                    }))
 198                }
 199            }
 200            (false, true) => {
 201                self.enabled = new_hint_settings.enabled;
 202                self.allowed_hint_kinds = new_allowed_hint_kinds;
 203                ControlFlow::Continue(())
 204            }
 205        }
 206    }
 207
 208    pub fn refresh_inlay_hints(
 209        &mut self,
 210        mut excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Range<usize>)>,
 211        invalidate: InvalidationStrategy,
 212        cx: &mut ViewContext<Editor>,
 213    ) {
 214        if !self.enabled {
 215            return;
 216        }
 217        let update_tasks = &mut self.update_tasks;
 218        let invalidate_cache = matches!(
 219            invalidate,
 220            InvalidationStrategy::Forced | InvalidationStrategy::OnConflict
 221        );
 222        if invalidate_cache {
 223            update_tasks
 224                .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
 225        }
 226        let cache_version = self.version;
 227        excerpts_to_query.retain(|visible_excerpt_id, _| {
 228            match update_tasks.entry(*visible_excerpt_id) {
 229                hash_map::Entry::Occupied(o) => match o.get().cache_version().cmp(&cache_version) {
 230                    cmp::Ordering::Less => true,
 231                    cmp::Ordering::Equal => invalidate_cache,
 232                    cmp::Ordering::Greater => false,
 233                },
 234                hash_map::Entry::Vacant(_) => true,
 235            }
 236        });
 237
 238        cx.spawn(|editor, mut cx| async move {
 239            editor
 240                .update(&mut cx, |editor, cx| {
 241                    spawn_new_update_tasks(editor, excerpts_to_query, invalidate, cache_version, cx)
 242                })
 243                .ok();
 244        })
 245        .detach();
 246    }
 247
 248    fn new_allowed_hint_kinds_splice(
 249        &self,
 250        multi_buffer: &ModelHandle<MultiBuffer>,
 251        visible_hints: &[Inlay],
 252        new_kinds: &HashSet<Option<InlayHintKind>>,
 253        cx: &mut ViewContext<Editor>,
 254    ) -> Option<InlaySplice> {
 255        let old_kinds = &self.allowed_hint_kinds;
 256        if new_kinds == old_kinds {
 257            return None;
 258        }
 259
 260        let mut to_remove = Vec::new();
 261        let mut to_insert = Vec::new();
 262        let mut shown_hints_to_remove = visible_hints.iter().fold(
 263            HashMap::<ExcerptId, Vec<(Anchor, InlayId)>>::default(),
 264            |mut current_hints, inlay| {
 265                current_hints
 266                    .entry(inlay.position.excerpt_id)
 267                    .or_default()
 268                    .push((inlay.position, inlay.id));
 269                current_hints
 270            },
 271        );
 272
 273        let multi_buffer = multi_buffer.read(cx);
 274        let multi_buffer_snapshot = multi_buffer.snapshot(cx);
 275
 276        for (excerpt_id, excerpt_cached_hints) in &self.hints {
 277            let shown_excerpt_hints_to_remove =
 278                shown_hints_to_remove.entry(*excerpt_id).or_default();
 279            let excerpt_cached_hints = excerpt_cached_hints.read();
 280            let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable();
 281            shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
 282                let Some(buffer) = shown_anchor
 283                    .buffer_id
 284                    .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false };
 285                let buffer_snapshot = buffer.read(cx).snapshot();
 286                loop {
 287                    match excerpt_cache.peek() {
 288                        Some((cached_hint_id, cached_hint)) => {
 289                            if cached_hint_id == shown_hint_id {
 290                                excerpt_cache.next();
 291                                return !new_kinds.contains(&cached_hint.kind);
 292                            }
 293
 294                            match cached_hint
 295                                .position
 296                                .cmp(&shown_anchor.text_anchor, &buffer_snapshot)
 297                            {
 298                                cmp::Ordering::Less | cmp::Ordering::Equal => {
 299                                    if !old_kinds.contains(&cached_hint.kind)
 300                                        && new_kinds.contains(&cached_hint.kind)
 301                                    {
 302                                        to_insert.push((
 303                                            multi_buffer_snapshot.anchor_in_excerpt(
 304                                                *excerpt_id,
 305                                                cached_hint.position,
 306                                            ),
 307                                            *cached_hint_id,
 308                                            cached_hint.clone(),
 309                                        ));
 310                                    }
 311                                    excerpt_cache.next();
 312                                }
 313                                cmp::Ordering::Greater => return true,
 314                            }
 315                        }
 316                        None => return true,
 317                    }
 318                }
 319            });
 320
 321            for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache {
 322                let cached_hint_kind = maybe_missed_cached_hint.kind;
 323                if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
 324                    to_insert.push((
 325                        multi_buffer_snapshot
 326                            .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
 327                        *cached_hint_id,
 328                        maybe_missed_cached_hint.clone(),
 329                    ));
 330                }
 331            }
 332        }
 333
 334        to_remove.extend(
 335            shown_hints_to_remove
 336                .into_values()
 337                .flatten()
 338                .map(|(_, hint_id)| hint_id),
 339        );
 340        if to_remove.is_empty() && to_insert.is_empty() {
 341            None
 342        } else {
 343            Some(InlaySplice {
 344                to_remove,
 345                to_insert,
 346            })
 347        }
 348    }
 349
 350    fn clear(&mut self) {
 351        self.version += 1;
 352        self.update_tasks.clear();
 353        self.hints.clear();
 354    }
 355}
 356
 357fn spawn_new_update_tasks(
 358    editor: &mut Editor,
 359    excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Range<usize>)>,
 360    invalidation_strategy: InvalidationStrategy,
 361    update_cache_version: usize,
 362    cx: &mut ViewContext<'_, '_, Editor>,
 363) {
 364    let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
 365    for (excerpt_id, (buffer_handle, excerpt_visible_range)) in excerpts_to_query {
 366        if !excerpt_visible_range.is_empty() {
 367            let buffer = buffer_handle.read(cx);
 368            let buffer_snapshot = buffer.snapshot();
 369            let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
 370            let cache_is_empty = match &cached_excerpt_hints {
 371                Some(cached_excerpt_hints) => {
 372                    let new_task_buffer_version = buffer_snapshot.version();
 373                    let cached_excerpt_hints = cached_excerpt_hints.read();
 374                    let cached_buffer_version = &cached_excerpt_hints.buffer_version;
 375                    if cached_excerpt_hints.version > update_cache_version
 376                        || cached_buffer_version.changed_since(new_task_buffer_version)
 377                    {
 378                        return;
 379                    }
 380                    if !new_task_buffer_version.changed_since(&cached_buffer_version)
 381                        && !matches!(invalidation_strategy, InvalidationStrategy::Forced)
 382                    {
 383                        return;
 384                    }
 385
 386                    cached_excerpt_hints.hints.is_empty()
 387                }
 388                None => true,
 389            };
 390
 391            let buffer_id = buffer.remote_id();
 392            let excerpt_visible_range_start = buffer.anchor_before(excerpt_visible_range.start);
 393            let excerpt_visible_range_end = buffer.anchor_after(excerpt_visible_range.end);
 394
 395            let (multi_buffer_snapshot, full_excerpt_range) =
 396                editor.buffer.update(cx, |multi_buffer, cx| {
 397                    let multi_buffer_snapshot = multi_buffer.snapshot(cx);
 398                    (
 399                        multi_buffer_snapshot,
 400                        multi_buffer
 401                            .excerpts_for_buffer(&buffer_handle, cx)
 402                            .into_iter()
 403                            .find(|(id, _)| id == &excerpt_id)
 404                            .map(|(_, range)| range.context),
 405                    )
 406                });
 407
 408            if let Some(full_excerpt_range) = full_excerpt_range {
 409                let query = ExcerptQuery {
 410                    buffer_id,
 411                    excerpt_id,
 412                    dimensions: ExcerptDimensions {
 413                        excerpt_range_start: full_excerpt_range.start,
 414                        excerpt_range_end: full_excerpt_range.end,
 415                        excerpt_visible_range_start,
 416                        excerpt_visible_range_end,
 417                    },
 418                    cache_version: update_cache_version,
 419                    invalidate: invalidation_strategy,
 420                };
 421
 422                let new_update_task = |previous_task| {
 423                    new_update_task(
 424                        query,
 425                        multi_buffer_snapshot,
 426                        buffer_snapshot,
 427                        Arc::clone(&visible_hints),
 428                        cached_excerpt_hints,
 429                        previous_task,
 430                        cx,
 431                    )
 432                };
 433                match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
 434                    hash_map::Entry::Occupied(mut o) => {
 435                        let update_task = o.get_mut();
 436                        if update_task.is_running() {
 437                            match (update_task.invalidation_strategy(), invalidation_strategy) {
 438                                (InvalidationStrategy::Forced, _)
 439                                | (_, InvalidationStrategy::OnConflict) => {
 440                                    o.insert(UpdateTask::new(
 441                                        invalidation_strategy,
 442                                        new_update_task(None),
 443                                    ));
 444                                }
 445                                (_, InvalidationStrategy::Forced) => {
 446                                    if cache_is_empty {
 447                                        o.insert(UpdateTask::new(
 448                                            invalidation_strategy,
 449                                            new_update_task(None),
 450                                        ));
 451                                    } else if update_task.pending_refresh.is_none() {
 452                                        update_task.pending_refresh = Some(new_update_task(Some(
 453                                            update_task.current.1.is_running_rx.clone(),
 454                                        )));
 455                                    }
 456                                }
 457                                _ => {}
 458                            }
 459                        } else {
 460                            o.insert(UpdateTask::new(
 461                                invalidation_strategy,
 462                                new_update_task(None),
 463                            ));
 464                        }
 465                    }
 466                    hash_map::Entry::Vacant(v) => {
 467                        v.insert(UpdateTask::new(
 468                            invalidation_strategy,
 469                            new_update_task(None),
 470                        ));
 471                    }
 472                }
 473            }
 474        }
 475    }
 476}
 477
 478fn new_update_task(
 479    query: ExcerptQuery,
 480    multi_buffer_snapshot: MultiBufferSnapshot,
 481    buffer_snapshot: BufferSnapshot,
 482    visible_hints: Arc<Vec<Inlay>>,
 483    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
 484    task_before_refresh: Option<smol::channel::Receiver<()>>,
 485    cx: &mut ViewContext<'_, '_, Editor>,
 486) -> SpawnedTask {
 487    let hints_fetch_tasks = query.hints_fetch_ranges(&buffer_snapshot);
 488    let (is_running_tx, is_running_rx) = smol::channel::bounded(1);
 489    let is_refresh_task = task_before_refresh.is_some();
 490    let _task = cx.spawn(|editor, cx| async move {
 491        let _is_running_tx = is_running_tx;
 492        if let Some(task_before_refresh) = task_before_refresh {
 493            task_before_refresh.recv().await.ok();
 494        }
 495        let create_update_task = |range| {
 496            fetch_and_update_hints(
 497                editor.clone(),
 498                multi_buffer_snapshot.clone(),
 499                buffer_snapshot.clone(),
 500                Arc::clone(&visible_hints),
 501                cached_excerpt_hints.as_ref().map(Arc::clone),
 502                query,
 503                range,
 504                cx.clone(),
 505            )
 506        };
 507
 508        if is_refresh_task {
 509            let visible_range_has_updates =
 510                match create_update_task(hints_fetch_tasks.visible_range).await {
 511                    Ok(updated) => updated,
 512                    Err(e) => {
 513                        error!("inlay hint visible range update task failed: {e:#}");
 514                        return;
 515                    }
 516                };
 517
 518            if visible_range_has_updates {
 519                let other_update_results = futures::future::join_all(
 520                    hints_fetch_tasks
 521                        .other_ranges
 522                        .into_iter()
 523                        .map(create_update_task),
 524                )
 525                .await;
 526
 527                for result in other_update_results {
 528                    if let Err(e) = result {
 529                        error!("inlay hint update task failed: {e:#}");
 530                        return;
 531                    }
 532                }
 533            }
 534        } else {
 535            let task_update_results = futures::future::join_all(
 536                std::iter::once(hints_fetch_tasks.visible_range)
 537                    .chain(hints_fetch_tasks.other_ranges.into_iter())
 538                    .map(create_update_task),
 539            )
 540            .await;
 541
 542            for result in task_update_results {
 543                if let Err(e) = result {
 544                    error!("inlay hint update task failed: {e:#}");
 545                }
 546            }
 547        }
 548    });
 549
 550    SpawnedTask {
 551        version: query.cache_version,
 552        _task,
 553        is_running_rx,
 554    }
 555}
 556
 557async fn fetch_and_update_hints(
 558    editor: gpui::WeakViewHandle<Editor>,
 559    multi_buffer_snapshot: MultiBufferSnapshot,
 560    buffer_snapshot: BufferSnapshot,
 561    visible_hints: Arc<Vec<Inlay>>,
 562    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
 563    query: ExcerptQuery,
 564    fetch_range: Range<language::Anchor>,
 565    mut cx: gpui::AsyncAppContext,
 566) -> anyhow::Result<bool> {
 567    let inlay_hints_fetch_task = editor
 568        .update(&mut cx, |editor, cx| {
 569            editor
 570                .buffer()
 571                .read(cx)
 572                .buffer(query.buffer_id)
 573                .and_then(|buffer| {
 574                    let project = editor.project.as_ref()?;
 575                    Some(project.update(cx, |project, cx| {
 576                        project.inlay_hints(buffer, fetch_range.clone(), cx)
 577                    }))
 578                })
 579        })
 580        .ok()
 581        .flatten();
 582    let mut update_happened = false;
 583    let Some(inlay_hints_fetch_task) = inlay_hints_fetch_task else { return Ok(update_happened) };
 584
 585    let new_hints = inlay_hints_fetch_task
 586        .await
 587        .context("inlay hint fetch task")?;
 588    let background_task_buffer_snapshot = buffer_snapshot.clone();
 589    let backround_fetch_range = fetch_range.clone();
 590    if let Some(new_update) = cx
 591        .background()
 592        .spawn(async move {
 593            calculate_hint_updates(
 594                query,
 595                backround_fetch_range,
 596                new_hints,
 597                &background_task_buffer_snapshot,
 598                cached_excerpt_hints,
 599                &visible_hints,
 600            )
 601        })
 602        .await
 603    {
 604        update_happened = !new_update.add_to_cache.is_empty()
 605            || !new_update.remove_from_cache.is_empty()
 606            || !new_update.remove_from_visible.is_empty();
 607        editor
 608            .update(&mut cx, |editor, cx| {
 609                let cached_excerpt_hints = editor
 610                    .inlay_hint_cache
 611                    .hints
 612                    .entry(new_update.excerpt_id)
 613                    .or_insert_with(|| {
 614                        Arc::new(RwLock::new(CachedExcerptHints {
 615                            version: new_update.cache_version,
 616                            buffer_version: buffer_snapshot.version().clone(),
 617                            hints: Vec::new(),
 618                        }))
 619                    });
 620                let mut cached_excerpt_hints = cached_excerpt_hints.write();
 621                match new_update.cache_version.cmp(&cached_excerpt_hints.version) {
 622                    cmp::Ordering::Less => return,
 623                    cmp::Ordering::Greater | cmp::Ordering::Equal => {
 624                        cached_excerpt_hints.version = new_update.cache_version;
 625                    }
 626                }
 627                cached_excerpt_hints
 628                    .hints
 629                    .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
 630                cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
 631                editor.inlay_hint_cache.version += 1;
 632
 633                let mut splice = InlaySplice {
 634                    to_remove: new_update.remove_from_visible,
 635                    to_insert: Vec::new(),
 636                };
 637
 638                for new_hint in new_update.add_to_cache {
 639                    let new_hint_position = multi_buffer_snapshot
 640                        .anchor_in_excerpt(query.excerpt_id, new_hint.position);
 641                    let new_inlay_id = InlayId::Hint(post_inc(&mut editor.next_inlay_id));
 642                    if editor
 643                        .inlay_hint_cache
 644                        .allowed_hint_kinds
 645                        .contains(&new_hint.kind)
 646                    {
 647                        splice
 648                            .to_insert
 649                            .push((new_hint_position, new_inlay_id, new_hint.clone()));
 650                    }
 651
 652                    cached_excerpt_hints.hints.push((new_inlay_id, new_hint));
 653                }
 654
 655                cached_excerpt_hints
 656                    .hints
 657                    .sort_by(|(_, hint_a), (_, hint_b)| {
 658                        hint_a.position.cmp(&hint_b.position, &buffer_snapshot)
 659                    });
 660                drop(cached_excerpt_hints);
 661
 662                let InlaySplice {
 663                    to_remove,
 664                    to_insert,
 665                } = splice;
 666                if !to_remove.is_empty() || !to_insert.is_empty() {
 667                    editor.splice_inlay_hints(to_remove, to_insert, cx)
 668                }
 669            })
 670            .ok();
 671    }
 672
 673    Ok(update_happened)
 674}
 675
 676fn calculate_hint_updates(
 677    query: ExcerptQuery,
 678    fetch_range: Range<language::Anchor>,
 679    new_excerpt_hints: Vec<InlayHint>,
 680    buffer_snapshot: &BufferSnapshot,
 681    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
 682    visible_hints: &[Inlay],
 683) -> Option<ExcerptHintsUpdate> {
 684    let mut add_to_cache: HashSet<InlayHint> = HashSet::default();
 685    let mut excerpt_hints_to_persist = HashMap::default();
 686    for new_hint in new_excerpt_hints {
 687        if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) {
 688            continue;
 689        }
 690        let missing_from_cache = match &cached_excerpt_hints {
 691            Some(cached_excerpt_hints) => {
 692                let cached_excerpt_hints = cached_excerpt_hints.read();
 693                match cached_excerpt_hints.hints.binary_search_by(|probe| {
 694                    probe.1.position.cmp(&new_hint.position, buffer_snapshot)
 695                }) {
 696                    Ok(ix) => {
 697                        let (cached_inlay_id, cached_hint) = &cached_excerpt_hints.hints[ix];
 698                        if cached_hint == &new_hint {
 699                            excerpt_hints_to_persist.insert(*cached_inlay_id, cached_hint.kind);
 700                            false
 701                        } else {
 702                            true
 703                        }
 704                    }
 705                    Err(_) => true,
 706                }
 707            }
 708            None => true,
 709        };
 710        if missing_from_cache {
 711            add_to_cache.insert(new_hint);
 712        }
 713    }
 714
 715    let mut remove_from_visible = Vec::new();
 716    let mut remove_from_cache = HashSet::default();
 717    if matches!(
 718        query.invalidate,
 719        InvalidationStrategy::Forced | InvalidationStrategy::OnConflict
 720    ) {
 721        remove_from_visible.extend(
 722            visible_hints
 723                .iter()
 724                .filter(|hint| hint.position.excerpt_id == query.excerpt_id)
 725                .filter(|hint| {
 726                    contains_position(&fetch_range, hint.position.text_anchor, buffer_snapshot)
 727                })
 728                .filter(|hint| {
 729                    fetch_range
 730                        .start
 731                        .cmp(&hint.position.text_anchor, buffer_snapshot)
 732                        .is_le()
 733                        && fetch_range
 734                            .end
 735                            .cmp(&hint.position.text_anchor, buffer_snapshot)
 736                            .is_ge()
 737                })
 738                .map(|inlay_hint| inlay_hint.id)
 739                .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
 740        );
 741
 742        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
 743            let cached_excerpt_hints = cached_excerpt_hints.read();
 744            remove_from_cache.extend(
 745                cached_excerpt_hints
 746                    .hints
 747                    .iter()
 748                    .filter(|(cached_inlay_id, _)| {
 749                        !excerpt_hints_to_persist.contains_key(cached_inlay_id)
 750                    })
 751                    .filter(|(_, cached_hint)| {
 752                        fetch_range
 753                            .start
 754                            .cmp(&cached_hint.position, buffer_snapshot)
 755                            .is_le()
 756                            && fetch_range
 757                                .end
 758                                .cmp(&cached_hint.position, buffer_snapshot)
 759                                .is_ge()
 760                    })
 761                    .map(|(cached_inlay_id, _)| *cached_inlay_id),
 762            );
 763        }
 764    }
 765
 766    if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
 767        None
 768    } else {
 769        Some(ExcerptHintsUpdate {
 770            cache_version: query.cache_version,
 771            excerpt_id: query.excerpt_id,
 772            remove_from_visible,
 773            remove_from_cache,
 774            add_to_cache,
 775        })
 776    }
 777}
 778
 779struct HintFetchRanges {
 780    visible_range: Range<language::Anchor>,
 781    other_ranges: Vec<Range<language::Anchor>>,
 782}
 783
 784fn contains_position(
 785    range: &Range<language::Anchor>,
 786    position: language::Anchor,
 787    buffer_snapshot: &BufferSnapshot,
 788) -> bool {
 789    range.start.cmp(&position, buffer_snapshot).is_le()
 790        && range.end.cmp(&position, buffer_snapshot).is_ge()
 791}
 792
 793#[cfg(test)]
 794mod tests {
 795    use std::sync::atomic::{AtomicU32, Ordering};
 796
 797    use crate::{serde_json::json, InlayHintSettings};
 798    use futures::StreamExt;
 799    use gpui::{TestAppContext, ViewHandle};
 800    use language::{
 801        language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
 802    };
 803    use lsp::FakeLanguageServer;
 804    use project::{FakeFs, Project};
 805    use settings::SettingsStore;
 806    use workspace::Workspace;
 807
 808    use crate::editor_tests::update_test_settings;
 809
 810    use super::*;
 811
 812    #[gpui::test]
 813    async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
 814        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
 815        init_test(cx, |settings| {
 816            settings.defaults.inlay_hints = Some(InlayHintSettings {
 817                enabled: true,
 818                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
 819                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
 820                show_other_hints: allowed_hint_kinds.contains(&None),
 821            })
 822        });
 823        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
 824        let lsp_request_count = Arc::new(AtomicU32::new(0));
 825        fake_server
 826            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
 827                let task_lsp_request_count = Arc::clone(&lsp_request_count);
 828                async move {
 829                    assert_eq!(
 830                        params.text_document.uri,
 831                        lsp::Url::from_file_path(file_with_hints).unwrap(),
 832                    );
 833                    let current_call_id =
 834                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
 835                    let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
 836                    for _ in 0..2 {
 837                        let mut i = current_call_id;
 838                        loop {
 839                            new_hints.push(lsp::InlayHint {
 840                                position: lsp::Position::new(0, i),
 841                                label: lsp::InlayHintLabel::String(i.to_string()),
 842                                kind: None,
 843                                text_edits: None,
 844                                tooltip: None,
 845                                padding_left: None,
 846                                padding_right: None,
 847                                data: None,
 848                            });
 849                            if i == 0 {
 850                                break;
 851                            }
 852                            i -= 1;
 853                        }
 854                    }
 855
 856                    Ok(Some(new_hints))
 857                }
 858            })
 859            .next()
 860            .await;
 861        cx.foreground().finish_waiting();
 862        cx.foreground().run_until_parked();
 863        let mut edits_made = 1;
 864        editor.update(cx, |editor, cx| {
 865            let expected_layers = vec!["0".to_string()];
 866            assert_eq!(
 867                expected_layers,
 868                cached_hint_labels(editor),
 869                "Should get its first hints when opening the editor"
 870            );
 871            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 872            let inlay_cache = editor.inlay_hint_cache();
 873            assert_eq!(
 874                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 875                "Cache should use editor settings to get the allowed hint kinds"
 876            );
 877            assert_eq!(
 878                inlay_cache.version, edits_made,
 879                "The editor update the cache version after every cache/view change"
 880            );
 881        });
 882
 883        editor.update(cx, |editor, cx| {
 884            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
 885            editor.handle_input("some change", cx);
 886            edits_made += 1;
 887        });
 888        cx.foreground().run_until_parked();
 889        editor.update(cx, |editor, cx| {
 890            let expected_layers = vec!["0".to_string(), "1".to_string()];
 891            assert_eq!(
 892                expected_layers,
 893                cached_hint_labels(editor),
 894                "Should get new hints after an edit"
 895            );
 896            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 897            let inlay_cache = editor.inlay_hint_cache();
 898            assert_eq!(
 899                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 900                "Cache should use editor settings to get the allowed hint kinds"
 901            );
 902            assert_eq!(
 903                inlay_cache.version, edits_made,
 904                "The editor update the cache version after every cache/view change"
 905            );
 906        });
 907
 908        fake_server
 909            .request::<lsp::request::InlayHintRefreshRequest>(())
 910            .await
 911            .expect("inlay refresh request failed");
 912        edits_made += 1;
 913        cx.foreground().run_until_parked();
 914        editor.update(cx, |editor, cx| {
 915            let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()];
 916            assert_eq!(
 917                expected_layers,
 918                cached_hint_labels(editor),
 919                "Should get new hints after hint refresh/ request"
 920            );
 921            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 922            let inlay_cache = editor.inlay_hint_cache();
 923            assert_eq!(
 924                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 925                "Cache should use editor settings to get the allowed hint kinds"
 926            );
 927            assert_eq!(
 928                inlay_cache.version, edits_made,
 929                "The editor update the cache version after every cache/view change"
 930            );
 931        });
 932    }
 933
 934    async fn prepare_test_objects(
 935        cx: &mut TestAppContext,
 936    ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
 937        let mut language = Language::new(
 938            LanguageConfig {
 939                name: "Rust".into(),
 940                path_suffixes: vec!["rs".to_string()],
 941                ..Default::default()
 942            },
 943            Some(tree_sitter_rust::language()),
 944        );
 945        let mut fake_servers = language
 946            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 947                capabilities: lsp::ServerCapabilities {
 948                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
 949                    ..Default::default()
 950                },
 951                ..Default::default()
 952            }))
 953            .await;
 954
 955        let fs = FakeFs::new(cx.background());
 956        fs.insert_tree(
 957            "/a",
 958            json!({
 959                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
 960                "other.rs": "// Test file",
 961            }),
 962        )
 963        .await;
 964
 965        let project = Project::test(fs, ["/a".as_ref()], cx).await;
 966        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
 967        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
 968        let worktree_id = workspace.update(cx, |workspace, cx| {
 969            workspace.project().read_with(cx, |project, cx| {
 970                project.worktrees(cx).next().unwrap().read(cx).id()
 971            })
 972        });
 973
 974        cx.foreground().start_waiting();
 975        let editor = workspace
 976            .update(cx, |workspace, cx| {
 977                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
 978            })
 979            .await
 980            .unwrap()
 981            .downcast::<Editor>()
 982            .unwrap();
 983
 984        let fake_server = fake_servers.next().await.unwrap();
 985
 986        ("/a/main.rs", editor, fake_server)
 987    }
 988
 989    #[gpui::test]
 990    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
 991        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
 992        init_test(cx, |settings| {
 993            settings.defaults.inlay_hints = Some(InlayHintSettings {
 994                enabled: true,
 995                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
 996                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
 997                show_other_hints: allowed_hint_kinds.contains(&None),
 998            })
 999        });
1000        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1001        let lsp_request_count = Arc::new(AtomicU32::new(0));
1002        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1003        fake_server
1004            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1005                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1006                async move {
1007                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1008                    assert_eq!(
1009                        params.text_document.uri,
1010                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1011                    );
1012                    Ok(Some(vec![
1013                        lsp::InlayHint {
1014                            position: lsp::Position::new(0, 1),
1015                            label: lsp::InlayHintLabel::String("type hint".to_string()),
1016                            kind: Some(lsp::InlayHintKind::TYPE),
1017                            text_edits: None,
1018                            tooltip: None,
1019                            padding_left: None,
1020                            padding_right: None,
1021                            data: None,
1022                        },
1023                        lsp::InlayHint {
1024                            position: lsp::Position::new(0, 2),
1025                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1026                            kind: Some(lsp::InlayHintKind::PARAMETER),
1027                            text_edits: None,
1028                            tooltip: None,
1029                            padding_left: None,
1030                            padding_right: None,
1031                            data: None,
1032                        },
1033                        lsp::InlayHint {
1034                            position: lsp::Position::new(0, 3),
1035                            label: lsp::InlayHintLabel::String("other hint".to_string()),
1036                            kind: None,
1037                            text_edits: None,
1038                            tooltip: None,
1039                            padding_left: None,
1040                            padding_right: None,
1041                            data: None,
1042                        },
1043                    ]))
1044                }
1045            })
1046            .next()
1047            .await;
1048        cx.foreground().finish_waiting();
1049        cx.foreground().run_until_parked();
1050
1051        let mut edits_made = 1;
1052        editor.update(cx, |editor, cx| {
1053            assert_eq!(
1054                lsp_request_count.load(Ordering::Relaxed),
1055                1,
1056                "Should query new hints once"
1057            );
1058            assert_eq!(
1059                vec![
1060                    "type hint".to_string(),
1061                    "parameter hint".to_string(),
1062                    "other hint".to_string()
1063                ],
1064                cached_hint_labels(editor),
1065                "Should get its first hints when opening the editor"
1066            );
1067            assert_eq!(
1068                vec!["type hint".to_string(), "other hint".to_string()],
1069                visible_hint_labels(editor, cx)
1070            );
1071            let inlay_cache = editor.inlay_hint_cache();
1072            assert_eq!(
1073                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1074                "Cache should use editor settings to get the allowed hint kinds"
1075            );
1076            assert_eq!(
1077                inlay_cache.version, edits_made,
1078                "The editor update the cache version after every cache/view change"
1079            );
1080        });
1081
1082        fake_server
1083            .request::<lsp::request::InlayHintRefreshRequest>(())
1084            .await
1085            .expect("inlay refresh request failed");
1086        cx.foreground().run_until_parked();
1087        editor.update(cx, |editor, cx| {
1088            assert_eq!(
1089                lsp_request_count.load(Ordering::Relaxed),
1090                2,
1091                "Should load new hints twice"
1092            );
1093            assert_eq!(
1094                vec![
1095                    "type hint".to_string(),
1096                    "parameter hint".to_string(),
1097                    "other hint".to_string()
1098                ],
1099                cached_hint_labels(editor),
1100                "Cached hints should not change due to allowed hint kinds settings update"
1101            );
1102            assert_eq!(
1103                vec!["type hint".to_string(), "other hint".to_string()],
1104                visible_hint_labels(editor, cx)
1105            );
1106            let inlay_cache = editor.inlay_hint_cache();
1107            assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds);
1108            assert_eq!(
1109                inlay_cache.version, edits_made,
1110                "Should not update cache version due to new loaded hints being the same"
1111            );
1112        });
1113
1114        for (new_allowed_hint_kinds, expected_visible_hints) in [
1115            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1116            (
1117                HashSet::from_iter([Some(InlayHintKind::Type)]),
1118                vec!["type hint".to_string()],
1119            ),
1120            (
1121                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1122                vec!["parameter hint".to_string()],
1123            ),
1124            (
1125                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1126                vec!["type hint".to_string(), "other hint".to_string()],
1127            ),
1128            (
1129                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1130                vec!["parameter hint".to_string(), "other hint".to_string()],
1131            ),
1132            (
1133                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1134                vec!["type hint".to_string(), "parameter hint".to_string()],
1135            ),
1136            (
1137                HashSet::from_iter([
1138                    None,
1139                    Some(InlayHintKind::Type),
1140                    Some(InlayHintKind::Parameter),
1141                ]),
1142                vec![
1143                    "type hint".to_string(),
1144                    "parameter hint".to_string(),
1145                    "other hint".to_string(),
1146                ],
1147            ),
1148        ] {
1149            edits_made += 1;
1150            update_test_settings(cx, |settings| {
1151                settings.defaults.inlay_hints = Some(InlayHintSettings {
1152                    enabled: true,
1153                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1154                    show_parameter_hints: new_allowed_hint_kinds
1155                        .contains(&Some(InlayHintKind::Parameter)),
1156                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1157                })
1158            });
1159            cx.foreground().run_until_parked();
1160            editor.update(cx, |editor, cx| {
1161                assert_eq!(
1162                    lsp_request_count.load(Ordering::Relaxed),
1163                    2,
1164                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1165                );
1166                assert_eq!(
1167                    vec![
1168                        "type hint".to_string(),
1169                        "parameter hint".to_string(),
1170                        "other hint".to_string(),
1171                    ],
1172                    cached_hint_labels(editor),
1173                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1174                );
1175                assert_eq!(
1176                    expected_visible_hints,
1177                    visible_hint_labels(editor, cx),
1178                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1179                );
1180                let inlay_cache = editor.inlay_hint_cache();
1181                assert_eq!(
1182                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1183                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1184                );
1185                assert_eq!(
1186                    inlay_cache.version, edits_made,
1187                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1188                );
1189            });
1190        }
1191
1192        edits_made += 1;
1193        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1194        update_test_settings(cx, |settings| {
1195            settings.defaults.inlay_hints = Some(InlayHintSettings {
1196                enabled: false,
1197                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1198                show_parameter_hints: another_allowed_hint_kinds
1199                    .contains(&Some(InlayHintKind::Parameter)),
1200                show_other_hints: another_allowed_hint_kinds.contains(&None),
1201            })
1202        });
1203        cx.foreground().run_until_parked();
1204        editor.update(cx, |editor, cx| {
1205            assert_eq!(
1206                lsp_request_count.load(Ordering::Relaxed),
1207                2,
1208                "Should not load new hints when hints got disabled"
1209            );
1210            assert!(
1211                cached_hint_labels(editor).is_empty(),
1212                "Should clear the cache when hints got disabled"
1213            );
1214            assert!(
1215                visible_hint_labels(editor, cx).is_empty(),
1216                "Should clear visible hints when hints got disabled"
1217            );
1218            let inlay_cache = editor.inlay_hint_cache();
1219            assert_eq!(
1220                inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1221                "Should update its allowed hint kinds even when hints got disabled"
1222            );
1223            assert_eq!(
1224                inlay_cache.version, edits_made,
1225                "The editor should update the cache version after hints got disabled"
1226            );
1227        });
1228
1229        fake_server
1230            .request::<lsp::request::InlayHintRefreshRequest>(())
1231            .await
1232            .expect("inlay refresh request failed");
1233        cx.foreground().run_until_parked();
1234        editor.update(cx, |editor, cx| {
1235            assert_eq!(
1236                lsp_request_count.load(Ordering::Relaxed),
1237                2,
1238                "Should not load new hints when they got disabled"
1239            );
1240            assert!(cached_hint_labels(editor).is_empty());
1241            assert!(visible_hint_labels(editor, cx).is_empty());
1242            let inlay_cache = editor.inlay_hint_cache();
1243            assert_eq!(inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds);
1244            assert_eq!(
1245                inlay_cache.version, edits_made,
1246                "The editor should not update the cache version after /refresh query without updates"
1247            );
1248        });
1249
1250        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1251        edits_made += 1;
1252        update_test_settings(cx, |settings| {
1253            settings.defaults.inlay_hints = Some(InlayHintSettings {
1254                enabled: true,
1255                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1256                show_parameter_hints: final_allowed_hint_kinds
1257                    .contains(&Some(InlayHintKind::Parameter)),
1258                show_other_hints: final_allowed_hint_kinds.contains(&None),
1259            })
1260        });
1261        cx.foreground().run_until_parked();
1262        editor.update(cx, |editor, cx| {
1263            assert_eq!(
1264                lsp_request_count.load(Ordering::Relaxed),
1265                3,
1266                "Should query for new hints when they got reenabled"
1267            );
1268            assert_eq!(
1269                vec![
1270                    "type hint".to_string(),
1271                    "parameter hint".to_string(),
1272                    "other hint".to_string(),
1273                ],
1274                cached_hint_labels(editor),
1275                "Should get its cached hints fully repopulated after the hints got reenabled"
1276            );
1277            assert_eq!(
1278                vec!["parameter hint".to_string()],
1279                visible_hint_labels(editor, cx),
1280                "Should get its visible hints repopulated and filtered after the h"
1281            );
1282            let inlay_cache = editor.inlay_hint_cache();
1283            assert_eq!(
1284                inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1285                "Cache should update editor settings when hints got reenabled"
1286            );
1287            assert_eq!(
1288                inlay_cache.version, edits_made,
1289                "Cache should update its version after hints got reenabled"
1290            );
1291        });
1292
1293        fake_server
1294            .request::<lsp::request::InlayHintRefreshRequest>(())
1295            .await
1296            .expect("inlay refresh request failed");
1297        cx.foreground().run_until_parked();
1298        editor.update(cx, |editor, cx| {
1299            assert_eq!(
1300                lsp_request_count.load(Ordering::Relaxed),
1301                4,
1302                "Should query for new hints again"
1303            );
1304            assert_eq!(
1305                vec![
1306                    "type hint".to_string(),
1307                    "parameter hint".to_string(),
1308                    "other hint".to_string(),
1309                ],
1310                cached_hint_labels(editor),
1311            );
1312            assert_eq!(
1313                vec!["parameter hint".to_string()],
1314                visible_hint_labels(editor, cx),
1315            );
1316            let inlay_cache = editor.inlay_hint_cache();
1317            assert_eq!(inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,);
1318            assert_eq!(inlay_cache.version, edits_made);
1319        });
1320    }
1321
1322    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
1323        cx.foreground().forbid_parking();
1324
1325        cx.update(|cx| {
1326            cx.set_global(SettingsStore::test(cx));
1327            theme::init((), cx);
1328            client::init_settings(cx);
1329            language::init(cx);
1330            Project::init_settings(cx);
1331            workspace::init_settings(cx);
1332            crate::init(cx);
1333        });
1334
1335        update_test_settings(cx, f);
1336    }
1337
1338    fn cached_hint_labels(editor: &Editor) -> Vec<String> {
1339        let mut labels = Vec::new();
1340        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
1341            let excerpt_hints = excerpt_hints.read();
1342            for (_, inlay) in excerpt_hints.hints.iter() {
1343                match &inlay.label {
1344                    project::InlayHintLabel::String(s) => labels.push(s.to_string()),
1345                    _ => unreachable!(),
1346                }
1347            }
1348        }
1349        labels
1350    }
1351
1352    fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
1353        editor
1354            .visible_inlay_hints(cx)
1355            .into_iter()
1356            .map(|hint| hint.text.to_string())
1357            .collect()
1358    }
1359}