inlay_hint_cache.rs

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