inlay_hint_cache.rs

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