inlay_hint_cache.rs

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