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