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