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        ExcerptRange,
1262    };
1263    use futures::StreamExt;
1264    use gpui::{Context, SemanticVersion, TestAppContext, WindowHandle};
1265    use itertools::Itertools;
1266    use language::{
1267        language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
1268        LanguageConfig, LanguageMatcher,
1269    };
1270    use lsp::FakeLanguageServer;
1271    use parking_lot::Mutex;
1272    use project::{FakeFs, Project};
1273    use serde_json::json;
1274    use settings::SettingsStore;
1275    use text::{Point, ToPoint};
1276
1277    use crate::editor_tests::update_test_language_settings;
1278
1279    use super::*;
1280
1281    #[gpui::test]
1282    async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
1283        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1284        init_test(cx, |settings| {
1285            settings.defaults.inlay_hints = Some(InlayHintSettings {
1286                enabled: true,
1287                edit_debounce_ms: 0,
1288                scroll_debounce_ms: 0,
1289                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1290                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1291                show_other_hints: allowed_hint_kinds.contains(&None),
1292                show_background: false,
1293            })
1294        });
1295
1296        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1297        let lsp_request_count = Arc::new(AtomicU32::new(0));
1298        fake_server
1299            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1300                let task_lsp_request_count = Arc::clone(&lsp_request_count);
1301                async move {
1302                    assert_eq!(
1303                        params.text_document.uri,
1304                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1305                    );
1306                    let current_call_id =
1307                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1308                    let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
1309                    for _ in 0..2 {
1310                        let mut i = current_call_id;
1311                        loop {
1312                            new_hints.push(lsp::InlayHint {
1313                                position: lsp::Position::new(0, i),
1314                                label: lsp::InlayHintLabel::String(i.to_string()),
1315                                kind: None,
1316                                text_edits: None,
1317                                tooltip: None,
1318                                padding_left: None,
1319                                padding_right: None,
1320                                data: None,
1321                            });
1322                            if i == 0 {
1323                                break;
1324                            }
1325                            i -= 1;
1326                        }
1327                    }
1328
1329                    Ok(Some(new_hints))
1330                }
1331            })
1332            .next()
1333            .await;
1334        cx.executor().run_until_parked();
1335
1336        let mut edits_made = 1;
1337        editor
1338            .update(cx, |editor, cx| {
1339                let expected_hints = vec!["0".to_string()];
1340                assert_eq!(
1341                    expected_hints,
1342                    cached_hint_labels(editor),
1343                    "Should get its first hints when opening the editor"
1344                );
1345                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1346                let inlay_cache = editor.inlay_hint_cache();
1347                assert_eq!(
1348                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1349                    "Cache should use editor settings to get the allowed hint kinds"
1350                );
1351                assert_eq!(
1352                    inlay_cache.version, edits_made,
1353                    "The editor update the cache version after every cache/view change"
1354                );
1355            })
1356            .unwrap();
1357
1358        editor
1359            .update(cx, |editor, cx| {
1360                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1361                editor.handle_input("some change", cx);
1362                edits_made += 1;
1363            })
1364            .unwrap();
1365        cx.executor().run_until_parked();
1366        editor
1367            .update(cx, |editor, cx| {
1368                let expected_hints = vec!["0".to_string(), "1".to_string()];
1369                assert_eq!(
1370                    expected_hints,
1371                    cached_hint_labels(editor),
1372                    "Should get new hints after an edit"
1373                );
1374                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1375                let inlay_cache = editor.inlay_hint_cache();
1376                assert_eq!(
1377                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1378                    "Cache should use editor settings to get the allowed hint kinds"
1379                );
1380                assert_eq!(
1381                    inlay_cache.version, edits_made,
1382                    "The editor update the cache version after every cache/view change"
1383                );
1384            })
1385            .unwrap();
1386
1387        fake_server
1388            .request::<lsp::request::InlayHintRefreshRequest>(())
1389            .await
1390            .expect("inlay refresh request failed");
1391        edits_made += 1;
1392        cx.executor().run_until_parked();
1393        editor
1394            .update(cx, |editor, cx| {
1395                let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()];
1396                assert_eq!(
1397                    expected_hints,
1398                    cached_hint_labels(editor),
1399                    "Should get new hints after hint refresh/ request"
1400                );
1401                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1402                let inlay_cache = editor.inlay_hint_cache();
1403                assert_eq!(
1404                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1405                    "Cache should use editor settings to get the allowed hint kinds"
1406                );
1407                assert_eq!(
1408                    inlay_cache.version, edits_made,
1409                    "The editor update the cache version after every cache/view change"
1410                );
1411            })
1412            .unwrap();
1413    }
1414
1415    #[gpui::test]
1416    async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
1417        init_test(cx, |settings| {
1418            settings.defaults.inlay_hints = Some(InlayHintSettings {
1419                enabled: true,
1420                edit_debounce_ms: 0,
1421                scroll_debounce_ms: 0,
1422                show_type_hints: true,
1423                show_parameter_hints: true,
1424                show_other_hints: true,
1425                show_background: false,
1426            })
1427        });
1428
1429        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1430        let lsp_request_count = Arc::new(AtomicU32::new(0));
1431        fake_server
1432            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1433                let task_lsp_request_count = Arc::clone(&lsp_request_count);
1434                async move {
1435                    assert_eq!(
1436                        params.text_document.uri,
1437                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1438                    );
1439                    let current_call_id =
1440                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1441                    Ok(Some(vec![lsp::InlayHint {
1442                        position: lsp::Position::new(0, current_call_id),
1443                        label: lsp::InlayHintLabel::String(current_call_id.to_string()),
1444                        kind: None,
1445                        text_edits: None,
1446                        tooltip: None,
1447                        padding_left: None,
1448                        padding_right: None,
1449                        data: None,
1450                    }]))
1451                }
1452            })
1453            .next()
1454            .await;
1455        cx.executor().run_until_parked();
1456
1457        let mut edits_made = 1;
1458        editor
1459            .update(cx, |editor, cx| {
1460                let expected_hints = vec!["0".to_string()];
1461                assert_eq!(
1462                    expected_hints,
1463                    cached_hint_labels(editor),
1464                    "Should get its first hints when opening the editor"
1465                );
1466                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1467                assert_eq!(
1468                    editor.inlay_hint_cache().version,
1469                    edits_made,
1470                    "The editor update the cache version after every cache/view change"
1471                );
1472            })
1473            .unwrap();
1474
1475        let progress_token = "test_progress_token";
1476        fake_server
1477            .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1478                token: lsp::ProgressToken::String(progress_token.to_string()),
1479            })
1480            .await
1481            .expect("work done progress create request failed");
1482        cx.executor().run_until_parked();
1483        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1484            token: lsp::ProgressToken::String(progress_token.to_string()),
1485            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
1486                lsp::WorkDoneProgressBegin::default(),
1487            )),
1488        });
1489        cx.executor().run_until_parked();
1490
1491        editor
1492            .update(cx, |editor, cx| {
1493                let expected_hints = vec!["0".to_string()];
1494                assert_eq!(
1495                    expected_hints,
1496                    cached_hint_labels(editor),
1497                    "Should not update hints while the work task is running"
1498                );
1499                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1500                assert_eq!(
1501                    editor.inlay_hint_cache().version,
1502                    edits_made,
1503                    "Should not update the cache while the work task is running"
1504                );
1505            })
1506            .unwrap();
1507
1508        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1509            token: lsp::ProgressToken::String(progress_token.to_string()),
1510            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
1511                lsp::WorkDoneProgressEnd::default(),
1512            )),
1513        });
1514        cx.executor().run_until_parked();
1515
1516        edits_made += 1;
1517        editor
1518            .update(cx, |editor, cx| {
1519                let expected_hints = vec!["1".to_string()];
1520                assert_eq!(
1521                    expected_hints,
1522                    cached_hint_labels(editor),
1523                    "New hints should be queried after the work task is done"
1524                );
1525                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1526                assert_eq!(
1527                    editor.inlay_hint_cache().version,
1528                    edits_made,
1529                    "Cache version should update once after the work task is done"
1530                );
1531            })
1532            .unwrap();
1533    }
1534
1535    #[gpui::test]
1536    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1537        init_test(cx, |settings| {
1538            settings.defaults.inlay_hints = Some(InlayHintSettings {
1539                enabled: true,
1540                edit_debounce_ms: 0,
1541                scroll_debounce_ms: 0,
1542                show_type_hints: true,
1543                show_parameter_hints: true,
1544                show_other_hints: true,
1545                show_background: false,
1546            })
1547        });
1548
1549        let fs = FakeFs::new(cx.background_executor.clone());
1550        fs.insert_tree(
1551                    "/a",
1552                    json!({
1553                        "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1554                        "other.md": "Test md file with some text",
1555                    }),
1556                )
1557                .await;
1558
1559        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1560
1561        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1562        let mut rs_fake_servers = None;
1563        let mut md_fake_servers = None;
1564        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1565            language_registry.add(Arc::new(Language::new(
1566                LanguageConfig {
1567                    name: name.into(),
1568                    matcher: LanguageMatcher {
1569                        path_suffixes: vec![path_suffix.to_string()],
1570                        ..Default::default()
1571                    },
1572                    ..Default::default()
1573                },
1574                Some(tree_sitter_rust::LANGUAGE.into()),
1575            )));
1576            let fake_servers = language_registry.register_fake_lsp(
1577                name,
1578                FakeLspAdapter {
1579                    name,
1580                    capabilities: lsp::ServerCapabilities {
1581                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1582                        ..Default::default()
1583                    },
1584                    ..Default::default()
1585                },
1586            );
1587            match name {
1588                "Rust" => rs_fake_servers = Some(fake_servers),
1589                "Markdown" => md_fake_servers = Some(fake_servers),
1590                _ => unreachable!(),
1591            }
1592        }
1593
1594        let rs_buffer = project
1595            .update(cx, |project, cx| {
1596                project.open_local_buffer("/a/main.rs", cx)
1597            })
1598            .await
1599            .unwrap();
1600        cx.executor().run_until_parked();
1601        cx.executor().start_waiting();
1602        let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1603        let rs_editor =
1604            cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
1605        let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1606        rs_fake_server
1607            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1608                let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
1609                async move {
1610                    assert_eq!(
1611                        params.text_document.uri,
1612                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1613                    );
1614                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1615                    Ok(Some(vec![lsp::InlayHint {
1616                        position: lsp::Position::new(0, i),
1617                        label: lsp::InlayHintLabel::String(i.to_string()),
1618                        kind: None,
1619                        text_edits: None,
1620                        tooltip: None,
1621                        padding_left: None,
1622                        padding_right: None,
1623                        data: None,
1624                    }]))
1625                }
1626            })
1627            .next()
1628            .await;
1629        cx.executor().run_until_parked();
1630        rs_editor
1631            .update(cx, |editor, cx| {
1632                let expected_hints = vec!["0".to_string()];
1633                assert_eq!(
1634                    expected_hints,
1635                    cached_hint_labels(editor),
1636                    "Should get its first hints when opening the editor"
1637                );
1638                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1639                assert_eq!(
1640                    editor.inlay_hint_cache().version,
1641                    1,
1642                    "Rust editor update the cache version after every cache/view change"
1643                );
1644            })
1645            .unwrap();
1646
1647        cx.executor().run_until_parked();
1648        let md_buffer = project
1649            .update(cx, |project, cx| {
1650                project.open_local_buffer("/a/other.md", cx)
1651            })
1652            .await
1653            .unwrap();
1654        cx.executor().run_until_parked();
1655        cx.executor().start_waiting();
1656        let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1657        let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
1658        let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1659        md_fake_server
1660            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1661                let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
1662                async move {
1663                    assert_eq!(
1664                        params.text_document.uri,
1665                        lsp::Url::from_file_path("/a/other.md").unwrap(),
1666                    );
1667                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1668                    Ok(Some(vec![lsp::InlayHint {
1669                        position: lsp::Position::new(0, i),
1670                        label: lsp::InlayHintLabel::String(i.to_string()),
1671                        kind: None,
1672                        text_edits: None,
1673                        tooltip: None,
1674                        padding_left: None,
1675                        padding_right: None,
1676                        data: None,
1677                    }]))
1678                }
1679            })
1680            .next()
1681            .await;
1682        cx.executor().run_until_parked();
1683        md_editor
1684            .update(cx, |editor, cx| {
1685                let expected_hints = vec!["0".to_string()];
1686                assert_eq!(
1687                    expected_hints,
1688                    cached_hint_labels(editor),
1689                    "Markdown editor should have a separate version, repeating Rust editor rules"
1690                );
1691                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1692                assert_eq!(editor.inlay_hint_cache().version, 1);
1693            })
1694            .unwrap();
1695
1696        rs_editor
1697            .update(cx, |editor, cx| {
1698                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1699                editor.handle_input("some rs change", cx);
1700            })
1701            .unwrap();
1702        cx.executor().run_until_parked();
1703        rs_editor
1704            .update(cx, |editor, cx| {
1705                let expected_hints = vec!["1".to_string()];
1706                assert_eq!(
1707                    expected_hints,
1708                    cached_hint_labels(editor),
1709                    "Rust inlay cache should change after the edit"
1710                );
1711                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1712                assert_eq!(
1713                    editor.inlay_hint_cache().version,
1714                    2,
1715                    "Every time hint cache changes, cache version should be incremented"
1716                );
1717            })
1718            .unwrap();
1719        md_editor
1720            .update(cx, |editor, cx| {
1721                let expected_hints = vec!["0".to_string()];
1722                assert_eq!(
1723                    expected_hints,
1724                    cached_hint_labels(editor),
1725                    "Markdown editor should not be affected by Rust editor changes"
1726                );
1727                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1728                assert_eq!(editor.inlay_hint_cache().version, 1);
1729            })
1730            .unwrap();
1731
1732        md_editor
1733            .update(cx, |editor, cx| {
1734                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1735                editor.handle_input("some md change", cx);
1736            })
1737            .unwrap();
1738        cx.executor().run_until_parked();
1739        md_editor
1740            .update(cx, |editor, cx| {
1741                let expected_hints = vec!["1".to_string()];
1742                assert_eq!(
1743                    expected_hints,
1744                    cached_hint_labels(editor),
1745                    "Rust editor should not be affected by Markdown editor changes"
1746                );
1747                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1748                assert_eq!(editor.inlay_hint_cache().version, 2);
1749            })
1750            .unwrap();
1751        rs_editor
1752            .update(cx, |editor, cx| {
1753                let expected_hints = vec!["1".to_string()];
1754                assert_eq!(
1755                    expected_hints,
1756                    cached_hint_labels(editor),
1757                    "Markdown editor should also change independently"
1758                );
1759                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1760                assert_eq!(editor.inlay_hint_cache().version, 2);
1761            })
1762            .unwrap();
1763    }
1764
1765    #[gpui::test]
1766    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1767        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1768        init_test(cx, |settings| {
1769            settings.defaults.inlay_hints = Some(InlayHintSettings {
1770                enabled: true,
1771                edit_debounce_ms: 0,
1772                scroll_debounce_ms: 0,
1773                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1774                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1775                show_other_hints: allowed_hint_kinds.contains(&None),
1776                show_background: false,
1777            })
1778        });
1779
1780        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1781        let lsp_request_count = Arc::new(AtomicU32::new(0));
1782        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1783        fake_server
1784            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1785                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1786                async move {
1787                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1788                    assert_eq!(
1789                        params.text_document.uri,
1790                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1791                    );
1792                    Ok(Some(vec![
1793                        lsp::InlayHint {
1794                            position: lsp::Position::new(0, 1),
1795                            label: lsp::InlayHintLabel::String("type hint".to_string()),
1796                            kind: Some(lsp::InlayHintKind::TYPE),
1797                            text_edits: None,
1798                            tooltip: None,
1799                            padding_left: None,
1800                            padding_right: None,
1801                            data: None,
1802                        },
1803                        lsp::InlayHint {
1804                            position: lsp::Position::new(0, 2),
1805                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1806                            kind: Some(lsp::InlayHintKind::PARAMETER),
1807                            text_edits: None,
1808                            tooltip: None,
1809                            padding_left: None,
1810                            padding_right: None,
1811                            data: None,
1812                        },
1813                        lsp::InlayHint {
1814                            position: lsp::Position::new(0, 3),
1815                            label: lsp::InlayHintLabel::String("other hint".to_string()),
1816                            kind: None,
1817                            text_edits: None,
1818                            tooltip: None,
1819                            padding_left: None,
1820                            padding_right: None,
1821                            data: None,
1822                        },
1823                    ]))
1824                }
1825            })
1826            .next()
1827            .await;
1828        cx.executor().run_until_parked();
1829
1830        let mut edits_made = 1;
1831        editor
1832            .update(cx, |editor, cx| {
1833                assert_eq!(
1834                    lsp_request_count.load(Ordering::Relaxed),
1835                    1,
1836                    "Should query new hints once"
1837                );
1838                assert_eq!(
1839                    vec![
1840                        "other hint".to_string(),
1841                        "parameter hint".to_string(),
1842                        "type hint".to_string(),
1843                    ],
1844                    cached_hint_labels(editor),
1845                    "Should get its first hints when opening the editor"
1846                );
1847                assert_eq!(
1848                    vec!["other hint".to_string(), "type hint".to_string()],
1849                    visible_hint_labels(editor, cx)
1850                );
1851                let inlay_cache = editor.inlay_hint_cache();
1852                assert_eq!(
1853                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1854                    "Cache should use editor settings to get the allowed hint kinds"
1855                );
1856                assert_eq!(
1857                    inlay_cache.version, edits_made,
1858                    "The editor update the cache version after every cache/view change"
1859                );
1860            })
1861            .unwrap();
1862
1863        fake_server
1864            .request::<lsp::request::InlayHintRefreshRequest>(())
1865            .await
1866            .expect("inlay refresh request failed");
1867        cx.executor().run_until_parked();
1868        editor
1869            .update(cx, |editor, cx| {
1870                assert_eq!(
1871                    lsp_request_count.load(Ordering::Relaxed),
1872                    2,
1873                    "Should load new hints twice"
1874                );
1875                assert_eq!(
1876                    vec![
1877                        "other hint".to_string(),
1878                        "parameter hint".to_string(),
1879                        "type hint".to_string(),
1880                    ],
1881                    cached_hint_labels(editor),
1882                    "Cached hints should not change due to allowed hint kinds settings update"
1883                );
1884                assert_eq!(
1885                    vec!["other hint".to_string(), "type hint".to_string()],
1886                    visible_hint_labels(editor, cx)
1887                );
1888                assert_eq!(
1889                    editor.inlay_hint_cache().version,
1890                    edits_made,
1891                    "Should not update cache version due to new loaded hints being the same"
1892                );
1893            })
1894            .unwrap();
1895
1896        for (new_allowed_hint_kinds, expected_visible_hints) in [
1897            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1898            (
1899                HashSet::from_iter([Some(InlayHintKind::Type)]),
1900                vec!["type hint".to_string()],
1901            ),
1902            (
1903                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1904                vec!["parameter hint".to_string()],
1905            ),
1906            (
1907                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1908                vec!["other hint".to_string(), "type hint".to_string()],
1909            ),
1910            (
1911                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1912                vec!["other hint".to_string(), "parameter hint".to_string()],
1913            ),
1914            (
1915                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1916                vec!["parameter hint".to_string(), "type hint".to_string()],
1917            ),
1918            (
1919                HashSet::from_iter([
1920                    None,
1921                    Some(InlayHintKind::Type),
1922                    Some(InlayHintKind::Parameter),
1923                ]),
1924                vec![
1925                    "other hint".to_string(),
1926                    "parameter hint".to_string(),
1927                    "type hint".to_string(),
1928                ],
1929            ),
1930        ] {
1931            edits_made += 1;
1932            update_test_language_settings(cx, |settings| {
1933                settings.defaults.inlay_hints = Some(InlayHintSettings {
1934                    enabled: true,
1935                    edit_debounce_ms: 0,
1936                    scroll_debounce_ms: 0,
1937                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1938                    show_parameter_hints: new_allowed_hint_kinds
1939                        .contains(&Some(InlayHintKind::Parameter)),
1940                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1941                    show_background: false,
1942                })
1943            });
1944            cx.executor().run_until_parked();
1945            editor.update(cx, |editor, cx| {
1946                assert_eq!(
1947                    lsp_request_count.load(Ordering::Relaxed),
1948                    2,
1949                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1950                );
1951                assert_eq!(
1952                    vec![
1953                        "other hint".to_string(),
1954                        "parameter hint".to_string(),
1955                        "type hint".to_string(),
1956                    ],
1957                    cached_hint_labels(editor),
1958                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1959                );
1960                assert_eq!(
1961                    expected_visible_hints,
1962                    visible_hint_labels(editor, cx),
1963                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1964                );
1965                let inlay_cache = editor.inlay_hint_cache();
1966                assert_eq!(
1967                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1968                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1969                );
1970                assert_eq!(
1971                    inlay_cache.version, edits_made,
1972                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1973                );
1974            }).unwrap();
1975        }
1976
1977        edits_made += 1;
1978        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1979        update_test_language_settings(cx, |settings| {
1980            settings.defaults.inlay_hints = Some(InlayHintSettings {
1981                enabled: false,
1982                edit_debounce_ms: 0,
1983                scroll_debounce_ms: 0,
1984                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1985                show_parameter_hints: another_allowed_hint_kinds
1986                    .contains(&Some(InlayHintKind::Parameter)),
1987                show_other_hints: another_allowed_hint_kinds.contains(&None),
1988                show_background: false,
1989            })
1990        });
1991        cx.executor().run_until_parked();
1992        editor
1993            .update(cx, |editor, cx| {
1994                assert_eq!(
1995                    lsp_request_count.load(Ordering::Relaxed),
1996                    2,
1997                    "Should not load new hints when hints got disabled"
1998                );
1999                assert!(
2000                    cached_hint_labels(editor).is_empty(),
2001                    "Should clear the cache when hints got disabled"
2002                );
2003                assert!(
2004                    visible_hint_labels(editor, cx).is_empty(),
2005                    "Should clear visible hints when hints got disabled"
2006                );
2007                let inlay_cache = editor.inlay_hint_cache();
2008                assert_eq!(
2009                    inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
2010                    "Should update its allowed hint kinds even when hints got disabled"
2011                );
2012                assert_eq!(
2013                    inlay_cache.version, edits_made,
2014                    "The editor should update the cache version after hints got disabled"
2015                );
2016            })
2017            .unwrap();
2018
2019        fake_server
2020            .request::<lsp::request::InlayHintRefreshRequest>(())
2021            .await
2022            .expect("inlay refresh request failed");
2023        cx.executor().run_until_parked();
2024        editor.update(cx, |editor, cx| {
2025            assert_eq!(
2026                lsp_request_count.load(Ordering::Relaxed),
2027                2,
2028                "Should not load new hints when they got disabled"
2029            );
2030            assert!(cached_hint_labels(editor).is_empty());
2031            assert!(visible_hint_labels(editor, cx).is_empty());
2032            assert_eq!(
2033                editor.inlay_hint_cache().version, edits_made,
2034                "The editor should not update the cache version after /refresh query without updates"
2035            );
2036        }).unwrap();
2037
2038        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
2039        edits_made += 1;
2040        update_test_language_settings(cx, |settings| {
2041            settings.defaults.inlay_hints = Some(InlayHintSettings {
2042                enabled: true,
2043                edit_debounce_ms: 0,
2044                scroll_debounce_ms: 0,
2045                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
2046                show_parameter_hints: final_allowed_hint_kinds
2047                    .contains(&Some(InlayHintKind::Parameter)),
2048                show_other_hints: final_allowed_hint_kinds.contains(&None),
2049                show_background: false,
2050            })
2051        });
2052        cx.executor().run_until_parked();
2053        editor
2054            .update(cx, |editor, cx| {
2055                assert_eq!(
2056                    lsp_request_count.load(Ordering::Relaxed),
2057                    3,
2058                    "Should query for new hints when they got re-enabled"
2059                );
2060                assert_eq!(
2061                    vec![
2062                        "other hint".to_string(),
2063                        "parameter hint".to_string(),
2064                        "type hint".to_string(),
2065                    ],
2066                    cached_hint_labels(editor),
2067                    "Should get its cached hints fully repopulated after the hints got re-enabled"
2068                );
2069                assert_eq!(
2070                    vec!["parameter hint".to_string()],
2071                    visible_hint_labels(editor, cx),
2072                    "Should get its visible hints repopulated and filtered after the h"
2073                );
2074                let inlay_cache = editor.inlay_hint_cache();
2075                assert_eq!(
2076                    inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
2077                    "Cache should update editor settings when hints got re-enabled"
2078                );
2079                assert_eq!(
2080                    inlay_cache.version, edits_made,
2081                    "Cache should update its version after hints got re-enabled"
2082                );
2083            })
2084            .unwrap();
2085
2086        fake_server
2087            .request::<lsp::request::InlayHintRefreshRequest>(())
2088            .await
2089            .expect("inlay refresh request failed");
2090        cx.executor().run_until_parked();
2091        editor
2092            .update(cx, |editor, cx| {
2093                assert_eq!(
2094                    lsp_request_count.load(Ordering::Relaxed),
2095                    4,
2096                    "Should query for new hints again"
2097                );
2098                assert_eq!(
2099                    vec![
2100                        "other hint".to_string(),
2101                        "parameter hint".to_string(),
2102                        "type hint".to_string(),
2103                    ],
2104                    cached_hint_labels(editor),
2105                );
2106                assert_eq!(
2107                    vec!["parameter hint".to_string()],
2108                    visible_hint_labels(editor, cx),
2109                );
2110                assert_eq!(editor.inlay_hint_cache().version, edits_made);
2111            })
2112            .unwrap();
2113    }
2114
2115    #[gpui::test]
2116    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
2117        init_test(cx, |settings| {
2118            settings.defaults.inlay_hints = Some(InlayHintSettings {
2119                enabled: true,
2120                edit_debounce_ms: 0,
2121                scroll_debounce_ms: 0,
2122                show_type_hints: true,
2123                show_parameter_hints: true,
2124                show_other_hints: true,
2125                show_background: false,
2126            })
2127        });
2128
2129        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
2130        let fake_server = Arc::new(fake_server);
2131        let lsp_request_count = Arc::new(AtomicU32::new(0));
2132        let another_lsp_request_count = Arc::clone(&lsp_request_count);
2133        fake_server
2134            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2135                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
2136                async move {
2137                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
2138                    assert_eq!(
2139                        params.text_document.uri,
2140                        lsp::Url::from_file_path(file_with_hints).unwrap(),
2141                    );
2142                    Ok(Some(vec![lsp::InlayHint {
2143                        position: lsp::Position::new(0, i),
2144                        label: lsp::InlayHintLabel::String(i.to_string()),
2145                        kind: None,
2146                        text_edits: None,
2147                        tooltip: None,
2148                        padding_left: None,
2149                        padding_right: None,
2150                        data: None,
2151                    }]))
2152                }
2153            })
2154            .next()
2155            .await;
2156
2157        let mut expected_changes = Vec::new();
2158        for change_after_opening in [
2159            "initial change #1",
2160            "initial change #2",
2161            "initial change #3",
2162        ] {
2163            editor
2164                .update(cx, |editor, cx| {
2165                    editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
2166                    editor.handle_input(change_after_opening, cx);
2167                })
2168                .unwrap();
2169            expected_changes.push(change_after_opening);
2170        }
2171
2172        cx.executor().run_until_parked();
2173
2174        editor.update(cx, |editor, cx| {
2175            let current_text = editor.text(cx);
2176            for change in &expected_changes {
2177                assert!(
2178                    current_text.contains(change),
2179                    "Should apply all changes made"
2180                );
2181            }
2182            assert_eq!(
2183                lsp_request_count.load(Ordering::Relaxed),
2184                2,
2185                "Should query new hints twice: for editor init and for the last edit that interrupted all others"
2186            );
2187            let expected_hints = vec!["2".to_string()];
2188            assert_eq!(
2189                expected_hints,
2190                cached_hint_labels(editor),
2191                "Should get hints from the last edit landed only"
2192            );
2193            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2194            assert_eq!(
2195                editor.inlay_hint_cache().version, 1,
2196                "Only one update should be registered in the cache after all cancellations"
2197            );
2198        }).unwrap();
2199
2200        let mut edits = Vec::new();
2201        for async_later_change in [
2202            "another change #1",
2203            "another change #2",
2204            "another change #3",
2205        ] {
2206            expected_changes.push(async_later_change);
2207            let task_editor = editor;
2208            edits.push(cx.spawn(|mut cx| async move {
2209                task_editor
2210                    .update(&mut cx, |editor, cx| {
2211                        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
2212                        editor.handle_input(async_later_change, cx);
2213                    })
2214                    .unwrap();
2215            }));
2216        }
2217        let _ = future::join_all(edits).await;
2218        cx.executor().run_until_parked();
2219
2220        editor
2221            .update(cx, |editor, cx| {
2222                let current_text = editor.text(cx);
2223                for change in &expected_changes {
2224                    assert!(
2225                        current_text.contains(change),
2226                        "Should apply all changes made"
2227                    );
2228                }
2229                assert_eq!(
2230                    lsp_request_count.load(Ordering::SeqCst),
2231                    3,
2232                    "Should query new hints one more time, for the last edit only"
2233                );
2234                let expected_hints = vec!["3".to_string()];
2235                assert_eq!(
2236                    expected_hints,
2237                    cached_hint_labels(editor),
2238                    "Should get hints from the last edit landed only"
2239                );
2240                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2241                assert_eq!(
2242                    editor.inlay_hint_cache().version,
2243                    2,
2244                    "Should update the cache version once more, for the new change"
2245                );
2246            })
2247            .unwrap();
2248    }
2249
2250    #[gpui::test(iterations = 10)]
2251    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
2252        init_test(cx, |settings| {
2253            settings.defaults.inlay_hints = Some(InlayHintSettings {
2254                enabled: true,
2255                edit_debounce_ms: 0,
2256                scroll_debounce_ms: 0,
2257                show_type_hints: true,
2258                show_parameter_hints: true,
2259                show_other_hints: true,
2260                show_background: false,
2261            })
2262        });
2263
2264        let fs = FakeFs::new(cx.background_executor.clone());
2265        fs.insert_tree(
2266            "/a",
2267            json!({
2268                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
2269                "other.rs": "// Test file",
2270            }),
2271        )
2272        .await;
2273
2274        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2275
2276        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2277        language_registry.add(crate::editor_tests::rust_lang());
2278        let mut fake_servers = language_registry.register_fake_lsp(
2279            "Rust",
2280            FakeLspAdapter {
2281                capabilities: lsp::ServerCapabilities {
2282                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2283                    ..Default::default()
2284                },
2285                ..Default::default()
2286            },
2287        );
2288
2289        let buffer = project
2290            .update(cx, |project, cx| {
2291                project.open_local_buffer("/a/main.rs", cx)
2292            })
2293            .await
2294            .unwrap();
2295        cx.executor().run_until_parked();
2296        cx.executor().start_waiting();
2297        let fake_server = fake_servers.next().await.unwrap();
2298        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
2299        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
2300        let lsp_request_count = Arc::new(AtomicUsize::new(0));
2301        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
2302        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2303        fake_server
2304            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2305                let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
2306                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2307                async move {
2308                    assert_eq!(
2309                        params.text_document.uri,
2310                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
2311                    );
2312
2313                    task_lsp_request_ranges.lock().push(params.range);
2314                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
2315                    Ok(Some(vec![lsp::InlayHint {
2316                        position: params.range.end,
2317                        label: lsp::InlayHintLabel::String(i.to_string()),
2318                        kind: None,
2319                        text_edits: None,
2320                        tooltip: None,
2321                        padding_left: None,
2322                        padding_right: None,
2323                        data: None,
2324                    }]))
2325                }
2326            })
2327            .next()
2328            .await;
2329
2330        fn editor_visible_range(
2331            editor: &WindowHandle<Editor>,
2332            cx: &mut gpui::TestAppContext,
2333        ) -> Range<Point> {
2334            let ranges = editor
2335                .update(cx, |editor, cx| {
2336                    editor.excerpts_for_inlay_hints_query(None, cx)
2337                })
2338                .unwrap();
2339            assert_eq!(
2340                ranges.len(),
2341                1,
2342                "Single buffer should produce a single excerpt with visible range"
2343            );
2344            let (_, (excerpt_buffer, _, excerpt_visible_range)) =
2345                ranges.into_iter().next().unwrap();
2346            excerpt_buffer.update(cx, |buffer, _| {
2347                let snapshot = buffer.snapshot();
2348                let start = buffer
2349                    .anchor_before(excerpt_visible_range.start)
2350                    .to_point(&snapshot);
2351                let end = buffer
2352                    .anchor_after(excerpt_visible_range.end)
2353                    .to_point(&snapshot);
2354                start..end
2355            })
2356        }
2357
2358        // in large buffers, requests are made for more than visible range of a buffer.
2359        // invisible parts are queried later, to avoid excessive requests on quick typing.
2360        // wait the timeout needed to get all requests.
2361        cx.executor().advance_clock(Duration::from_millis(
2362            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2363        ));
2364        cx.executor().run_until_parked();
2365        let initial_visible_range = editor_visible_range(&editor, cx);
2366        let lsp_initial_visible_range = lsp::Range::new(
2367            lsp::Position::new(
2368                initial_visible_range.start.row,
2369                initial_visible_range.start.column,
2370            ),
2371            lsp::Position::new(
2372                initial_visible_range.end.row,
2373                initial_visible_range.end.column,
2374            ),
2375        );
2376        let expected_initial_query_range_end =
2377            lsp::Position::new(initial_visible_range.end.row * 2, 2);
2378        let mut expected_invisible_query_start = lsp_initial_visible_range.end;
2379        expected_invisible_query_start.character += 1;
2380        editor.update(cx, |editor, cx| {
2381            let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2382            assert_eq!(ranges.len(), 2,
2383                "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:?}");
2384            let visible_query_range = &ranges[0];
2385            assert_eq!(visible_query_range.start, lsp_initial_visible_range.start);
2386            assert_eq!(visible_query_range.end, lsp_initial_visible_range.end);
2387            let invisible_query_range = &ranges[1];
2388
2389            assert_eq!(invisible_query_range.start, expected_invisible_query_start, "Should initially query visible edge of the document");
2390            assert_eq!(invisible_query_range.end, expected_initial_query_range_end, "Should initially query visible edge of the document");
2391
2392            let requests_count = lsp_request_count.load(Ordering::Acquire);
2393            assert_eq!(requests_count, 2, "Visible + invisible request");
2394            let expected_hints = vec!["1".to_string(), "2".to_string()];
2395            assert_eq!(
2396                expected_hints,
2397                cached_hint_labels(editor),
2398                "Should have hints from both LSP requests made for a big file"
2399            );
2400            assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
2401            assert_eq!(
2402                editor.inlay_hint_cache().version, requests_count,
2403                "LSP queries should've bumped the cache version"
2404            );
2405        }).unwrap();
2406
2407        editor
2408            .update(cx, |editor, cx| {
2409                editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
2410            })
2411            .unwrap();
2412        cx.executor().run_until_parked();
2413        editor
2414            .update(cx, |editor, cx| {
2415                editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
2416            })
2417            .unwrap();
2418        cx.executor().advance_clock(Duration::from_millis(
2419            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2420        ));
2421        cx.executor().run_until_parked();
2422        let visible_range_after_scrolls = editor_visible_range(&editor, cx);
2423        let visible_line_count = editor
2424            .update(cx, |editor, _| editor.visible_line_count().unwrap())
2425            .unwrap();
2426        let selection_in_cached_range = editor
2427            .update(cx, |editor, cx| {
2428                let ranges = lsp_request_ranges
2429                    .lock()
2430                    .drain(..)
2431                    .sorted_by_key(|r| r.start)
2432                    .collect::<Vec<_>>();
2433                assert_eq!(
2434                    ranges.len(),
2435                    2,
2436                    "Should query 2 ranges after both scrolls, but got: {ranges:?}"
2437                );
2438                let first_scroll = &ranges[0];
2439                let second_scroll = &ranges[1];
2440                assert_eq!(
2441                    first_scroll.end, second_scroll.start,
2442                    "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
2443                );
2444                assert_eq!(
2445                first_scroll.start, expected_initial_query_range_end,
2446                "First scroll should start the query right after the end of the original scroll",
2447            );
2448                assert_eq!(
2449                second_scroll.end,
2450                lsp::Position::new(
2451                    visible_range_after_scrolls.end.row
2452                        + visible_line_count.ceil() as u32,
2453                    1,
2454                ),
2455                "Second scroll should query one more screen down after the end of the visible range"
2456            );
2457
2458                let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2459                assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
2460                let expected_hints = vec![
2461                    "1".to_string(),
2462                    "2".to_string(),
2463                    "3".to_string(),
2464                    "4".to_string(),
2465                ];
2466                assert_eq!(
2467                    expected_hints,
2468                    cached_hint_labels(editor),
2469                    "Should have hints from the new LSP response after the edit"
2470                );
2471                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2472                assert_eq!(
2473                    editor.inlay_hint_cache().version,
2474                    lsp_requests,
2475                    "Should update the cache for every LSP response with hints added"
2476                );
2477
2478                let mut selection_in_cached_range = visible_range_after_scrolls.end;
2479                selection_in_cached_range.row -= visible_line_count.ceil() as u32;
2480                selection_in_cached_range
2481            })
2482            .unwrap();
2483
2484        editor
2485            .update(cx, |editor, cx| {
2486                editor.change_selections(Some(Autoscroll::center()), cx, |s| {
2487                    s.select_ranges([selection_in_cached_range..selection_in_cached_range])
2488                });
2489            })
2490            .unwrap();
2491        cx.executor().advance_clock(Duration::from_millis(
2492            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2493        ));
2494        cx.executor().run_until_parked();
2495        editor.update(cx, |_, _| {
2496            let ranges = lsp_request_ranges
2497                .lock()
2498                .drain(..)
2499                .sorted_by_key(|r| r.start)
2500                .collect::<Vec<_>>();
2501            assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
2502            assert_eq!(lsp_request_count.load(Ordering::Acquire), 4);
2503        }).unwrap();
2504
2505        editor
2506            .update(cx, |editor, cx| {
2507                editor.handle_input("++++more text++++", cx);
2508            })
2509            .unwrap();
2510        cx.executor().advance_clock(Duration::from_millis(
2511            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2512        ));
2513        cx.executor().run_until_parked();
2514        editor.update(cx, |editor, cx| {
2515            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2516            ranges.sort_by_key(|r| r.start);
2517
2518            assert_eq!(ranges.len(), 3,
2519                "On edit, should scroll to selection and query a range around it: visible + same range above and below. Instead, got query ranges {ranges:?}");
2520            let above_query_range = &ranges[0];
2521            let visible_query_range = &ranges[1];
2522            let below_query_range = &ranges[2];
2523            assert!(above_query_range.end.character < visible_query_range.start.character || above_query_range.end.line + 1 == visible_query_range.start.line,
2524                "Above range {above_query_range:?} should be before visible range {visible_query_range:?}");
2525            assert!(visible_query_range.end.character < below_query_range.start.character || visible_query_range.end.line  + 1 == below_query_range.start.line,
2526                "Visible range {visible_query_range:?} should be before below range {below_query_range:?}");
2527            assert!(above_query_range.start.line < selection_in_cached_range.row,
2528                "Hints should be queried with the selected range after the query range start");
2529            assert!(below_query_range.end.line > selection_in_cached_range.row,
2530                "Hints should be queried with the selected range before the query range end");
2531            assert!(above_query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
2532                "Hints query range should contain one more screen before");
2533            assert!(below_query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
2534                "Hints query range should contain one more screen after");
2535
2536            let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2537            assert_eq!(lsp_requests, 7, "There should be a visible range and two ranges above and below it queried");
2538            let expected_hints = vec!["5".to_string(), "6".to_string(), "7".to_string()];
2539            assert_eq!(expected_hints, cached_hint_labels(editor),
2540                "Should have hints from the new LSP response after the edit");
2541            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2542            assert_eq!(editor.inlay_hint_cache().version, lsp_requests, "Should update the cache for every LSP response with hints added");
2543        }).unwrap();
2544    }
2545
2546    #[gpui::test(iterations = 30)]
2547    async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
2548        init_test(cx, |settings| {
2549            settings.defaults.inlay_hints = Some(InlayHintSettings {
2550                enabled: true,
2551                edit_debounce_ms: 0,
2552                scroll_debounce_ms: 0,
2553                show_type_hints: true,
2554                show_parameter_hints: true,
2555                show_other_hints: true,
2556                show_background: false,
2557            })
2558        });
2559
2560        let fs = FakeFs::new(cx.background_executor.clone());
2561        fs.insert_tree(
2562                "/a",
2563                json!({
2564                    "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2565                    "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2566                }),
2567            )
2568            .await;
2569
2570        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2571
2572        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2573        let language = crate::editor_tests::rust_lang();
2574        language_registry.add(language);
2575        let mut fake_servers = language_registry.register_fake_lsp(
2576            "Rust",
2577            FakeLspAdapter {
2578                capabilities: lsp::ServerCapabilities {
2579                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2580                    ..Default::default()
2581                },
2582                ..Default::default()
2583            },
2584        );
2585
2586        let worktree_id = project.update(cx, |project, cx| {
2587            project.worktrees(cx).next().unwrap().read(cx).id()
2588        });
2589
2590        let buffer_1 = project
2591            .update(cx, |project, cx| {
2592                project.open_buffer((worktree_id, "main.rs"), cx)
2593            })
2594            .await
2595            .unwrap();
2596        let buffer_2 = project
2597            .update(cx, |project, cx| {
2598                project.open_buffer((worktree_id, "other.rs"), cx)
2599            })
2600            .await
2601            .unwrap();
2602        let multibuffer = cx.new_model(|cx| {
2603            let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2604            multibuffer.push_excerpts(
2605                buffer_1.clone(),
2606                [
2607                    ExcerptRange {
2608                        context: Point::new(0, 0)..Point::new(2, 0),
2609                        primary: None,
2610                    },
2611                    ExcerptRange {
2612                        context: Point::new(4, 0)..Point::new(11, 0),
2613                        primary: None,
2614                    },
2615                    ExcerptRange {
2616                        context: Point::new(22, 0)..Point::new(33, 0),
2617                        primary: None,
2618                    },
2619                    ExcerptRange {
2620                        context: Point::new(44, 0)..Point::new(55, 0),
2621                        primary: None,
2622                    },
2623                    ExcerptRange {
2624                        context: Point::new(56, 0)..Point::new(66, 0),
2625                        primary: None,
2626                    },
2627                    ExcerptRange {
2628                        context: Point::new(67, 0)..Point::new(77, 0),
2629                        primary: None,
2630                    },
2631                ],
2632                cx,
2633            );
2634            multibuffer.push_excerpts(
2635                buffer_2.clone(),
2636                [
2637                    ExcerptRange {
2638                        context: Point::new(0, 1)..Point::new(2, 1),
2639                        primary: None,
2640                    },
2641                    ExcerptRange {
2642                        context: Point::new(4, 1)..Point::new(11, 1),
2643                        primary: None,
2644                    },
2645                    ExcerptRange {
2646                        context: Point::new(22, 1)..Point::new(33, 1),
2647                        primary: None,
2648                    },
2649                    ExcerptRange {
2650                        context: Point::new(44, 1)..Point::new(55, 1),
2651                        primary: None,
2652                    },
2653                    ExcerptRange {
2654                        context: Point::new(56, 1)..Point::new(66, 1),
2655                        primary: None,
2656                    },
2657                    ExcerptRange {
2658                        context: Point::new(67, 1)..Point::new(77, 1),
2659                        primary: None,
2660                    },
2661                ],
2662                cx,
2663            );
2664            multibuffer
2665        });
2666
2667        cx.executor().run_until_parked();
2668        let editor = cx
2669            .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx));
2670
2671        let editor_edited = Arc::new(AtomicBool::new(false));
2672        let fake_server = fake_servers.next().await.unwrap();
2673        let closure_editor_edited = Arc::clone(&editor_edited);
2674        fake_server
2675            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2676                let task_editor_edited = Arc::clone(&closure_editor_edited);
2677                async move {
2678                    let hint_text = if params.text_document.uri
2679                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2680                    {
2681                        "main hint"
2682                    } else if params.text_document.uri
2683                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2684                    {
2685                        "other hint"
2686                    } else {
2687                        panic!("unexpected uri: {:?}", params.text_document.uri);
2688                    };
2689
2690                    // one hint per excerpt
2691                    let positions = [
2692                        lsp::Position::new(0, 2),
2693                        lsp::Position::new(4, 2),
2694                        lsp::Position::new(22, 2),
2695                        lsp::Position::new(44, 2),
2696                        lsp::Position::new(56, 2),
2697                        lsp::Position::new(67, 2),
2698                    ];
2699                    let out_of_range_hint = lsp::InlayHint {
2700                        position: lsp::Position::new(
2701                            params.range.start.line + 99,
2702                            params.range.start.character + 99,
2703                        ),
2704                        label: lsp::InlayHintLabel::String(
2705                            "out of excerpt range, should be ignored".to_string(),
2706                        ),
2707                        kind: None,
2708                        text_edits: None,
2709                        tooltip: None,
2710                        padding_left: None,
2711                        padding_right: None,
2712                        data: None,
2713                    };
2714
2715                    let edited = task_editor_edited.load(Ordering::Acquire);
2716                    Ok(Some(
2717                        std::iter::once(out_of_range_hint)
2718                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2719                                lsp::InlayHint {
2720                                    position,
2721                                    label: lsp::InlayHintLabel::String(format!(
2722                                        "{hint_text}{} #{i}",
2723                                        if edited { "(edited)" } else { "" },
2724                                    )),
2725                                    kind: None,
2726                                    text_edits: None,
2727                                    tooltip: None,
2728                                    padding_left: None,
2729                                    padding_right: None,
2730                                    data: None,
2731                                }
2732                            }))
2733                            .collect(),
2734                    ))
2735                }
2736            })
2737            .next()
2738            .await;
2739        cx.executor().run_until_parked();
2740
2741        editor.update(cx, |editor, cx| {
2742                let expected_hints = vec![
2743                    "main hint #0".to_string(),
2744                    "main hint #1".to_string(),
2745                    "main hint #2".to_string(),
2746                    "main hint #3".to_string(),
2747                    "main hint #4".to_string(),
2748                    "main hint #5".to_string(),
2749                ];
2750                assert_eq!(
2751                    expected_hints,
2752                    cached_hint_labels(editor),
2753                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2754                );
2755                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2756                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the version");
2757            }).unwrap();
2758
2759        editor
2760            .update(cx, |editor, cx| {
2761                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2762                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2763                });
2764                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2765                    s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2766                });
2767                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2768                    s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2769                });
2770            })
2771            .unwrap();
2772        cx.executor().run_until_parked();
2773        editor.update(cx, |editor, cx| {
2774                let expected_hints = vec![
2775                    "main hint #0".to_string(),
2776                    "main hint #1".to_string(),
2777                    "main hint #2".to_string(),
2778                    "main hint #3".to_string(),
2779                    "main hint #4".to_string(),
2780                    "main hint #5".to_string(),
2781                    "other hint #0".to_string(),
2782                    "other hint #1".to_string(),
2783                    "other hint #2".to_string(),
2784                ];
2785                assert_eq!(expected_hints, cached_hint_labels(editor),
2786                    "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2787                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2788                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
2789                    "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
2790            }).unwrap();
2791
2792        editor
2793            .update(cx, |editor, cx| {
2794                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2795                    s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2796                });
2797            })
2798            .unwrap();
2799        cx.executor().advance_clock(Duration::from_millis(
2800            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2801        ));
2802        cx.executor().run_until_parked();
2803        let last_scroll_update_version = editor.update(cx, |editor, cx| {
2804                let expected_hints = vec![
2805                    "main hint #0".to_string(),
2806                    "main hint #1".to_string(),
2807                    "main hint #2".to_string(),
2808                    "main hint #3".to_string(),
2809                    "main hint #4".to_string(),
2810                    "main hint #5".to_string(),
2811                    "other hint #0".to_string(),
2812                    "other hint #1".to_string(),
2813                    "other hint #2".to_string(),
2814                    "other hint #3".to_string(),
2815                    "other hint #4".to_string(),
2816                    "other hint #5".to_string(),
2817                ];
2818                assert_eq!(expected_hints, cached_hint_labels(editor),
2819                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2820                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2821                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
2822                expected_hints.len()
2823            }).unwrap();
2824
2825        editor
2826            .update(cx, |editor, cx| {
2827                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2828                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2829                });
2830            })
2831            .unwrap();
2832        cx.executor().advance_clock(Duration::from_millis(
2833            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2834        ));
2835        cx.executor().run_until_parked();
2836        editor.update(cx, |editor, cx| {
2837                let expected_hints = vec![
2838                    "main hint #0".to_string(),
2839                    "main hint #1".to_string(),
2840                    "main hint #2".to_string(),
2841                    "main hint #3".to_string(),
2842                    "main hint #4".to_string(),
2843                    "main hint #5".to_string(),
2844                    "other hint #0".to_string(),
2845                    "other hint #1".to_string(),
2846                    "other hint #2".to_string(),
2847                    "other hint #3".to_string(),
2848                    "other hint #4".to_string(),
2849                    "other hint #5".to_string(),
2850                ];
2851                assert_eq!(expected_hints, cached_hint_labels(editor),
2852                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2853                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2854                assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scrolled buffer");
2855            }).unwrap();
2856
2857        editor_edited.store(true, Ordering::Release);
2858        editor
2859            .update(cx, |editor, cx| {
2860                editor.change_selections(None, cx, |s| {
2861                    s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
2862                });
2863                editor.handle_input("++++more text++++", cx);
2864            })
2865            .unwrap();
2866        cx.executor().run_until_parked();
2867        editor.update(cx, |editor, cx| {
2868            let expected_hints = vec![
2869                "main hint #0".to_string(),
2870                "main hint #1".to_string(),
2871                "main hint #2".to_string(),
2872                "main hint #3".to_string(),
2873                "main hint #4".to_string(),
2874                "main hint #5".to_string(),
2875                "other hint(edited) #0".to_string(),
2876                "other hint(edited) #1".to_string(),
2877                "other hint(edited) #2".to_string(),
2878            ];
2879            assert_eq!(
2880                expected_hints,
2881                cached_hint_labels(editor),
2882                "After multibuffer edit, editor gets scrolled back to the last selection; \
2883    all hints should be invalidated and required for all of its visible excerpts"
2884            );
2885            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2886
2887            let current_cache_version = editor.inlay_hint_cache().version;
2888            // We expect three new hints for the excerpts from `other.rs`:
2889            let expected_version = last_scroll_update_version + 3;
2890            assert_eq!(
2891                current_cache_version,
2892                expected_version,
2893                "We should have updated cache N times == N of new hints arrived (separately from each edited excerpt)"
2894            );
2895        }).unwrap();
2896    }
2897
2898    #[gpui::test]
2899    async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
2900        init_test(cx, |settings| {
2901            settings.defaults.inlay_hints = Some(InlayHintSettings {
2902                enabled: true,
2903                edit_debounce_ms: 0,
2904                scroll_debounce_ms: 0,
2905                show_type_hints: false,
2906                show_parameter_hints: false,
2907                show_other_hints: false,
2908                show_background: false,
2909            })
2910        });
2911
2912        let fs = FakeFs::new(cx.background_executor.clone());
2913        fs.insert_tree(
2914            "/a",
2915            json!({
2916                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2917                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2918            }),
2919        )
2920        .await;
2921
2922        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2923
2924        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2925        language_registry.add(crate::editor_tests::rust_lang());
2926        let mut fake_servers = language_registry.register_fake_lsp(
2927            "Rust",
2928            FakeLspAdapter {
2929                capabilities: lsp::ServerCapabilities {
2930                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2931                    ..Default::default()
2932                },
2933                ..Default::default()
2934            },
2935        );
2936
2937        let worktree_id = project.update(cx, |project, cx| {
2938            project.worktrees(cx).next().unwrap().read(cx).id()
2939        });
2940
2941        let buffer_1 = project
2942            .update(cx, |project, cx| {
2943                project.open_buffer((worktree_id, "main.rs"), cx)
2944            })
2945            .await
2946            .unwrap();
2947        let buffer_2 = project
2948            .update(cx, |project, cx| {
2949                project.open_buffer((worktree_id, "other.rs"), cx)
2950            })
2951            .await
2952            .unwrap();
2953        let multibuffer = cx.new_model(|_| MultiBuffer::new(Capability::ReadWrite));
2954        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2955            let buffer_1_excerpts = multibuffer.push_excerpts(
2956                buffer_1.clone(),
2957                [ExcerptRange {
2958                    context: Point::new(0, 0)..Point::new(2, 0),
2959                    primary: None,
2960                }],
2961                cx,
2962            );
2963            let buffer_2_excerpts = multibuffer.push_excerpts(
2964                buffer_2.clone(),
2965                [ExcerptRange {
2966                    context: Point::new(0, 1)..Point::new(2, 1),
2967                    primary: None,
2968                }],
2969                cx,
2970            );
2971            (buffer_1_excerpts, buffer_2_excerpts)
2972        });
2973
2974        assert!(!buffer_1_excerpts.is_empty());
2975        assert!(!buffer_2_excerpts.is_empty());
2976
2977        cx.executor().run_until_parked();
2978        let editor = cx
2979            .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx));
2980        let editor_edited = Arc::new(AtomicBool::new(false));
2981        let fake_server = fake_servers.next().await.unwrap();
2982        let closure_editor_edited = Arc::clone(&editor_edited);
2983        fake_server
2984            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2985                let task_editor_edited = Arc::clone(&closure_editor_edited);
2986                async move {
2987                    let hint_text = if params.text_document.uri
2988                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2989                    {
2990                        "main hint"
2991                    } else if params.text_document.uri
2992                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2993                    {
2994                        "other hint"
2995                    } else {
2996                        panic!("unexpected uri: {:?}", params.text_document.uri);
2997                    };
2998
2999                    let positions = [
3000                        lsp::Position::new(0, 2),
3001                        lsp::Position::new(4, 2),
3002                        lsp::Position::new(22, 2),
3003                        lsp::Position::new(44, 2),
3004                        lsp::Position::new(56, 2),
3005                        lsp::Position::new(67, 2),
3006                    ];
3007                    let out_of_range_hint = lsp::InlayHint {
3008                        position: lsp::Position::new(
3009                            params.range.start.line + 99,
3010                            params.range.start.character + 99,
3011                        ),
3012                        label: lsp::InlayHintLabel::String(
3013                            "out of excerpt range, should be ignored".to_string(),
3014                        ),
3015                        kind: None,
3016                        text_edits: None,
3017                        tooltip: None,
3018                        padding_left: None,
3019                        padding_right: None,
3020                        data: None,
3021                    };
3022
3023                    let edited = task_editor_edited.load(Ordering::Acquire);
3024                    Ok(Some(
3025                        std::iter::once(out_of_range_hint)
3026                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
3027                                lsp::InlayHint {
3028                                    position,
3029                                    label: lsp::InlayHintLabel::String(format!(
3030                                        "{hint_text}{} #{i}",
3031                                        if edited { "(edited)" } else { "" },
3032                                    )),
3033                                    kind: None,
3034                                    text_edits: None,
3035                                    tooltip: None,
3036                                    padding_left: None,
3037                                    padding_right: None,
3038                                    data: None,
3039                                }
3040                            }))
3041                            .collect(),
3042                    ))
3043                }
3044            })
3045            .next()
3046            .await;
3047        cx.executor().run_until_parked();
3048
3049        editor
3050            .update(cx, |editor, cx| {
3051                assert_eq!(
3052                    vec!["main hint #0".to_string(), "other hint #0".to_string()],
3053                    cached_hint_labels(editor),
3054                    "Cache should update for both excerpts despite hints display was disabled"
3055                );
3056                assert!(
3057                visible_hint_labels(editor, cx).is_empty(),
3058                "All hints are disabled and should not be shown despite being present in the cache"
3059            );
3060                assert_eq!(
3061                    editor.inlay_hint_cache().version,
3062                    2,
3063                    "Cache should update once per excerpt query"
3064                );
3065            })
3066            .unwrap();
3067
3068        editor
3069            .update(cx, |editor, cx| {
3070                editor.buffer().update(cx, |multibuffer, cx| {
3071                    multibuffer.remove_excerpts(buffer_2_excerpts, cx)
3072                })
3073            })
3074            .unwrap();
3075        cx.executor().run_until_parked();
3076        editor
3077            .update(cx, |editor, cx| {
3078                assert_eq!(
3079                    vec!["main hint #0".to_string()],
3080                    cached_hint_labels(editor),
3081                    "For the removed excerpt, should clean corresponding cached hints"
3082                );
3083                assert!(
3084                visible_hint_labels(editor, cx).is_empty(),
3085                "All hints are disabled and should not be shown despite being present in the cache"
3086            );
3087                assert_eq!(
3088                    editor.inlay_hint_cache().version,
3089                    3,
3090                    "Excerpt removal should trigger a cache update"
3091                );
3092            })
3093            .unwrap();
3094
3095        update_test_language_settings(cx, |settings| {
3096            settings.defaults.inlay_hints = Some(InlayHintSettings {
3097                enabled: true,
3098                edit_debounce_ms: 0,
3099                scroll_debounce_ms: 0,
3100                show_type_hints: true,
3101                show_parameter_hints: true,
3102                show_other_hints: true,
3103                show_background: false,
3104            })
3105        });
3106        cx.executor().run_until_parked();
3107        editor
3108            .update(cx, |editor, cx| {
3109                let expected_hints = vec!["main hint #0".to_string()];
3110                assert_eq!(
3111                    expected_hints,
3112                    cached_hint_labels(editor),
3113                    "Hint display settings change should not change the cache"
3114                );
3115                assert_eq!(
3116                    expected_hints,
3117                    visible_hint_labels(editor, cx),
3118                    "Settings change should make cached hints visible"
3119                );
3120                assert_eq!(
3121                    editor.inlay_hint_cache().version,
3122                    4,
3123                    "Settings change should trigger a cache update"
3124                );
3125            })
3126            .unwrap();
3127    }
3128
3129    #[gpui::test]
3130    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
3131        init_test(cx, |settings| {
3132            settings.defaults.inlay_hints = Some(InlayHintSettings {
3133                enabled: true,
3134                edit_debounce_ms: 0,
3135                scroll_debounce_ms: 0,
3136                show_type_hints: true,
3137                show_parameter_hints: true,
3138                show_other_hints: true,
3139                show_background: false,
3140            })
3141        });
3142
3143        let fs = FakeFs::new(cx.background_executor.clone());
3144        fs.insert_tree(
3145            "/a",
3146            json!({
3147                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
3148                "other.rs": "// Test file",
3149            }),
3150        )
3151        .await;
3152
3153        let project = Project::test(fs, ["/a".as_ref()], cx).await;
3154
3155        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3156        language_registry.add(crate::editor_tests::rust_lang());
3157        let mut fake_servers = language_registry.register_fake_lsp(
3158            "Rust",
3159            FakeLspAdapter {
3160                capabilities: lsp::ServerCapabilities {
3161                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3162                    ..Default::default()
3163                },
3164                ..Default::default()
3165            },
3166        );
3167
3168        let buffer = project
3169            .update(cx, |project, cx| {
3170                project.open_local_buffer("/a/main.rs", cx)
3171            })
3172            .await
3173            .unwrap();
3174        cx.executor().run_until_parked();
3175        cx.executor().start_waiting();
3176        let fake_server = fake_servers.next().await.unwrap();
3177        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
3178        let lsp_request_count = Arc::new(AtomicU32::new(0));
3179        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
3180        fake_server
3181            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
3182                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
3183                async move {
3184                    assert_eq!(
3185                        params.text_document.uri,
3186                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
3187                    );
3188                    let query_start = params.range.start;
3189                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
3190                    Ok(Some(vec![lsp::InlayHint {
3191                        position: query_start,
3192                        label: lsp::InlayHintLabel::String(i.to_string()),
3193                        kind: None,
3194                        text_edits: None,
3195                        tooltip: None,
3196                        padding_left: None,
3197                        padding_right: None,
3198                        data: None,
3199                    }]))
3200                }
3201            })
3202            .next()
3203            .await;
3204
3205        cx.executor().run_until_parked();
3206        editor
3207            .update(cx, |editor, cx| {
3208                editor.change_selections(None, cx, |s| {
3209                    s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3210                })
3211            })
3212            .unwrap();
3213        cx.executor().run_until_parked();
3214        editor
3215            .update(cx, |editor, cx| {
3216                let expected_hints = vec!["1".to_string()];
3217                assert_eq!(expected_hints, cached_hint_labels(editor));
3218                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3219                assert_eq!(editor.inlay_hint_cache().version, 1);
3220            })
3221            .unwrap();
3222    }
3223
3224    #[gpui::test]
3225    async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) {
3226        init_test(cx, |settings| {
3227            settings.defaults.inlay_hints = Some(InlayHintSettings {
3228                enabled: false,
3229                edit_debounce_ms: 0,
3230                scroll_debounce_ms: 0,
3231                show_type_hints: true,
3232                show_parameter_hints: true,
3233                show_other_hints: true,
3234                show_background: false,
3235            })
3236        });
3237
3238        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
3239
3240        editor
3241            .update(cx, |editor, cx| {
3242                editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3243            })
3244            .unwrap();
3245        cx.executor().start_waiting();
3246        let lsp_request_count = Arc::new(AtomicU32::new(0));
3247        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
3248        fake_server
3249            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
3250                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
3251                async move {
3252                    assert_eq!(
3253                        params.text_document.uri,
3254                        lsp::Url::from_file_path(file_with_hints).unwrap(),
3255                    );
3256
3257                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
3258                    Ok(Some(vec![lsp::InlayHint {
3259                        position: lsp::Position::new(0, i),
3260                        label: lsp::InlayHintLabel::String(i.to_string()),
3261                        kind: None,
3262                        text_edits: None,
3263                        tooltip: None,
3264                        padding_left: None,
3265                        padding_right: None,
3266                        data: None,
3267                    }]))
3268                }
3269            })
3270            .next()
3271            .await;
3272        cx.executor().run_until_parked();
3273        editor
3274            .update(cx, |editor, cx| {
3275                let expected_hints = vec!["1".to_string()];
3276                assert_eq!(
3277                    expected_hints,
3278                    cached_hint_labels(editor),
3279                    "Should display inlays after toggle despite them disabled in settings"
3280                );
3281                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3282                assert_eq!(
3283                    editor.inlay_hint_cache().version,
3284                    1,
3285                    "First toggle should be cache's first update"
3286                );
3287            })
3288            .unwrap();
3289
3290        editor
3291            .update(cx, |editor, cx| {
3292                editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3293            })
3294            .unwrap();
3295        cx.executor().run_until_parked();
3296        editor
3297            .update(cx, |editor, cx| {
3298                assert!(
3299                    cached_hint_labels(editor).is_empty(),
3300                    "Should clear hints after 2nd toggle"
3301                );
3302                assert!(visible_hint_labels(editor, cx).is_empty());
3303                assert_eq!(editor.inlay_hint_cache().version, 2);
3304            })
3305            .unwrap();
3306
3307        update_test_language_settings(cx, |settings| {
3308            settings.defaults.inlay_hints = Some(InlayHintSettings {
3309                enabled: true,
3310                edit_debounce_ms: 0,
3311                scroll_debounce_ms: 0,
3312                show_type_hints: true,
3313                show_parameter_hints: true,
3314                show_other_hints: true,
3315                show_background: false,
3316            })
3317        });
3318        cx.executor().run_until_parked();
3319        editor
3320            .update(cx, |editor, cx| {
3321                let expected_hints = vec!["2".to_string()];
3322                assert_eq!(
3323                    expected_hints,
3324                    cached_hint_labels(editor),
3325                    "Should query LSP hints for the 2nd time after enabling hints in settings"
3326                );
3327                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3328                assert_eq!(editor.inlay_hint_cache().version, 3);
3329            })
3330            .unwrap();
3331
3332        editor
3333            .update(cx, |editor, cx| {
3334                editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3335            })
3336            .unwrap();
3337        cx.executor().run_until_parked();
3338        editor
3339            .update(cx, |editor, cx| {
3340                assert!(
3341                    cached_hint_labels(editor).is_empty(),
3342                    "Should clear hints after enabling in settings and a 3rd toggle"
3343                );
3344                assert!(visible_hint_labels(editor, cx).is_empty());
3345                assert_eq!(editor.inlay_hint_cache().version, 4);
3346            })
3347            .unwrap();
3348
3349        editor
3350            .update(cx, |editor, cx| {
3351                editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3352            })
3353            .unwrap();
3354        cx.executor().run_until_parked();
3355        editor.update(cx, |editor, cx| {
3356            let expected_hints = vec!["3".to_string()];
3357            assert_eq!(
3358                expected_hints,
3359                cached_hint_labels(editor),
3360                "Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
3361            );
3362            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3363            assert_eq!(editor.inlay_hint_cache().version, 5);
3364        }).unwrap();
3365    }
3366
3367    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
3368        cx.update(|cx| {
3369            let settings_store = SettingsStore::test(cx);
3370            cx.set_global(settings_store);
3371            theme::init(theme::LoadThemes::JustBase, cx);
3372            release_channel::init(SemanticVersion::default(), cx);
3373            client::init_settings(cx);
3374            language::init(cx);
3375            Project::init_settings(cx);
3376            workspace::init_settings(cx);
3377            crate::init(cx);
3378        });
3379
3380        update_test_language_settings(cx, f);
3381    }
3382
3383    async fn prepare_test_objects(
3384        cx: &mut TestAppContext,
3385    ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
3386        let fs = FakeFs::new(cx.background_executor.clone());
3387        fs.insert_tree(
3388            "/a",
3389            json!({
3390                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
3391                "other.rs": "// Test file",
3392            }),
3393        )
3394        .await;
3395
3396        let project = Project::test(fs, ["/a".as_ref()], cx).await;
3397
3398        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3399        language_registry.add(crate::editor_tests::rust_lang());
3400        let mut fake_servers = language_registry.register_fake_lsp(
3401            "Rust",
3402            FakeLspAdapter {
3403                capabilities: lsp::ServerCapabilities {
3404                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3405                    ..Default::default()
3406                },
3407                ..Default::default()
3408            },
3409        );
3410
3411        let buffer = project
3412            .update(cx, |project, cx| {
3413                project.open_local_buffer("/a/main.rs", cx)
3414            })
3415            .await
3416            .unwrap();
3417        cx.executor().run_until_parked();
3418        cx.executor().start_waiting();
3419        let fake_server = fake_servers.next().await.unwrap();
3420        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
3421
3422        editor
3423            .update(cx, |editor, cx| {
3424                assert!(cached_hint_labels(editor).is_empty());
3425                assert!(visible_hint_labels(editor, cx).is_empty());
3426                assert_eq!(editor.inlay_hint_cache().version, 0);
3427            })
3428            .unwrap();
3429
3430        ("/a/main.rs", editor, fake_server)
3431    }
3432
3433    pub fn cached_hint_labels(editor: &Editor) -> Vec<String> {
3434        let mut labels = Vec::new();
3435        for excerpt_hints in editor.inlay_hint_cache().hints.values() {
3436            let excerpt_hints = excerpt_hints.read();
3437            for id in &excerpt_hints.ordered_hints {
3438                labels.push(excerpt_hints.hints_by_id[id].text());
3439            }
3440        }
3441
3442        labels.sort();
3443        labels
3444    }
3445
3446    pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, Editor>) -> Vec<String> {
3447        let mut hints = editor
3448            .visible_inlay_hints(cx)
3449            .into_iter()
3450            .map(|hint| hint.text.to_string())
3451            .collect::<Vec<_>>();
3452        hints.sort();
3453        hints
3454    }
3455}