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                .filter(|hint| {
 663                    contains_position(&fetch_range, hint.position.text_anchor, buffer_snapshot)
 664                })
 665                .filter(|hint| {
 666                    fetch_range
 667                        .start
 668                        .cmp(&hint.position.text_anchor, buffer_snapshot)
 669                        .is_le()
 670                        && fetch_range
 671                            .end
 672                            .cmp(&hint.position.text_anchor, buffer_snapshot)
 673                            .is_ge()
 674                })
 675                .map(|inlay_hint| inlay_hint.id)
 676                .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
 677        );
 678
 679        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
 680            let cached_excerpt_hints = cached_excerpt_hints.read();
 681            remove_from_cache.extend(
 682                cached_excerpt_hints
 683                    .hints
 684                    .iter()
 685                    .filter(|(cached_inlay_id, _)| {
 686                        !excerpt_hints_to_persist.contains_key(cached_inlay_id)
 687                    })
 688                    .filter(|(_, cached_hint)| {
 689                        fetch_range
 690                            .start
 691                            .cmp(&cached_hint.position, buffer_snapshot)
 692                            .is_le()
 693                            && fetch_range
 694                                .end
 695                                .cmp(&cached_hint.position, buffer_snapshot)
 696                                .is_ge()
 697                    })
 698                    .map(|(cached_inlay_id, _)| *cached_inlay_id),
 699            );
 700        }
 701    }
 702
 703    if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
 704        None
 705    } else {
 706        Some(ExcerptHintsUpdate {
 707            excerpt_id: query.excerpt_id,
 708            remove_from_visible,
 709            remove_from_cache,
 710            add_to_cache,
 711        })
 712    }
 713}
 714
 715fn contains_position(
 716    range: &Range<language::Anchor>,
 717    position: language::Anchor,
 718    buffer_snapshot: &BufferSnapshot,
 719) -> bool {
 720    range.start.cmp(&position, buffer_snapshot).is_le()
 721        && range.end.cmp(&position, buffer_snapshot).is_ge()
 722}
 723
 724fn apply_hint_update(
 725    editor: &mut Editor,
 726    new_update: ExcerptHintsUpdate,
 727    query: ExcerptQuery,
 728    buffer_snapshot: BufferSnapshot,
 729    multi_buffer_snapshot: MultiBufferSnapshot,
 730    cx: &mut ViewContext<'_, '_, Editor>,
 731) {
 732    let cached_excerpt_hints = editor
 733        .inlay_hint_cache
 734        .hints
 735        .entry(new_update.excerpt_id)
 736        .or_insert_with(|| {
 737            Arc::new(RwLock::new(CachedExcerptHints {
 738                version: query.cache_version,
 739                buffer_version: buffer_snapshot.version().clone(),
 740                buffer_id: query.buffer_id,
 741                hints: Vec::new(),
 742            }))
 743        });
 744    let mut cached_excerpt_hints = cached_excerpt_hints.write();
 745    match query.cache_version.cmp(&cached_excerpt_hints.version) {
 746        cmp::Ordering::Less => return,
 747        cmp::Ordering::Greater | cmp::Ordering::Equal => {
 748            cached_excerpt_hints.version = query.cache_version;
 749        }
 750    }
 751
 752    let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
 753    cached_excerpt_hints
 754        .hints
 755        .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id));
 756    let mut splice = InlaySplice {
 757        to_remove: new_update.remove_from_visible,
 758        to_insert: Vec::new(),
 759    };
 760    for new_hint in new_update.add_to_cache {
 761        let cached_hints = &mut cached_excerpt_hints.hints;
 762        let insert_position = match cached_hints
 763            .binary_search_by(|probe| probe.1.position.cmp(&new_hint.position, &buffer_snapshot))
 764        {
 765            Ok(i) => {
 766                if cached_hints[i].1.text() == new_hint.text() {
 767                    None
 768                } else {
 769                    Some(i)
 770                }
 771            }
 772            Err(i) => Some(i),
 773        };
 774
 775        if let Some(insert_position) = insert_position {
 776            let new_inlay_id = post_inc(&mut editor.next_inlay_id);
 777            if editor
 778                .inlay_hint_cache
 779                .allowed_hint_kinds
 780                .contains(&new_hint.kind)
 781            {
 782                let new_hint_position =
 783                    multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
 784                splice
 785                    .to_insert
 786                    .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
 787            }
 788            cached_hints.insert(insert_position, (InlayId::Hint(new_inlay_id), new_hint));
 789            cached_inlays_changed = true;
 790        }
 791    }
 792    cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
 793    drop(cached_excerpt_hints);
 794
 795    if query.invalidate.should_invalidate() {
 796        let mut outdated_excerpt_caches = HashSet::default();
 797        for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
 798            let excerpt_hints = excerpt_hints.read();
 799            if excerpt_hints.buffer_id == query.buffer_id
 800                && excerpt_id != &query.excerpt_id
 801                && buffer_snapshot
 802                    .version()
 803                    .changed_since(&excerpt_hints.buffer_version)
 804            {
 805                outdated_excerpt_caches.insert(*excerpt_id);
 806                splice
 807                    .to_remove
 808                    .extend(excerpt_hints.hints.iter().map(|(id, _)| id));
 809            }
 810        }
 811        cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
 812        editor
 813            .inlay_hint_cache
 814            .hints
 815            .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
 816    }
 817
 818    let InlaySplice {
 819        to_remove,
 820        to_insert,
 821    } = splice;
 822    let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
 823    if cached_inlays_changed || displayed_inlays_changed {
 824        editor.inlay_hint_cache.version += 1;
 825    }
 826    if displayed_inlays_changed {
 827        editor.splice_inlay_hints(to_remove, to_insert, cx)
 828    }
 829}
 830
 831#[cfg(test)]
 832mod tests {
 833    use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
 834
 835    use crate::{
 836        scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
 837        serde_json::json,
 838        ExcerptRange,
 839    };
 840    use futures::StreamExt;
 841    use gpui::{executor::Deterministic, TestAppContext, ViewHandle};
 842    use itertools::Itertools;
 843    use language::{
 844        language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig,
 845    };
 846    use lsp::FakeLanguageServer;
 847    use parking_lot::Mutex;
 848    use project::{FakeFs, Project};
 849    use settings::SettingsStore;
 850    use text::{Point, ToPoint};
 851    use workspace::Workspace;
 852
 853    use crate::editor_tests::update_test_language_settings;
 854
 855    use super::*;
 856
 857    #[gpui::test]
 858    async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
 859        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
 860        init_test(cx, |settings| {
 861            settings.defaults.inlay_hints = Some(InlayHintSettings {
 862                enabled: true,
 863                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
 864                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
 865                show_other_hints: allowed_hint_kinds.contains(&None),
 866            })
 867        });
 868
 869        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
 870        let lsp_request_count = Arc::new(AtomicU32::new(0));
 871        fake_server
 872            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
 873                let task_lsp_request_count = Arc::clone(&lsp_request_count);
 874                async move {
 875                    assert_eq!(
 876                        params.text_document.uri,
 877                        lsp::Url::from_file_path(file_with_hints).unwrap(),
 878                    );
 879                    let current_call_id =
 880                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
 881                    let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
 882                    for _ in 0..2 {
 883                        let mut i = current_call_id;
 884                        loop {
 885                            new_hints.push(lsp::InlayHint {
 886                                position: lsp::Position::new(0, i),
 887                                label: lsp::InlayHintLabel::String(i.to_string()),
 888                                kind: None,
 889                                text_edits: None,
 890                                tooltip: None,
 891                                padding_left: None,
 892                                padding_right: None,
 893                                data: None,
 894                            });
 895                            if i == 0 {
 896                                break;
 897                            }
 898                            i -= 1;
 899                        }
 900                    }
 901
 902                    Ok(Some(new_hints))
 903                }
 904            })
 905            .next()
 906            .await;
 907        cx.foreground().run_until_parked();
 908
 909        let mut edits_made = 1;
 910        editor.update(cx, |editor, cx| {
 911            let expected_layers = vec!["0".to_string()];
 912            assert_eq!(
 913                expected_layers,
 914                cached_hint_labels(editor),
 915                "Should get its first hints when opening the editor"
 916            );
 917            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 918            let inlay_cache = editor.inlay_hint_cache();
 919            assert_eq!(
 920                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 921                "Cache should use editor settings to get the allowed hint kinds"
 922            );
 923            assert_eq!(
 924                inlay_cache.version, edits_made,
 925                "The editor update the cache version after every cache/view change"
 926            );
 927        });
 928
 929        editor.update(cx, |editor, cx| {
 930            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
 931            editor.handle_input("some change", cx);
 932            edits_made += 1;
 933        });
 934        cx.foreground().run_until_parked();
 935        editor.update(cx, |editor, cx| {
 936            let expected_layers = vec!["0".to_string(), "1".to_string()];
 937            assert_eq!(
 938                expected_layers,
 939                cached_hint_labels(editor),
 940                "Should get new hints after an edit"
 941            );
 942            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 943            let inlay_cache = editor.inlay_hint_cache();
 944            assert_eq!(
 945                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 946                "Cache should use editor settings to get the allowed hint kinds"
 947            );
 948            assert_eq!(
 949                inlay_cache.version, edits_made,
 950                "The editor update the cache version after every cache/view change"
 951            );
 952        });
 953
 954        fake_server
 955            .request::<lsp::request::InlayHintRefreshRequest>(())
 956            .await
 957            .expect("inlay refresh request failed");
 958        edits_made += 1;
 959        cx.foreground().run_until_parked();
 960        editor.update(cx, |editor, cx| {
 961            let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()];
 962            assert_eq!(
 963                expected_layers,
 964                cached_hint_labels(editor),
 965                "Should get new hints after hint refresh/ request"
 966            );
 967            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
 968            let inlay_cache = editor.inlay_hint_cache();
 969            assert_eq!(
 970                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
 971                "Cache should use editor settings to get the allowed hint kinds"
 972            );
 973            assert_eq!(
 974                inlay_cache.version, edits_made,
 975                "The editor update the cache version after every cache/view change"
 976            );
 977        });
 978    }
 979
 980    #[gpui::test]
 981    async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
 982        init_test(cx, |settings| {
 983            settings.defaults.inlay_hints = Some(InlayHintSettings {
 984                enabled: true,
 985                show_type_hints: true,
 986                show_parameter_hints: true,
 987                show_other_hints: true,
 988            })
 989        });
 990
 991        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
 992        let lsp_request_count = Arc::new(AtomicU32::new(0));
 993        fake_server
 994            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
 995                let task_lsp_request_count = Arc::clone(&lsp_request_count);
 996                async move {
 997                    assert_eq!(
 998                        params.text_document.uri,
 999                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1000                    );
1001                    let current_call_id =
1002                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1003                    Ok(Some(vec![lsp::InlayHint {
1004                        position: lsp::Position::new(0, current_call_id),
1005                        label: lsp::InlayHintLabel::String(current_call_id.to_string()),
1006                        kind: None,
1007                        text_edits: None,
1008                        tooltip: None,
1009                        padding_left: None,
1010                        padding_right: None,
1011                        data: None,
1012                    }]))
1013                }
1014            })
1015            .next()
1016            .await;
1017        cx.foreground().run_until_parked();
1018
1019        let mut edits_made = 1;
1020        editor.update(cx, |editor, cx| {
1021            let expected_layers = vec!["0".to_string()];
1022            assert_eq!(
1023                expected_layers,
1024                cached_hint_labels(editor),
1025                "Should get its first hints when opening the editor"
1026            );
1027            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1028            assert_eq!(
1029                editor.inlay_hint_cache().version,
1030                edits_made,
1031                "The editor update the cache version after every cache/view change"
1032            );
1033        });
1034
1035        let progress_token = "test_progress_token";
1036        fake_server
1037            .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1038                token: lsp::ProgressToken::String(progress_token.to_string()),
1039            })
1040            .await
1041            .expect("work done progress create request failed");
1042        cx.foreground().run_until_parked();
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::Begin(
1046                lsp::WorkDoneProgressBegin::default(),
1047            )),
1048        });
1049        cx.foreground().run_until_parked();
1050
1051        editor.update(cx, |editor, cx| {
1052            let expected_layers = vec!["0".to_string()];
1053            assert_eq!(
1054                expected_layers,
1055                cached_hint_labels(editor),
1056                "Should not update hints while the work task is running"
1057            );
1058            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1059            assert_eq!(
1060                editor.inlay_hint_cache().version,
1061                edits_made,
1062                "Should not update the cache while the work task is running"
1063            );
1064        });
1065
1066        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1067            token: lsp::ProgressToken::String(progress_token.to_string()),
1068            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
1069                lsp::WorkDoneProgressEnd::default(),
1070            )),
1071        });
1072        cx.foreground().run_until_parked();
1073
1074        edits_made += 1;
1075        editor.update(cx, |editor, cx| {
1076            let expected_layers = vec!["1".to_string()];
1077            assert_eq!(
1078                expected_layers,
1079                cached_hint_labels(editor),
1080                "New hints should be queried after the work task is done"
1081            );
1082            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1083            assert_eq!(
1084                editor.inlay_hint_cache().version,
1085                edits_made,
1086                "Cache version should udpate once after the work task is done"
1087            );
1088        });
1089    }
1090
1091    #[gpui::test]
1092    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1093        init_test(cx, |settings| {
1094            settings.defaults.inlay_hints = Some(InlayHintSettings {
1095                enabled: true,
1096                show_type_hints: true,
1097                show_parameter_hints: true,
1098                show_other_hints: true,
1099            })
1100        });
1101
1102        let fs = FakeFs::new(cx.background());
1103        fs.insert_tree(
1104                    "/a",
1105                    json!({
1106                        "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1107                        "other.md": "Test md file with some text",
1108                    }),
1109                )
1110                .await;
1111        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1112        let workspace = cx
1113            .add_window(|cx| Workspace::test_new(project.clone(), cx))
1114            .root(cx);
1115        let worktree_id = workspace.update(cx, |workspace, cx| {
1116            workspace.project().read_with(cx, |project, cx| {
1117                project.worktrees(cx).next().unwrap().read(cx).id()
1118            })
1119        });
1120
1121        let mut rs_fake_servers = None;
1122        let mut md_fake_servers = None;
1123        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1124            let mut language = Language::new(
1125                LanguageConfig {
1126                    name: name.into(),
1127                    path_suffixes: vec![path_suffix.to_string()],
1128                    ..Default::default()
1129                },
1130                Some(tree_sitter_rust::language()),
1131            );
1132            let fake_servers = language
1133                .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1134                    name,
1135                    capabilities: lsp::ServerCapabilities {
1136                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1137                        ..Default::default()
1138                    },
1139                    ..Default::default()
1140                }))
1141                .await;
1142            match name {
1143                "Rust" => rs_fake_servers = Some(fake_servers),
1144                "Markdown" => md_fake_servers = Some(fake_servers),
1145                _ => unreachable!(),
1146            }
1147            project.update(cx, |project, _| {
1148                project.languages().add(Arc::new(language));
1149            });
1150        }
1151
1152        let _rs_buffer = project
1153            .update(cx, |project, cx| {
1154                project.open_local_buffer("/a/main.rs", cx)
1155            })
1156            .await
1157            .unwrap();
1158        cx.foreground().run_until_parked();
1159        cx.foreground().start_waiting();
1160        let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1161        let rs_editor = workspace
1162            .update(cx, |workspace, cx| {
1163                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1164            })
1165            .await
1166            .unwrap()
1167            .downcast::<Editor>()
1168            .unwrap();
1169        let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1170        rs_fake_server
1171            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1172                let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
1173                async move {
1174                    assert_eq!(
1175                        params.text_document.uri,
1176                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1177                    );
1178                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1179                    Ok(Some(vec![lsp::InlayHint {
1180                        position: lsp::Position::new(0, i),
1181                        label: lsp::InlayHintLabel::String(i.to_string()),
1182                        kind: None,
1183                        text_edits: None,
1184                        tooltip: None,
1185                        padding_left: None,
1186                        padding_right: None,
1187                        data: None,
1188                    }]))
1189                }
1190            })
1191            .next()
1192            .await;
1193        cx.foreground().run_until_parked();
1194        rs_editor.update(cx, |editor, cx| {
1195            let expected_layers = vec!["0".to_string()];
1196            assert_eq!(
1197                expected_layers,
1198                cached_hint_labels(editor),
1199                "Should get its first hints when opening the editor"
1200            );
1201            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1202            assert_eq!(
1203                editor.inlay_hint_cache().version,
1204                1,
1205                "Rust editor update the cache version after every cache/view change"
1206            );
1207        });
1208
1209        cx.foreground().run_until_parked();
1210        let _md_buffer = project
1211            .update(cx, |project, cx| {
1212                project.open_local_buffer("/a/other.md", cx)
1213            })
1214            .await
1215            .unwrap();
1216        cx.foreground().run_until_parked();
1217        cx.foreground().start_waiting();
1218        let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1219        let md_editor = workspace
1220            .update(cx, |workspace, cx| {
1221                workspace.open_path((worktree_id, "other.md"), None, true, cx)
1222            })
1223            .await
1224            .unwrap()
1225            .downcast::<Editor>()
1226            .unwrap();
1227        let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1228        md_fake_server
1229            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1230                let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
1231                async move {
1232                    assert_eq!(
1233                        params.text_document.uri,
1234                        lsp::Url::from_file_path("/a/other.md").unwrap(),
1235                    );
1236                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1237                    Ok(Some(vec![lsp::InlayHint {
1238                        position: lsp::Position::new(0, i),
1239                        label: lsp::InlayHintLabel::String(i.to_string()),
1240                        kind: None,
1241                        text_edits: None,
1242                        tooltip: None,
1243                        padding_left: None,
1244                        padding_right: None,
1245                        data: None,
1246                    }]))
1247                }
1248            })
1249            .next()
1250            .await;
1251        cx.foreground().run_until_parked();
1252        md_editor.update(cx, |editor, cx| {
1253            let expected_layers = vec!["0".to_string()];
1254            assert_eq!(
1255                expected_layers,
1256                cached_hint_labels(editor),
1257                "Markdown editor should have a separate verison, repeating Rust editor rules"
1258            );
1259            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1260            assert_eq!(editor.inlay_hint_cache().version, 1);
1261        });
1262
1263        rs_editor.update(cx, |editor, cx| {
1264            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1265            editor.handle_input("some rs change", cx);
1266        });
1267        cx.foreground().run_until_parked();
1268        rs_editor.update(cx, |editor, cx| {
1269            let expected_layers = vec!["1".to_string()];
1270            assert_eq!(
1271                expected_layers,
1272                cached_hint_labels(editor),
1273                "Rust inlay cache should change after the edit"
1274            );
1275            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1276            assert_eq!(
1277                editor.inlay_hint_cache().version,
1278                2,
1279                "Every time hint cache changes, cache version should be incremented"
1280            );
1281        });
1282        md_editor.update(cx, |editor, cx| {
1283            let expected_layers = vec!["0".to_string()];
1284            assert_eq!(
1285                expected_layers,
1286                cached_hint_labels(editor),
1287                "Markdown editor should not be affected by Rust editor changes"
1288            );
1289            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1290            assert_eq!(editor.inlay_hint_cache().version, 1);
1291        });
1292
1293        md_editor.update(cx, |editor, cx| {
1294            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1295            editor.handle_input("some md change", cx);
1296        });
1297        cx.foreground().run_until_parked();
1298        md_editor.update(cx, |editor, cx| {
1299            let expected_layers = vec!["1".to_string()];
1300            assert_eq!(
1301                expected_layers,
1302                cached_hint_labels(editor),
1303                "Rust editor should not be affected by Markdown editor changes"
1304            );
1305            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1306            assert_eq!(editor.inlay_hint_cache().version, 2);
1307        });
1308        rs_editor.update(cx, |editor, cx| {
1309            let expected_layers = vec!["1".to_string()];
1310            assert_eq!(
1311                expected_layers,
1312                cached_hint_labels(editor),
1313                "Markdown editor should also change independently"
1314            );
1315            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1316            assert_eq!(editor.inlay_hint_cache().version, 2);
1317        });
1318    }
1319
1320    #[gpui::test]
1321    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1322        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1323        init_test(cx, |settings| {
1324            settings.defaults.inlay_hints = Some(InlayHintSettings {
1325                enabled: true,
1326                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1327                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1328                show_other_hints: allowed_hint_kinds.contains(&None),
1329            })
1330        });
1331
1332        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1333        let lsp_request_count = Arc::new(AtomicU32::new(0));
1334        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1335        fake_server
1336            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1337                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1338                async move {
1339                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1340                    assert_eq!(
1341                        params.text_document.uri,
1342                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1343                    );
1344                    Ok(Some(vec![
1345                        lsp::InlayHint {
1346                            position: lsp::Position::new(0, 1),
1347                            label: lsp::InlayHintLabel::String("type hint".to_string()),
1348                            kind: Some(lsp::InlayHintKind::TYPE),
1349                            text_edits: None,
1350                            tooltip: None,
1351                            padding_left: None,
1352                            padding_right: None,
1353                            data: None,
1354                        },
1355                        lsp::InlayHint {
1356                            position: lsp::Position::new(0, 2),
1357                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1358                            kind: Some(lsp::InlayHintKind::PARAMETER),
1359                            text_edits: None,
1360                            tooltip: None,
1361                            padding_left: None,
1362                            padding_right: None,
1363                            data: None,
1364                        },
1365                        lsp::InlayHint {
1366                            position: lsp::Position::new(0, 3),
1367                            label: lsp::InlayHintLabel::String("other hint".to_string()),
1368                            kind: None,
1369                            text_edits: None,
1370                            tooltip: None,
1371                            padding_left: None,
1372                            padding_right: None,
1373                            data: None,
1374                        },
1375                    ]))
1376                }
1377            })
1378            .next()
1379            .await;
1380        cx.foreground().run_until_parked();
1381
1382        let mut edits_made = 1;
1383        editor.update(cx, |editor, cx| {
1384            assert_eq!(
1385                lsp_request_count.load(Ordering::Relaxed),
1386                1,
1387                "Should query new hints once"
1388            );
1389            assert_eq!(
1390                vec![
1391                    "other hint".to_string(),
1392                    "parameter hint".to_string(),
1393                    "type hint".to_string(),
1394                ],
1395                cached_hint_labels(editor),
1396                "Should get its first hints when opening the editor"
1397            );
1398            assert_eq!(
1399                vec!["other hint".to_string(), "type hint".to_string()],
1400                visible_hint_labels(editor, cx)
1401            );
1402            let inlay_cache = editor.inlay_hint_cache();
1403            assert_eq!(
1404                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1405                "Cache should use editor settings to get the allowed hint kinds"
1406            );
1407            assert_eq!(
1408                inlay_cache.version, edits_made,
1409                "The editor update the cache version after every cache/view change"
1410            );
1411        });
1412
1413        fake_server
1414            .request::<lsp::request::InlayHintRefreshRequest>(())
1415            .await
1416            .expect("inlay refresh request failed");
1417        cx.foreground().run_until_parked();
1418        editor.update(cx, |editor, cx| {
1419            assert_eq!(
1420                lsp_request_count.load(Ordering::Relaxed),
1421                2,
1422                "Should load new hints twice"
1423            );
1424            assert_eq!(
1425                vec![
1426                    "other hint".to_string(),
1427                    "parameter hint".to_string(),
1428                    "type hint".to_string(),
1429                ],
1430                cached_hint_labels(editor),
1431                "Cached hints should not change due to allowed hint kinds settings update"
1432            );
1433            assert_eq!(
1434                vec!["other hint".to_string(), "type hint".to_string()],
1435                visible_hint_labels(editor, cx)
1436            );
1437            assert_eq!(
1438                editor.inlay_hint_cache().version,
1439                edits_made,
1440                "Should not update cache version due to new loaded hints being the same"
1441            );
1442        });
1443
1444        for (new_allowed_hint_kinds, expected_visible_hints) in [
1445            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1446            (
1447                HashSet::from_iter([Some(InlayHintKind::Type)]),
1448                vec!["type hint".to_string()],
1449            ),
1450            (
1451                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1452                vec!["parameter hint".to_string()],
1453            ),
1454            (
1455                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1456                vec!["other hint".to_string(), "type hint".to_string()],
1457            ),
1458            (
1459                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1460                vec!["other hint".to_string(), "parameter hint".to_string()],
1461            ),
1462            (
1463                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1464                vec!["parameter hint".to_string(), "type hint".to_string()],
1465            ),
1466            (
1467                HashSet::from_iter([
1468                    None,
1469                    Some(InlayHintKind::Type),
1470                    Some(InlayHintKind::Parameter),
1471                ]),
1472                vec![
1473                    "other hint".to_string(),
1474                    "parameter hint".to_string(),
1475                    "type hint".to_string(),
1476                ],
1477            ),
1478        ] {
1479            edits_made += 1;
1480            update_test_language_settings(cx, |settings| {
1481                settings.defaults.inlay_hints = Some(InlayHintSettings {
1482                    enabled: true,
1483                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1484                    show_parameter_hints: new_allowed_hint_kinds
1485                        .contains(&Some(InlayHintKind::Parameter)),
1486                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1487                })
1488            });
1489            cx.foreground().run_until_parked();
1490            editor.update(cx, |editor, cx| {
1491                assert_eq!(
1492                    lsp_request_count.load(Ordering::Relaxed),
1493                    2,
1494                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1495                );
1496                assert_eq!(
1497                    vec![
1498                        "other hint".to_string(),
1499                        "parameter hint".to_string(),
1500                        "type hint".to_string(),
1501                    ],
1502                    cached_hint_labels(editor),
1503                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1504                );
1505                assert_eq!(
1506                    expected_visible_hints,
1507                    visible_hint_labels(editor, cx),
1508                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1509                );
1510                let inlay_cache = editor.inlay_hint_cache();
1511                assert_eq!(
1512                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1513                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1514                );
1515                assert_eq!(
1516                    inlay_cache.version, edits_made,
1517                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1518                );
1519            });
1520        }
1521
1522        edits_made += 1;
1523        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1524        update_test_language_settings(cx, |settings| {
1525            settings.defaults.inlay_hints = Some(InlayHintSettings {
1526                enabled: false,
1527                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1528                show_parameter_hints: another_allowed_hint_kinds
1529                    .contains(&Some(InlayHintKind::Parameter)),
1530                show_other_hints: another_allowed_hint_kinds.contains(&None),
1531            })
1532        });
1533        cx.foreground().run_until_parked();
1534        editor.update(cx, |editor, cx| {
1535            assert_eq!(
1536                lsp_request_count.load(Ordering::Relaxed),
1537                2,
1538                "Should not load new hints when hints got disabled"
1539            );
1540            assert!(
1541                cached_hint_labels(editor).is_empty(),
1542                "Should clear the cache when hints got disabled"
1543            );
1544            assert!(
1545                visible_hint_labels(editor, cx).is_empty(),
1546                "Should clear visible hints when hints got disabled"
1547            );
1548            let inlay_cache = editor.inlay_hint_cache();
1549            assert_eq!(
1550                inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1551                "Should update its allowed hint kinds even when hints got disabled"
1552            );
1553            assert_eq!(
1554                inlay_cache.version, edits_made,
1555                "The editor should update the cache version after hints got disabled"
1556            );
1557        });
1558
1559        fake_server
1560            .request::<lsp::request::InlayHintRefreshRequest>(())
1561            .await
1562            .expect("inlay refresh request failed");
1563        cx.foreground().run_until_parked();
1564        editor.update(cx, |editor, cx| {
1565            assert_eq!(
1566                lsp_request_count.load(Ordering::Relaxed),
1567                2,
1568                "Should not load new hints when they got disabled"
1569            );
1570            assert!(cached_hint_labels(editor).is_empty());
1571            assert!(visible_hint_labels(editor, cx).is_empty());
1572            assert_eq!(
1573                editor.inlay_hint_cache().version, edits_made,
1574                "The editor should not update the cache version after /refresh query without updates"
1575            );
1576        });
1577
1578        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1579        edits_made += 1;
1580        update_test_language_settings(cx, |settings| {
1581            settings.defaults.inlay_hints = Some(InlayHintSettings {
1582                enabled: true,
1583                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1584                show_parameter_hints: final_allowed_hint_kinds
1585                    .contains(&Some(InlayHintKind::Parameter)),
1586                show_other_hints: final_allowed_hint_kinds.contains(&None),
1587            })
1588        });
1589        cx.foreground().run_until_parked();
1590        editor.update(cx, |editor, cx| {
1591            assert_eq!(
1592                lsp_request_count.load(Ordering::Relaxed),
1593                3,
1594                "Should query for new hints when they got reenabled"
1595            );
1596            assert_eq!(
1597                vec![
1598                    "other hint".to_string(),
1599                    "parameter hint".to_string(),
1600                    "type hint".to_string(),
1601                ],
1602                cached_hint_labels(editor),
1603                "Should get its cached hints fully repopulated after the hints got reenabled"
1604            );
1605            assert_eq!(
1606                vec!["parameter hint".to_string()],
1607                visible_hint_labels(editor, cx),
1608                "Should get its visible hints repopulated and filtered after the h"
1609            );
1610            let inlay_cache = editor.inlay_hint_cache();
1611            assert_eq!(
1612                inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1613                "Cache should update editor settings when hints got reenabled"
1614            );
1615            assert_eq!(
1616                inlay_cache.version, edits_made,
1617                "Cache should update its version after hints got reenabled"
1618            );
1619        });
1620
1621        fake_server
1622            .request::<lsp::request::InlayHintRefreshRequest>(())
1623            .await
1624            .expect("inlay refresh request failed");
1625        cx.foreground().run_until_parked();
1626        editor.update(cx, |editor, cx| {
1627            assert_eq!(
1628                lsp_request_count.load(Ordering::Relaxed),
1629                4,
1630                "Should query for new hints again"
1631            );
1632            assert_eq!(
1633                vec![
1634                    "other hint".to_string(),
1635                    "parameter hint".to_string(),
1636                    "type hint".to_string(),
1637                ],
1638                cached_hint_labels(editor),
1639            );
1640            assert_eq!(
1641                vec!["parameter hint".to_string()],
1642                visible_hint_labels(editor, cx),
1643            );
1644            assert_eq!(editor.inlay_hint_cache().version, edits_made);
1645        });
1646    }
1647
1648    #[gpui::test]
1649    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
1650        init_test(cx, |settings| {
1651            settings.defaults.inlay_hints = Some(InlayHintSettings {
1652                enabled: true,
1653                show_type_hints: true,
1654                show_parameter_hints: true,
1655                show_other_hints: true,
1656            })
1657        });
1658
1659        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1660        let fake_server = Arc::new(fake_server);
1661        let lsp_request_count = Arc::new(AtomicU32::new(0));
1662        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1663        fake_server
1664            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1665                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1666                async move {
1667                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
1668                    assert_eq!(
1669                        params.text_document.uri,
1670                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1671                    );
1672                    Ok(Some(vec![lsp::InlayHint {
1673                        position: lsp::Position::new(0, i),
1674                        label: lsp::InlayHintLabel::String(i.to_string()),
1675                        kind: None,
1676                        text_edits: None,
1677                        tooltip: None,
1678                        padding_left: None,
1679                        padding_right: None,
1680                        data: None,
1681                    }]))
1682                }
1683            })
1684            .next()
1685            .await;
1686
1687        let mut expected_changes = Vec::new();
1688        for change_after_opening in [
1689            "initial change #1",
1690            "initial change #2",
1691            "initial change #3",
1692        ] {
1693            editor.update(cx, |editor, cx| {
1694                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1695                editor.handle_input(change_after_opening, cx);
1696            });
1697            expected_changes.push(change_after_opening);
1698        }
1699
1700        cx.foreground().run_until_parked();
1701
1702        editor.update(cx, |editor, cx| {
1703            let current_text = editor.text(cx);
1704            for change in &expected_changes {
1705                assert!(
1706                    current_text.contains(change),
1707                    "Should apply all changes made"
1708                );
1709            }
1710            assert_eq!(
1711                lsp_request_count.load(Ordering::Relaxed),
1712                2,
1713                "Should query new hints twice: for editor init and for the last edit that interrupted all others"
1714            );
1715            let expected_hints = vec!["2".to_string()];
1716            assert_eq!(
1717                expected_hints,
1718                cached_hint_labels(editor),
1719                "Should get hints from the last edit landed only"
1720            );
1721            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1722            assert_eq!(
1723                editor.inlay_hint_cache().version, 1,
1724                "Only one update should be registered in the cache after all cancellations"
1725            );
1726        });
1727
1728        let mut edits = Vec::new();
1729        for async_later_change in [
1730            "another change #1",
1731            "another change #2",
1732            "another change #3",
1733        ] {
1734            expected_changes.push(async_later_change);
1735            let task_editor = editor.clone();
1736            let mut task_cx = cx.clone();
1737            edits.push(cx.foreground().spawn(async move {
1738                task_editor.update(&mut task_cx, |editor, cx| {
1739                    editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1740                    editor.handle_input(async_later_change, cx);
1741                });
1742            }));
1743        }
1744        let _ = futures::future::join_all(edits).await;
1745        cx.foreground().run_until_parked();
1746
1747        editor.update(cx, |editor, cx| {
1748            let current_text = editor.text(cx);
1749            for change in &expected_changes {
1750                assert!(
1751                    current_text.contains(change),
1752                    "Should apply all changes made"
1753                );
1754            }
1755            assert_eq!(
1756                lsp_request_count.load(Ordering::SeqCst),
1757                3,
1758                "Should query new hints one more time, for the last edit only"
1759            );
1760            let expected_hints = vec!["3".to_string()];
1761            assert_eq!(
1762                expected_hints,
1763                cached_hint_labels(editor),
1764                "Should get hints from the last edit landed only"
1765            );
1766            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1767            assert_eq!(
1768                editor.inlay_hint_cache().version,
1769                2,
1770                "Should update the cache version once more, for the new change"
1771            );
1772        });
1773    }
1774
1775    #[gpui::test]
1776    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
1777        init_test(cx, |settings| {
1778            settings.defaults.inlay_hints = Some(InlayHintSettings {
1779                enabled: true,
1780                show_type_hints: true,
1781                show_parameter_hints: true,
1782                show_other_hints: true,
1783            })
1784        });
1785
1786        let mut language = Language::new(
1787            LanguageConfig {
1788                name: "Rust".into(),
1789                path_suffixes: vec!["rs".to_string()],
1790                ..Default::default()
1791            },
1792            Some(tree_sitter_rust::language()),
1793        );
1794        let mut fake_servers = language
1795            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1796                capabilities: lsp::ServerCapabilities {
1797                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1798                    ..Default::default()
1799                },
1800                ..Default::default()
1801            }))
1802            .await;
1803        let fs = FakeFs::new(cx.background());
1804        fs.insert_tree(
1805            "/a",
1806            json!({
1807                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
1808                "other.rs": "// Test file",
1809            }),
1810        )
1811        .await;
1812        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1813        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
1814        let workspace = cx
1815            .add_window(|cx| Workspace::test_new(project.clone(), cx))
1816            .root(cx);
1817        let worktree_id = workspace.update(cx, |workspace, cx| {
1818            workspace.project().read_with(cx, |project, cx| {
1819                project.worktrees(cx).next().unwrap().read(cx).id()
1820            })
1821        });
1822
1823        let _buffer = project
1824            .update(cx, |project, cx| {
1825                project.open_local_buffer("/a/main.rs", cx)
1826            })
1827            .await
1828            .unwrap();
1829        cx.foreground().run_until_parked();
1830        cx.foreground().start_waiting();
1831        let fake_server = fake_servers.next().await.unwrap();
1832        let editor = workspace
1833            .update(cx, |workspace, cx| {
1834                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1835            })
1836            .await
1837            .unwrap()
1838            .downcast::<Editor>()
1839            .unwrap();
1840        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
1841        let lsp_request_count = Arc::new(AtomicU32::new(0));
1842        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
1843        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
1844        fake_server
1845            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1846                let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
1847                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
1848                async move {
1849                    assert_eq!(
1850                        params.text_document.uri,
1851                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1852                    );
1853
1854                    task_lsp_request_ranges.lock().push(params.range);
1855                    let query_start = params.range.start;
1856                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
1857                    Ok(Some(vec![lsp::InlayHint {
1858                        position: query_start,
1859                        label: lsp::InlayHintLabel::String(i.to_string()),
1860                        kind: None,
1861                        text_edits: None,
1862                        tooltip: None,
1863                        padding_left: None,
1864                        padding_right: None,
1865                        data: None,
1866                    }]))
1867                }
1868            })
1869            .next()
1870            .await;
1871        fn editor_visible_range(
1872            editor: &ViewHandle<Editor>,
1873            cx: &mut gpui::TestAppContext,
1874        ) -> Range<Point> {
1875            let ranges = editor.update(cx, |editor, cx| editor.excerpt_visible_offsets(None, cx));
1876            assert_eq!(
1877                ranges.len(),
1878                1,
1879                "Single buffer should produce a single excerpt with visible range"
1880            );
1881            let (_, (excerpt_buffer, _, excerpt_visible_range)) =
1882                ranges.into_iter().next().unwrap();
1883            excerpt_buffer.update(cx, |buffer, _| {
1884                let snapshot = buffer.snapshot();
1885                let start = buffer
1886                    .anchor_before(excerpt_visible_range.start)
1887                    .to_point(&snapshot);
1888                let end = buffer
1889                    .anchor_after(excerpt_visible_range.end)
1890                    .to_point(&snapshot);
1891                start..end
1892            })
1893        }
1894
1895        let initial_visible_range = editor_visible_range(&editor, cx);
1896        let expected_initial_query_range_end =
1897            lsp::Position::new(initial_visible_range.end.row * 2, 1);
1898        cx.foreground().run_until_parked();
1899        editor.update(cx, |editor, cx| {
1900            let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
1901            assert_eq!(ranges.len(), 1,
1902                "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:?}");
1903            let query_range = &ranges[0];
1904            assert_eq!(query_range.start, lsp::Position::new(0, 0), "Should query initially from the beginning of the document");
1905            assert_eq!(query_range.end, expected_initial_query_range_end, "Should query initially for double lines of the visible part of the document");
1906
1907            assert_eq!(lsp_request_count.load(Ordering::Acquire), 1);
1908            let expected_layers = vec!["1".to_string()];
1909            assert_eq!(
1910                expected_layers,
1911                cached_hint_labels(editor),
1912                "Should have hints from both LSP requests made for a big file"
1913            );
1914            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1915            assert_eq!(
1916                editor.inlay_hint_cache().version, 1,
1917                "LSP queries should've bumped the cache version"
1918            );
1919        });
1920
1921        editor.update(cx, |editor, cx| {
1922            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1923            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
1924        });
1925
1926        let visible_range_after_scrolls = editor_visible_range(&editor, cx);
1927        cx.foreground().run_until_parked();
1928        let selection_in_cached_range = editor.update(cx, |editor, cx| {
1929            let ranges = lsp_request_ranges
1930                .lock()
1931                .drain(..)
1932                .sorted_by_key(|r| r.start)
1933                .collect::<Vec<_>>();
1934            assert_eq!(
1935                ranges.len(),
1936                2,
1937                "Should query 2 ranges after both scrolls, but got: {ranges:?}"
1938            );
1939            let first_scroll = &ranges[0];
1940            let second_scroll = &ranges[1];
1941            assert_eq!(
1942                first_scroll.end, second_scroll.start,
1943                "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
1944            );
1945            assert_eq!(
1946                first_scroll.start, expected_initial_query_range_end,
1947                "First scroll should start the query right after the end of the original scroll",
1948            );
1949            let expected_increment = editor.visible_line_count().unwrap().ceil() as u32;
1950            assert_eq!(
1951                second_scroll.end,
1952                lsp::Position::new(
1953                    visible_range_after_scrolls.end.row
1954                        + expected_increment,
1955                    0
1956                ),
1957                "Second scroll should query one more screen down after the end of the visible range"
1958            );
1959
1960            assert_eq!(
1961                lsp_request_count.load(Ordering::Acquire),
1962                3,
1963                "Should query for hints after every scroll"
1964            );
1965            let expected_layers = vec!["1".to_string(), "2".to_string(), "3".to_string()];
1966            assert_eq!(
1967                expected_layers,
1968                cached_hint_labels(editor),
1969                "Should have hints from the new LSP response after the edit"
1970            );
1971            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
1972            assert_eq!(
1973                editor.inlay_hint_cache().version,
1974                3,
1975                "Should update the cache for every LSP response with hints added"
1976            );
1977
1978            let mut selection_in_cached_range = visible_range_after_scrolls.end;
1979            selection_in_cached_range.row -= expected_increment;
1980            selection_in_cached_range
1981        });
1982
1983        editor.update(cx, |editor, cx| {
1984            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
1985                s.select_ranges([selection_in_cached_range..selection_in_cached_range])
1986            });
1987        });
1988        cx.foreground().run_until_parked();
1989        editor.update(cx, |_, _| {
1990            let ranges = lsp_request_ranges
1991                .lock()
1992                .drain(..)
1993                .sorted_by_key(|r| r.start)
1994                .collect::<Vec<_>>();
1995            assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
1996            assert_eq!(lsp_request_count.load(Ordering::Acquire), 3);
1997        });
1998
1999        editor.update(cx, |editor, cx| {
2000            editor.handle_input("++++more text++++", cx);
2001        });
2002        cx.foreground().run_until_parked();
2003        editor.update(cx, |editor, cx| {
2004            let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2005            let expected_increment = editor.visible_line_count().unwrap().ceil() as u32;
2006            assert_eq!(ranges.len(), 1,
2007                "On edit, should scroll to selection and query a range around it. Instead, got query ranges {ranges:?}");
2008            let query_range = &ranges[0];
2009            assert_eq!(query_range.start, lsp::Position::new(selection_in_cached_range.row - expected_increment, 0));
2010            assert_eq!(query_range.end, lsp::Position::new(selection_in_cached_range.row + expected_increment, 0));
2011
2012            assert_eq!(lsp_request_count.load(Ordering::Acquire), 3, "Should query for hints after the scroll and again after the edit");
2013            let expected_layers = vec!["1".to_string(), "2".to_string(), "3".to_string()];
2014            assert_eq!(expected_layers, cached_hint_labels(editor),
2015                "Should have hints from the new LSP response after the edit");
2016            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2017            assert_eq!(editor.inlay_hint_cache().version, 3, "Should update the cache for every LSP response with hints added");
2018        });
2019    }
2020
2021    #[gpui::test]
2022    async fn test_multiple_excerpts_large_multibuffer(
2023        deterministic: Arc<Deterministic>,
2024        cx: &mut gpui::TestAppContext,
2025    ) {
2026        init_test(cx, |settings| {
2027            settings.defaults.inlay_hints = Some(InlayHintSettings {
2028                enabled: true,
2029                show_type_hints: true,
2030                show_parameter_hints: true,
2031                show_other_hints: true,
2032            })
2033        });
2034
2035        let mut language = Language::new(
2036            LanguageConfig {
2037                name: "Rust".into(),
2038                path_suffixes: vec!["rs".to_string()],
2039                ..Default::default()
2040            },
2041            Some(tree_sitter_rust::language()),
2042        );
2043        let mut fake_servers = language
2044            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2045                capabilities: lsp::ServerCapabilities {
2046                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2047                    ..Default::default()
2048                },
2049                ..Default::default()
2050            }))
2051            .await;
2052        let language = Arc::new(language);
2053        let fs = FakeFs::new(cx.background());
2054        fs.insert_tree(
2055            "/a",
2056            json!({
2057                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2058                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2059            }),
2060        )
2061        .await;
2062        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2063        project.update(cx, |project, _| {
2064            project.languages().add(Arc::clone(&language))
2065        });
2066        let workspace = cx
2067            .add_window(|cx| Workspace::test_new(project.clone(), cx))
2068            .root(cx);
2069        let worktree_id = workspace.update(cx, |workspace, cx| {
2070            workspace.project().read_with(cx, |project, cx| {
2071                project.worktrees(cx).next().unwrap().read(cx).id()
2072            })
2073        });
2074
2075        let buffer_1 = project
2076            .update(cx, |project, cx| {
2077                project.open_buffer((worktree_id, "main.rs"), cx)
2078            })
2079            .await
2080            .unwrap();
2081        let buffer_2 = project
2082            .update(cx, |project, cx| {
2083                project.open_buffer((worktree_id, "other.rs"), cx)
2084            })
2085            .await
2086            .unwrap();
2087        let multibuffer = cx.add_model(|cx| {
2088            let mut multibuffer = MultiBuffer::new(0);
2089            multibuffer.push_excerpts(
2090                buffer_1.clone(),
2091                [
2092                    ExcerptRange {
2093                        context: Point::new(0, 0)..Point::new(2, 0),
2094                        primary: None,
2095                    },
2096                    ExcerptRange {
2097                        context: Point::new(4, 0)..Point::new(11, 0),
2098                        primary: None,
2099                    },
2100                    ExcerptRange {
2101                        context: Point::new(22, 0)..Point::new(33, 0),
2102                        primary: None,
2103                    },
2104                    ExcerptRange {
2105                        context: Point::new(44, 0)..Point::new(55, 0),
2106                        primary: None,
2107                    },
2108                    ExcerptRange {
2109                        context: Point::new(56, 0)..Point::new(66, 0),
2110                        primary: None,
2111                    },
2112                    ExcerptRange {
2113                        context: Point::new(67, 0)..Point::new(77, 0),
2114                        primary: None,
2115                    },
2116                ],
2117                cx,
2118            );
2119            multibuffer.push_excerpts(
2120                buffer_2.clone(),
2121                [
2122                    ExcerptRange {
2123                        context: Point::new(0, 1)..Point::new(2, 1),
2124                        primary: None,
2125                    },
2126                    ExcerptRange {
2127                        context: Point::new(4, 1)..Point::new(11, 1),
2128                        primary: None,
2129                    },
2130                    ExcerptRange {
2131                        context: Point::new(22, 1)..Point::new(33, 1),
2132                        primary: None,
2133                    },
2134                    ExcerptRange {
2135                        context: Point::new(44, 1)..Point::new(55, 1),
2136                        primary: None,
2137                    },
2138                    ExcerptRange {
2139                        context: Point::new(56, 1)..Point::new(66, 1),
2140                        primary: None,
2141                    },
2142                    ExcerptRange {
2143                        context: Point::new(67, 1)..Point::new(77, 1),
2144                        primary: None,
2145                    },
2146                ],
2147                cx,
2148            );
2149            multibuffer
2150        });
2151
2152        deterministic.run_until_parked();
2153        cx.foreground().run_until_parked();
2154        let editor = cx
2155            .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
2156            .root(cx);
2157        let editor_edited = Arc::new(AtomicBool::new(false));
2158        let fake_server = fake_servers.next().await.unwrap();
2159        let closure_editor_edited = Arc::clone(&editor_edited);
2160        fake_server
2161            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2162                let task_editor_edited = Arc::clone(&closure_editor_edited);
2163                async move {
2164                    let hint_text = if params.text_document.uri
2165                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2166                    {
2167                        "main hint"
2168                    } else if params.text_document.uri
2169                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2170                    {
2171                        "other hint"
2172                    } else {
2173                        panic!("unexpected uri: {:?}", params.text_document.uri);
2174                    };
2175
2176                    // one hint per excerpt
2177                    let positions = [
2178                        lsp::Position::new(0, 2),
2179                        lsp::Position::new(4, 2),
2180                        lsp::Position::new(22, 2),
2181                        lsp::Position::new(44, 2),
2182                        lsp::Position::new(56, 2),
2183                        lsp::Position::new(67, 2),
2184                    ];
2185                    let out_of_range_hint = lsp::InlayHint {
2186                        position: lsp::Position::new(
2187                            params.range.start.line + 99,
2188                            params.range.start.character + 99,
2189                        ),
2190                        label: lsp::InlayHintLabel::String(
2191                            "out of excerpt range, should be ignored".to_string(),
2192                        ),
2193                        kind: None,
2194                        text_edits: None,
2195                        tooltip: None,
2196                        padding_left: None,
2197                        padding_right: None,
2198                        data: None,
2199                    };
2200
2201                    let edited = task_editor_edited.load(Ordering::Acquire);
2202                    Ok(Some(
2203                        std::iter::once(out_of_range_hint)
2204                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2205                                lsp::InlayHint {
2206                                    position,
2207                                    label: lsp::InlayHintLabel::String(format!(
2208                                        "{hint_text}{} #{i}",
2209                                        if edited { "(edited)" } else { "" },
2210                                    )),
2211                                    kind: None,
2212                                    text_edits: None,
2213                                    tooltip: None,
2214                                    padding_left: None,
2215                                    padding_right: None,
2216                                    data: None,
2217                                }
2218                            }))
2219                            .collect(),
2220                    ))
2221                }
2222            })
2223            .next()
2224            .await;
2225        cx.foreground().run_until_parked();
2226
2227        editor.update(cx, |editor, cx| {
2228            let expected_layers = vec![
2229                "main hint #0".to_string(),
2230                "main hint #1".to_string(),
2231                "main hint #2".to_string(),
2232                "main hint #3".to_string(),
2233            ];
2234            assert_eq!(
2235                expected_layers,
2236                cached_hint_labels(editor),
2237                "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2238            );
2239            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2240            assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(), "Every visible excerpt hints should bump the verison");
2241        });
2242
2243        editor.update(cx, |editor, cx| {
2244            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2245                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2246            });
2247            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2248                s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2249            });
2250            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2251                s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2252            });
2253        });
2254        cx.foreground().run_until_parked();
2255        editor.update(cx, |editor, cx| {
2256            let expected_layers = vec![
2257                "main hint #0".to_string(),
2258                "main hint #1".to_string(),
2259                "main hint #2".to_string(),
2260                "main hint #3".to_string(),
2261                "main hint #4".to_string(),
2262                "main hint #5".to_string(),
2263                "other hint #0".to_string(),
2264                "other hint #1".to_string(),
2265                "other hint #2".to_string(),
2266            ];
2267            assert_eq!(expected_layers, cached_hint_labels(editor),
2268                "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2269            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2270            assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(),
2271                "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
2272        });
2273
2274        editor.update(cx, |editor, cx| {
2275            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2276                s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2277            });
2278        });
2279        cx.foreground().run_until_parked();
2280        let last_scroll_update_version = editor.update(cx, |editor, cx| {
2281            let expected_layers = vec![
2282                "main hint #0".to_string(),
2283                "main hint #1".to_string(),
2284                "main hint #2".to_string(),
2285                "main hint #3".to_string(),
2286                "main hint #4".to_string(),
2287                "main hint #5".to_string(),
2288                "other hint #0".to_string(),
2289                "other hint #1".to_string(),
2290                "other hint #2".to_string(),
2291                "other hint #3".to_string(),
2292                "other hint #4".to_string(),
2293                "other hint #5".to_string(),
2294            ];
2295            assert_eq!(expected_layers, cached_hint_labels(editor),
2296                "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2297            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2298            assert_eq!(editor.inlay_hint_cache().version, expected_layers.len());
2299            expected_layers.len()
2300        });
2301
2302        editor.update(cx, |editor, cx| {
2303            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2304                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2305            });
2306        });
2307        cx.foreground().run_until_parked();
2308        editor.update(cx, |editor, cx| {
2309            let expected_layers = vec![
2310                "main hint #0".to_string(),
2311                "main hint #1".to_string(),
2312                "main hint #2".to_string(),
2313                "main hint #3".to_string(),
2314                "main hint #4".to_string(),
2315                "main hint #5".to_string(),
2316                "other hint #0".to_string(),
2317                "other hint #1".to_string(),
2318                "other hint #2".to_string(),
2319                "other hint #3".to_string(),
2320                "other hint #4".to_string(),
2321                "other hint #5".to_string(),
2322            ];
2323            assert_eq!(expected_layers, cached_hint_labels(editor),
2324                "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2325            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2326            assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
2327        });
2328
2329        editor_edited.store(true, Ordering::Release);
2330        editor.update(cx, |editor, cx| {
2331            editor.change_selections(None, cx, |s| {
2332                s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
2333            });
2334            editor.handle_input("++++more text++++", cx);
2335        });
2336        cx.foreground().run_until_parked();
2337        editor.update(cx, |editor, cx| {
2338            let expected_layers = vec![
2339                "main hint(edited) #0".to_string(),
2340                "main hint(edited) #1".to_string(),
2341                "main hint(edited) #2".to_string(),
2342                "main hint(edited) #3".to_string(),
2343                "main hint(edited) #4".to_string(),
2344                "main hint(edited) #5".to_string(),
2345                "other hint(edited) #0".to_string(),
2346                "other hint(edited) #1".to_string(),
2347            ];
2348            assert_eq!(
2349                expected_layers,
2350                cached_hint_labels(editor),
2351                "After multibuffer edit, editor gets scolled back to the last selection; \
2352all hints should be invalidated and requeried for all of its visible excerpts"
2353            );
2354            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2355            assert_eq!(
2356                editor.inlay_hint_cache().version,
2357                last_scroll_update_version + expected_layers.len(),
2358                "Due to every excerpt having one hint, cache should update per new excerpt received"
2359            );
2360        });
2361    }
2362
2363    #[gpui::test]
2364    async fn test_excerpts_removed(
2365        deterministic: Arc<Deterministic>,
2366        cx: &mut gpui::TestAppContext,
2367    ) {
2368        init_test(cx, |settings| {
2369            settings.defaults.inlay_hints = Some(InlayHintSettings {
2370                enabled: true,
2371                show_type_hints: false,
2372                show_parameter_hints: false,
2373                show_other_hints: false,
2374            })
2375        });
2376
2377        let mut language = Language::new(
2378            LanguageConfig {
2379                name: "Rust".into(),
2380                path_suffixes: vec!["rs".to_string()],
2381                ..Default::default()
2382            },
2383            Some(tree_sitter_rust::language()),
2384        );
2385        let mut fake_servers = language
2386            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2387                capabilities: lsp::ServerCapabilities {
2388                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2389                    ..Default::default()
2390                },
2391                ..Default::default()
2392            }))
2393            .await;
2394        let language = Arc::new(language);
2395        let fs = FakeFs::new(cx.background());
2396        fs.insert_tree(
2397            "/a",
2398            json!({
2399                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2400                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2401            }),
2402        )
2403        .await;
2404        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2405        project.update(cx, |project, _| {
2406            project.languages().add(Arc::clone(&language))
2407        });
2408        let workspace = cx
2409            .add_window(|cx| Workspace::test_new(project.clone(), cx))
2410            .root(cx);
2411        let worktree_id = workspace.update(cx, |workspace, cx| {
2412            workspace.project().read_with(cx, |project, cx| {
2413                project.worktrees(cx).next().unwrap().read(cx).id()
2414            })
2415        });
2416
2417        let buffer_1 = project
2418            .update(cx, |project, cx| {
2419                project.open_buffer((worktree_id, "main.rs"), cx)
2420            })
2421            .await
2422            .unwrap();
2423        let buffer_2 = project
2424            .update(cx, |project, cx| {
2425                project.open_buffer((worktree_id, "other.rs"), cx)
2426            })
2427            .await
2428            .unwrap();
2429        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
2430        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2431            let buffer_1_excerpts = multibuffer.push_excerpts(
2432                buffer_1.clone(),
2433                [ExcerptRange {
2434                    context: Point::new(0, 0)..Point::new(2, 0),
2435                    primary: None,
2436                }],
2437                cx,
2438            );
2439            let buffer_2_excerpts = multibuffer.push_excerpts(
2440                buffer_2.clone(),
2441                [ExcerptRange {
2442                    context: Point::new(0, 1)..Point::new(2, 1),
2443                    primary: None,
2444                }],
2445                cx,
2446            );
2447            (buffer_1_excerpts, buffer_2_excerpts)
2448        });
2449
2450        assert!(!buffer_1_excerpts.is_empty());
2451        assert!(!buffer_2_excerpts.is_empty());
2452
2453        deterministic.run_until_parked();
2454        cx.foreground().run_until_parked();
2455        let editor = cx
2456            .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
2457            .root(cx);
2458        let editor_edited = Arc::new(AtomicBool::new(false));
2459        let fake_server = fake_servers.next().await.unwrap();
2460        let closure_editor_edited = Arc::clone(&editor_edited);
2461        fake_server
2462            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2463                let task_editor_edited = Arc::clone(&closure_editor_edited);
2464                async move {
2465                    let hint_text = if params.text_document.uri
2466                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2467                    {
2468                        "main hint"
2469                    } else if params.text_document.uri
2470                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2471                    {
2472                        "other hint"
2473                    } else {
2474                        panic!("unexpected uri: {:?}", params.text_document.uri);
2475                    };
2476
2477                    let positions = [
2478                        lsp::Position::new(0, 2),
2479                        lsp::Position::new(4, 2),
2480                        lsp::Position::new(22, 2),
2481                        lsp::Position::new(44, 2),
2482                        lsp::Position::new(56, 2),
2483                        lsp::Position::new(67, 2),
2484                    ];
2485                    let out_of_range_hint = lsp::InlayHint {
2486                        position: lsp::Position::new(
2487                            params.range.start.line + 99,
2488                            params.range.start.character + 99,
2489                        ),
2490                        label: lsp::InlayHintLabel::String(
2491                            "out of excerpt range, should be ignored".to_string(),
2492                        ),
2493                        kind: None,
2494                        text_edits: None,
2495                        tooltip: None,
2496                        padding_left: None,
2497                        padding_right: None,
2498                        data: None,
2499                    };
2500
2501                    let edited = task_editor_edited.load(Ordering::Acquire);
2502                    Ok(Some(
2503                        std::iter::once(out_of_range_hint)
2504                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2505                                lsp::InlayHint {
2506                                    position,
2507                                    label: lsp::InlayHintLabel::String(format!(
2508                                        "{hint_text}{} #{i}",
2509                                        if edited { "(edited)" } else { "" },
2510                                    )),
2511                                    kind: None,
2512                                    text_edits: None,
2513                                    tooltip: None,
2514                                    padding_left: None,
2515                                    padding_right: None,
2516                                    data: None,
2517                                }
2518                            }))
2519                            .collect(),
2520                    ))
2521                }
2522            })
2523            .next()
2524            .await;
2525        cx.foreground().run_until_parked();
2526
2527        editor.update(cx, |editor, cx| {
2528            assert_eq!(
2529                vec!["main hint #0".to_string(), "other hint #0".to_string()],
2530                cached_hint_labels(editor),
2531                "Cache should update for both excerpts despite hints display was disabled"
2532            );
2533            assert!(
2534                visible_hint_labels(editor, cx).is_empty(),
2535                "All hints are disabled and should not be shown despite being present in the cache"
2536            );
2537            assert_eq!(
2538                editor.inlay_hint_cache().version,
2539                2,
2540                "Cache should update once per excerpt query"
2541            );
2542        });
2543
2544        editor.update(cx, |editor, cx| {
2545            editor.buffer().update(cx, |multibuffer, cx| {
2546                multibuffer.remove_excerpts(buffer_2_excerpts, cx)
2547            })
2548        });
2549        cx.foreground().run_until_parked();
2550        editor.update(cx, |editor, cx| {
2551            assert_eq!(
2552                vec!["main hint #0".to_string()],
2553                cached_hint_labels(editor),
2554                "For the removed excerpt, should clean corresponding cached hints"
2555            );
2556            assert!(
2557                visible_hint_labels(editor, cx).is_empty(),
2558                "All hints are disabled and should not be shown despite being present in the cache"
2559            );
2560            assert_eq!(
2561                editor.inlay_hint_cache().version,
2562                2,
2563                "Excerpt removal should trigger a cache update"
2564            );
2565        });
2566
2567        update_test_language_settings(cx, |settings| {
2568            settings.defaults.inlay_hints = Some(InlayHintSettings {
2569                enabled: true,
2570                show_type_hints: true,
2571                show_parameter_hints: true,
2572                show_other_hints: true,
2573            })
2574        });
2575        cx.foreground().run_until_parked();
2576        editor.update(cx, |editor, cx| {
2577            let expected_hints = vec!["main hint #0".to_string()];
2578            assert_eq!(
2579                expected_hints,
2580                cached_hint_labels(editor),
2581                "Hint display settings change should not change the cache"
2582            );
2583            assert_eq!(
2584                expected_hints,
2585                visible_hint_labels(editor, cx),
2586                "Settings change should make cached hints visible"
2587            );
2588            assert_eq!(
2589                editor.inlay_hint_cache().version,
2590                3,
2591                "Settings change should trigger a cache update"
2592            );
2593        });
2594    }
2595
2596    #[gpui::test]
2597    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
2598        init_test(cx, |settings| {
2599            settings.defaults.inlay_hints = Some(InlayHintSettings {
2600                enabled: true,
2601                show_type_hints: true,
2602                show_parameter_hints: true,
2603                show_other_hints: true,
2604            })
2605        });
2606
2607        let mut language = Language::new(
2608            LanguageConfig {
2609                name: "Rust".into(),
2610                path_suffixes: vec!["rs".to_string()],
2611                ..Default::default()
2612            },
2613            Some(tree_sitter_rust::language()),
2614        );
2615        let mut fake_servers = language
2616            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2617                capabilities: lsp::ServerCapabilities {
2618                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2619                    ..Default::default()
2620                },
2621                ..Default::default()
2622            }))
2623            .await;
2624        let fs = FakeFs::new(cx.background());
2625        fs.insert_tree(
2626            "/a",
2627            json!({
2628                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
2629                "other.rs": "// Test file",
2630            }),
2631        )
2632        .await;
2633        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2634        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2635        let workspace = cx
2636            .add_window(|cx| Workspace::test_new(project.clone(), cx))
2637            .root(cx);
2638        let worktree_id = workspace.update(cx, |workspace, cx| {
2639            workspace.project().read_with(cx, |project, cx| {
2640                project.worktrees(cx).next().unwrap().read(cx).id()
2641            })
2642        });
2643
2644        let _buffer = project
2645            .update(cx, |project, cx| {
2646                project.open_local_buffer("/a/main.rs", cx)
2647            })
2648            .await
2649            .unwrap();
2650        cx.foreground().run_until_parked();
2651        cx.foreground().start_waiting();
2652        let fake_server = fake_servers.next().await.unwrap();
2653        let editor = workspace
2654            .update(cx, |workspace, cx| {
2655                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2656            })
2657            .await
2658            .unwrap()
2659            .downcast::<Editor>()
2660            .unwrap();
2661        let lsp_request_count = Arc::new(AtomicU32::new(0));
2662        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2663        fake_server
2664            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2665                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2666                async move {
2667                    assert_eq!(
2668                        params.text_document.uri,
2669                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
2670                    );
2671                    let query_start = params.range.start;
2672                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
2673                    Ok(Some(vec![lsp::InlayHint {
2674                        position: query_start,
2675                        label: lsp::InlayHintLabel::String(i.to_string()),
2676                        kind: None,
2677                        text_edits: None,
2678                        tooltip: None,
2679                        padding_left: None,
2680                        padding_right: None,
2681                        data: None,
2682                    }]))
2683                }
2684            })
2685            .next()
2686            .await;
2687
2688        cx.foreground().run_until_parked();
2689        editor.update(cx, |editor, cx| {
2690            editor.change_selections(None, cx, |s| {
2691                s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
2692            })
2693        });
2694        cx.foreground().run_until_parked();
2695        editor.update(cx, |editor, cx| {
2696            let expected_layers = vec!["1".to_string()];
2697            assert_eq!(expected_layers, cached_hint_labels(editor));
2698            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
2699            assert_eq!(editor.inlay_hint_cache().version, 1);
2700        });
2701    }
2702
2703    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
2704        cx.foreground().forbid_parking();
2705
2706        cx.update(|cx| {
2707            cx.set_global(SettingsStore::test(cx));
2708            theme::init((), cx);
2709            client::init_settings(cx);
2710            language::init(cx);
2711            Project::init_settings(cx);
2712            workspace::init_settings(cx);
2713            crate::init(cx);
2714        });
2715
2716        update_test_language_settings(cx, f);
2717    }
2718
2719    async fn prepare_test_objects(
2720        cx: &mut TestAppContext,
2721    ) -> (&'static str, ViewHandle<Editor>, FakeLanguageServer) {
2722        let mut language = Language::new(
2723            LanguageConfig {
2724                name: "Rust".into(),
2725                path_suffixes: vec!["rs".to_string()],
2726                ..Default::default()
2727            },
2728            Some(tree_sitter_rust::language()),
2729        );
2730        let mut fake_servers = language
2731            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2732                capabilities: lsp::ServerCapabilities {
2733                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2734                    ..Default::default()
2735                },
2736                ..Default::default()
2737            }))
2738            .await;
2739
2740        let fs = FakeFs::new(cx.background());
2741        fs.insert_tree(
2742            "/a",
2743            json!({
2744                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
2745                "other.rs": "// Test file",
2746            }),
2747        )
2748        .await;
2749
2750        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2751        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2752        let workspace = cx
2753            .add_window(|cx| Workspace::test_new(project.clone(), cx))
2754            .root(cx);
2755        let worktree_id = workspace.update(cx, |workspace, cx| {
2756            workspace.project().read_with(cx, |project, cx| {
2757                project.worktrees(cx).next().unwrap().read(cx).id()
2758            })
2759        });
2760
2761        let _buffer = project
2762            .update(cx, |project, cx| {
2763                project.open_local_buffer("/a/main.rs", cx)
2764            })
2765            .await
2766            .unwrap();
2767        cx.foreground().run_until_parked();
2768        cx.foreground().start_waiting();
2769        let fake_server = fake_servers.next().await.unwrap();
2770        let editor = workspace
2771            .update(cx, |workspace, cx| {
2772                workspace.open_path((worktree_id, "main.rs"), None, true, cx)
2773            })
2774            .await
2775            .unwrap()
2776            .downcast::<Editor>()
2777            .unwrap();
2778
2779        ("/a/main.rs", editor, fake_server)
2780    }
2781
2782    fn cached_hint_labels(editor: &Editor) -> Vec<String> {
2783        let mut labels = Vec::new();
2784        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
2785            let excerpt_hints = excerpt_hints.read();
2786            for (_, inlay) in excerpt_hints.hints.iter() {
2787                match &inlay.label {
2788                    project::InlayHintLabel::String(s) => labels.push(s.to_string()),
2789                    _ => unreachable!(),
2790                }
2791            }
2792        }
2793
2794        labels.sort();
2795        labels
2796    }
2797
2798    fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec<String> {
2799        let mut hints = editor
2800            .visible_inlay_hints(cx)
2801            .into_iter()
2802            .map(|hint| hint.text.to_string())
2803            .collect::<Vec<_>>();
2804        hints.sort();
2805        hints
2806    }
2807}