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