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