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