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