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