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