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