inlay_hint_cache.rs

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