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