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    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    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]
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            assert_eq!(
2339                editor.inlay_hint_cache().version,
2340                last_scroll_update_version + expected_layers.len(),
2341                "Due to every excerpt having one hint, cache should update per new excerpt received"
2342            );
2343        });
2344    }
2345
2346    #[gpui::test]
2347    async fn test_excerpts_removed(
2348        deterministic: Arc<Deterministic>,
2349        cx: &mut gpui::TestAppContext,
2350    ) {
2351        init_test(cx, |settings| {
2352            settings.defaults.inlay_hints = Some(InlayHintSettings {
2353                enabled: true,
2354                show_type_hints: false,
2355                show_parameter_hints: false,
2356                show_other_hints: false,
2357            })
2358        });
2359
2360        let mut language = Language::new(
2361            LanguageConfig {
2362                name: "Rust".into(),
2363                path_suffixes: vec!["rs".to_string()],
2364                ..Default::default()
2365            },
2366            Some(tree_sitter_rust::language()),
2367        );
2368        let mut fake_servers = language
2369            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2370                capabilities: lsp::ServerCapabilities {
2371                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2372                    ..Default::default()
2373                },
2374                ..Default::default()
2375            }))
2376            .await;
2377        let language = Arc::new(language);
2378        let fs = FakeFs::new(cx.background());
2379        fs.insert_tree(
2380            "/a",
2381            json!({
2382                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2383                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2384            }),
2385        )
2386        .await;
2387        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2388        project.update(cx, |project, _| {
2389            project.languages().add(Arc::clone(&language))
2390        });
2391        let workspace = cx
2392            .add_window(|cx| Workspace::test_new(project.clone(), cx))
2393            .root(cx);
2394        let worktree_id = workspace.update(cx, |workspace, cx| {
2395            workspace.project().read_with(cx, |project, cx| {
2396                project.worktrees(cx).next().unwrap().read(cx).id()
2397            })
2398        });
2399
2400        let buffer_1 = project
2401            .update(cx, |project, cx| {
2402                project.open_buffer((worktree_id, "main.rs"), cx)
2403            })
2404            .await
2405            .unwrap();
2406        let buffer_2 = project
2407            .update(cx, |project, cx| {
2408                project.open_buffer((worktree_id, "other.rs"), cx)
2409            })
2410            .await
2411            .unwrap();
2412        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
2413        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2414            let buffer_1_excerpts = multibuffer.push_excerpts(
2415                buffer_1.clone(),
2416                [ExcerptRange {
2417                    context: Point::new(0, 0)..Point::new(2, 0),
2418                    primary: None,
2419                }],
2420                cx,
2421            );
2422            let buffer_2_excerpts = multibuffer.push_excerpts(
2423                buffer_2.clone(),
2424                [ExcerptRange {
2425                    context: Point::new(0, 1)..Point::new(2, 1),
2426                    primary: None,
2427                }],
2428                cx,
2429            );
2430            (buffer_1_excerpts, buffer_2_excerpts)
2431        });
2432
2433        assert!(!buffer_1_excerpts.is_empty());
2434        assert!(!buffer_2_excerpts.is_empty());
2435
2436        deterministic.run_until_parked();
2437        cx.foreground().run_until_parked();
2438        let editor = cx
2439            .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
2440            .root(cx);
2441        let editor_edited = Arc::new(AtomicBool::new(false));
2442        let fake_server = fake_servers.next().await.unwrap();
2443        let closure_editor_edited = Arc::clone(&editor_edited);
2444        fake_server
2445            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2446                let task_editor_edited = Arc::clone(&closure_editor_edited);
2447                async move {
2448                    let hint_text = if params.text_document.uri
2449                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2450                    {
2451                        "main hint"
2452                    } else if params.text_document.uri
2453                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2454                    {
2455                        "other hint"
2456                    } else {
2457                        panic!("unexpected uri: {:?}", params.text_document.uri);
2458                    };
2459
2460                    let positions = [
2461                        lsp::Position::new(0, 2),
2462                        lsp::Position::new(4, 2),
2463                        lsp::Position::new(22, 2),
2464                        lsp::Position::new(44, 2),
2465                        lsp::Position::new(56, 2),
2466                        lsp::Position::new(67, 2),
2467                    ];
2468                    let out_of_range_hint = lsp::InlayHint {
2469                        position: lsp::Position::new(
2470                            params.range.start.line + 99,
2471                            params.range.start.character + 99,
2472                        ),
2473                        label: lsp::InlayHintLabel::String(
2474                            "out of excerpt range, should be ignored".to_string(),
2475                        ),
2476                        kind: None,
2477                        text_edits: None,
2478                        tooltip: None,
2479                        padding_left: None,
2480                        padding_right: None,
2481                        data: None,
2482                    };
2483
2484                    let edited = task_editor_edited.load(Ordering::Acquire);
2485                    Ok(Some(
2486                        std::iter::once(out_of_range_hint)
2487                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2488                                lsp::InlayHint {
2489                                    position,
2490                                    label: lsp::InlayHintLabel::String(format!(
2491                                        "{hint_text}{} #{i}",
2492                                        if edited { "(edited)" } else { "" },
2493                                    )),
2494                                    kind: None,
2495                                    text_edits: None,
2496                                    tooltip: None,
2497                                    padding_left: None,
2498                                    padding_right: None,
2499                                    data: None,
2500                                }
2501                            }))
2502                            .collect(),
2503                    ))
2504                }
2505            })
2506            .next()
2507            .await;
2508        cx.foreground().run_until_parked();
2509
2510        editor.update(cx, |editor, cx| {
2511            assert_eq!(
2512                vec!["main hint #0".to_string(), "other hint #0".to_string()],
2513                cached_hint_labels(editor),
2514                "Cache should update for both excerpts despite hints display was disabled"
2515            );
2516            assert!(
2517                visible_hint_labels(editor, cx).is_empty(),
2518                "All hints are disabled and should not be shown despite being present in the cache"
2519            );
2520            assert_eq!(
2521                editor.inlay_hint_cache().version,
2522                2,
2523                "Cache should update once per excerpt query"
2524            );
2525        });
2526
2527        editor.update(cx, |editor, cx| {
2528            editor.buffer().update(cx, |multibuffer, cx| {
2529                multibuffer.remove_excerpts(buffer_2_excerpts, cx)
2530            })
2531        });
2532        cx.foreground().run_until_parked();
2533        editor.update(cx, |editor, cx| {
2534            assert_eq!(
2535                vec!["main hint #0".to_string()],
2536                cached_hint_labels(editor),
2537                "For the removed excerpt, should clean corresponding cached hints"
2538            );
2539            assert!(
2540                visible_hint_labels(editor, cx).is_empty(),
2541                "All hints are disabled and should not be shown despite being present in the cache"
2542            );
2543            assert_eq!(
2544                editor.inlay_hint_cache().version,
2545                2,
2546                "Excerpt removal should trigger a cache update"
2547            );
2548        });
2549
2550        update_test_language_settings(cx, |settings| {
2551            settings.defaults.inlay_hints = Some(InlayHintSettings {
2552                enabled: true,
2553                show_type_hints: true,
2554                show_parameter_hints: true,
2555                show_other_hints: true,
2556            })
2557        });
2558        cx.foreground().run_until_parked();
2559        editor.update(cx, |editor, cx| {
2560            let expected_hints = vec!["main hint #0".to_string()];
2561            assert_eq!(
2562                expected_hints,
2563                cached_hint_labels(editor),
2564                "Hint display settings change should not change the cache"
2565            );
2566            assert_eq!(
2567                expected_hints,
2568                visible_hint_labels(editor, cx),
2569                "Settings change should make cached hints visible"
2570            );
2571            assert_eq!(
2572                editor.inlay_hint_cache().version,
2573                3,
2574                "Settings change should trigger a cache update"
2575            );
2576        });
2577    }
2578
2579    #[gpui::test]
2580    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
2581        init_test(cx, |settings| {
2582            settings.defaults.inlay_hints = Some(InlayHintSettings {
2583                enabled: true,
2584                show_type_hints: true,
2585                show_parameter_hints: true,
2586                show_other_hints: true,
2587            })
2588        });
2589
2590        let mut language = Language::new(
2591            LanguageConfig {
2592                name: "Rust".into(),
2593                path_suffixes: vec!["rs".to_string()],
2594                ..Default::default()
2595            },
2596            Some(tree_sitter_rust::language()),
2597        );
2598        let mut fake_servers = language
2599            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2600                capabilities: lsp::ServerCapabilities {
2601                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2602                    ..Default::default()
2603                },
2604                ..Default::default()
2605            }))
2606            .await;
2607        let fs = FakeFs::new(cx.background());
2608        fs.insert_tree(
2609            "/a",
2610            json!({
2611                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
2612                "other.rs": "// Test file",
2613            }),
2614        )
2615        .await;
2616        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2617        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2618        let workspace = cx
2619            .add_window(|cx| Workspace::test_new(project.clone(), cx))
2620            .root(cx);
2621        let worktree_id = workspace.update(cx, |workspace, cx| {
2622            workspace.project().read_with(cx, |project, cx| {
2623                project.worktrees(cx).next().unwrap().read(cx).id()
2624            })
2625        });
2626
2627        let _buffer = project
2628            .update(cx, |project, cx| {
2629                project.open_local_buffer("/a/main.rs", cx)
2630            })
2631            .await
2632            .unwrap();
2633        cx.foreground().run_until_parked();
2634        cx.foreground().start_waiting();
2635        let fake_server = fake_servers.next().await.unwrap();
2636        let editor = workspace
2637            .update(cx, |workspace, cx| {
2638                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2639            })
2640            .await
2641            .unwrap()
2642            .downcast::<Editor>()
2643            .unwrap();
2644        let lsp_request_count = Arc::new(AtomicU32::new(0));
2645        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2646        fake_server
2647            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2648                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2649                async move {
2650                    assert_eq!(
2651                        params.text_document.uri,
2652                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
2653                    );
2654                    let query_start = params.range.start;
2655                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
2656                    Ok(Some(vec![lsp::InlayHint {
2657                        position: query_start,
2658                        label: lsp::InlayHintLabel::String(i.to_string()),
2659                        kind: None,
2660                        text_edits: None,
2661                        tooltip: None,
2662                        padding_left: None,
2663                        padding_right: None,
2664                        data: None,
2665                    }]))
2666                }
2667            })
2668            .next()
2669            .await;
2670
2671        cx.foreground().run_until_parked();
2672        editor.update(cx, |editor, cx| {
2673            editor.change_selections(None, cx, |s| {
2674                s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
2675            })
2676        });
2677        cx.foreground().run_until_parked();
2678        editor.update(cx, |editor, cx| {
2679            let expected_layers = vec!["1".to_string()];
2680            assert_eq!(expected_layers, cached_hint_labels(editor));
2681            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2682            assert_eq!(editor.inlay_hint_cache().version, 1);
2683        });
2684    }
2685
2686    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2687        cx.foreground().forbid_parking();
2688
2689        cx.update(|cx| {
2690            cx.set_global(SettingsStore::test(cx));
2691            theme::init((), cx);
2692            client::init_settings(cx);
2693            language::init(cx);
2694            Project::init_settings(cx);
2695            workspace::init_settings(cx);
2696            crate::init(cx);
2697        });
2698
2699        update_test_language_settings(cx, f);
2700    }
2701
2702    async fn prepare_test_objects(
2703        cx: &mut TestAppContext,
2704    ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
2705        let mut language = Language::new(
2706            LanguageConfig {
2707                name: "Rust".into(),
2708                path_suffixes: vec!["rs".to_string()],
2709                ..Default::default()
2710            },
2711            Some(tree_sitter_rust::language()),
2712        );
2713        let mut fake_servers = language
2714            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2715                capabilities: lsp::ServerCapabilities {
2716                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2717                    ..Default::default()
2718                },
2719                ..Default::default()
2720            }))
2721            .await;
2722
2723        let fs = FakeFs::new(cx.background());
2724        fs.insert_tree(
2725            "/a",
2726            json!({
2727                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
2728                "other.rs": "// Test file",
2729            }),
2730        )
2731        .await;
2732
2733        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2734        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2735        let workspace = cx
2736            .add_window(|cx| Workspace::test_new(project.clone(), cx))
2737            .root(cx);
2738        let worktree_id = workspace.update(cx, |workspace, cx| {
2739            workspace.project().read_with(cx, |project, cx| {
2740                project.worktrees(cx).next().unwrap().read(cx).id()
2741            })
2742        });
2743
2744        let _buffer = project
2745            .update(cx, |project, cx| {
2746                project.open_local_buffer("/a/main.rs", cx)
2747            })
2748            .await
2749            .unwrap();
2750        cx.foreground().run_until_parked();
2751        cx.foreground().start_waiting();
2752        let fake_server = fake_servers.next().await.unwrap();
2753        let editor = workspace
2754            .update(cx, |workspace, cx| {
2755                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2756            })
2757            .await
2758            .unwrap()
2759            .downcast::<Editor>()
2760            .unwrap();
2761
2762        ("/a/main.rs", editor, fake_server)
2763    }
2764
2765    fn cached_hint_labels(editor: &Editor) -> Vec<String> {
2766        let mut labels = Vec::new();
2767        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
2768            let excerpt_hints = excerpt_hints.read();
2769            for (_, inlay) in excerpt_hints.hints.iter() {
2770                match &inlay.label {
2771                    project::InlayHintLabel::String(s) => labels.push(s.to_string()),
2772                    _ => unreachable!(),
2773                }
2774            }
2775        }
2776
2777        labels.sort();
2778        labels
2779    }
2780
2781    fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
2782        let mut hints = editor
2783            .visible_inlay_hints(cx)
2784            .into_iter()
2785            .map(|hint| hint.text.to_string())
2786            .collect::<Vec<_>>();
2787        hints.sort();
2788        hints
2789    }
2790}