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::{AppContext as _, 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 buffer = excerpt_buffer.read(cx);
 747    let full_excerpt_range = multi_buffer
 748        .excerpts_for_buffer(buffer.remote_id(), cx)
 749        .into_iter()
 750        .find(|(id, _)| id == &excerpt_id)
 751        .map(|(_, range)| range.context)?;
 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.background_spawn(async move {
1000            calculate_hint_updates(
1001                query.excerpt_id,
1002                invalidate,
1003                background_fetch_range,
1004                new_hints,
1005                &background_task_buffer_snapshot,
1006                cached_excerpt_hints,
1007                &visible_hints,
1008            )
1009        })
1010            .await;
1011        if let Some(new_update) = new_update {
1012            log::debug!(
1013                "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
1014                new_update.remove_from_visible.len(),
1015                new_update.remove_from_cache.len(),
1016                new_update.add_to_cache.len()
1017            );
1018            log::trace!("New update: {new_update:?}");
1019            editor
1020                .update(&mut cx, |editor,  cx| {
1021                    apply_hint_update(
1022                        editor,
1023                        new_update,
1024                        query,
1025                        invalidate,
1026                        buffer_snapshot,
1027                        multi_buffer_snapshot,
1028                        cx,
1029                    );
1030                })
1031                .ok();
1032        }
1033        anyhow::Ok(())
1034    })
1035}
1036
1037fn calculate_hint_updates(
1038    excerpt_id: ExcerptId,
1039    invalidate: bool,
1040    fetch_range: Range<language::Anchor>,
1041    new_excerpt_hints: Vec<InlayHint>,
1042    buffer_snapshot: &BufferSnapshot,
1043    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
1044    visible_hints: &[Inlay],
1045) -> Option<ExcerptHintsUpdate> {
1046    let mut add_to_cache = Vec::<InlayHint>::new();
1047    let mut excerpt_hints_to_persist = HashMap::default();
1048    for new_hint in new_excerpt_hints {
1049        if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) {
1050            continue;
1051        }
1052        let missing_from_cache = match &cached_excerpt_hints {
1053            Some(cached_excerpt_hints) => {
1054                let cached_excerpt_hints = cached_excerpt_hints.read();
1055                match cached_excerpt_hints
1056                    .ordered_hints
1057                    .binary_search_by(|probe| {
1058                        cached_excerpt_hints.hints_by_id[probe]
1059                            .position
1060                            .cmp(&new_hint.position, buffer_snapshot)
1061                    }) {
1062                    Ok(ix) => {
1063                        let mut missing_from_cache = true;
1064                        for id in &cached_excerpt_hints.ordered_hints[ix..] {
1065                            let cached_hint = &cached_excerpt_hints.hints_by_id[id];
1066                            if new_hint
1067                                .position
1068                                .cmp(&cached_hint.position, buffer_snapshot)
1069                                .is_gt()
1070                            {
1071                                break;
1072                            }
1073                            if cached_hint == &new_hint {
1074                                excerpt_hints_to_persist.insert(*id, cached_hint.kind);
1075                                missing_from_cache = false;
1076                            }
1077                        }
1078                        missing_from_cache
1079                    }
1080                    Err(_) => true,
1081                }
1082            }
1083            None => true,
1084        };
1085        if missing_from_cache {
1086            add_to_cache.push(new_hint);
1087        }
1088    }
1089
1090    let mut remove_from_visible = HashSet::default();
1091    let mut remove_from_cache = HashSet::default();
1092    if invalidate {
1093        remove_from_visible.extend(
1094            visible_hints
1095                .iter()
1096                .filter(|hint| hint.position.excerpt_id == excerpt_id)
1097                .map(|inlay_hint| inlay_hint.id)
1098                .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
1099        );
1100
1101        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
1102            let cached_excerpt_hints = cached_excerpt_hints.read();
1103            remove_from_cache.extend(
1104                cached_excerpt_hints
1105                    .ordered_hints
1106                    .iter()
1107                    .filter(|cached_inlay_id| {
1108                        !excerpt_hints_to_persist.contains_key(cached_inlay_id)
1109                    })
1110                    .copied(),
1111            );
1112            remove_from_visible.extend(remove_from_cache.iter().cloned());
1113        }
1114    }
1115
1116    if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
1117        None
1118    } else {
1119        Some(ExcerptHintsUpdate {
1120            excerpt_id,
1121            remove_from_visible,
1122            remove_from_cache,
1123            add_to_cache,
1124        })
1125    }
1126}
1127
1128fn contains_position(
1129    range: &Range<language::Anchor>,
1130    position: language::Anchor,
1131    buffer_snapshot: &BufferSnapshot,
1132) -> bool {
1133    range.start.cmp(&position, buffer_snapshot).is_le()
1134        && range.end.cmp(&position, buffer_snapshot).is_ge()
1135}
1136
1137fn apply_hint_update(
1138    editor: &mut Editor,
1139    new_update: ExcerptHintsUpdate,
1140    query: ExcerptQuery,
1141    invalidate: bool,
1142    buffer_snapshot: BufferSnapshot,
1143    multi_buffer_snapshot: MultiBufferSnapshot,
1144    cx: &mut Context<Editor>,
1145) {
1146    let cached_excerpt_hints = editor
1147        .inlay_hint_cache
1148        .hints
1149        .entry(new_update.excerpt_id)
1150        .or_insert_with(|| {
1151            Arc::new(RwLock::new(CachedExcerptHints {
1152                version: query.cache_version,
1153                buffer_version: buffer_snapshot.version().clone(),
1154                buffer_id: query.buffer_id,
1155                ordered_hints: Vec::new(),
1156                hints_by_id: HashMap::default(),
1157            }))
1158        });
1159    let mut cached_excerpt_hints = cached_excerpt_hints.write();
1160    match query.cache_version.cmp(&cached_excerpt_hints.version) {
1161        cmp::Ordering::Less => return,
1162        cmp::Ordering::Greater | cmp::Ordering::Equal => {
1163            cached_excerpt_hints.version = query.cache_version;
1164        }
1165    }
1166
1167    let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
1168    cached_excerpt_hints
1169        .ordered_hints
1170        .retain(|hint_id| !new_update.remove_from_cache.contains(hint_id));
1171    cached_excerpt_hints
1172        .hints_by_id
1173        .retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id));
1174    let mut splice = InlaySplice::default();
1175    splice.to_remove.extend(new_update.remove_from_visible);
1176    for new_hint in new_update.add_to_cache {
1177        let insert_position = match cached_excerpt_hints
1178            .ordered_hints
1179            .binary_search_by(|probe| {
1180                cached_excerpt_hints.hints_by_id[probe]
1181                    .position
1182                    .cmp(&new_hint.position, &buffer_snapshot)
1183            }) {
1184            Ok(i) => {
1185                // When a hint is added to the same position where existing ones are present,
1186                // do not deduplicate it: we split hint queries into non-overlapping ranges
1187                // and each hint batch returned by the server should already contain unique hints.
1188                i + cached_excerpt_hints.ordered_hints[i..].len() + 1
1189            }
1190            Err(i) => i,
1191        };
1192
1193        let new_inlay_id = post_inc(&mut editor.next_inlay_id);
1194        if editor
1195            .inlay_hint_cache
1196            .allowed_hint_kinds
1197            .contains(&new_hint.kind)
1198        {
1199            if let Some(new_hint_position) =
1200                multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position)
1201            {
1202                splice
1203                    .to_insert
1204                    .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
1205            }
1206        }
1207        let new_id = InlayId::Hint(new_inlay_id);
1208        cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
1209        if cached_excerpt_hints.ordered_hints.len() <= insert_position {
1210            cached_excerpt_hints.ordered_hints.push(new_id);
1211        } else {
1212            cached_excerpt_hints
1213                .ordered_hints
1214                .insert(insert_position, new_id);
1215        }
1216
1217        cached_inlays_changed = true;
1218    }
1219    cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
1220    drop(cached_excerpt_hints);
1221
1222    if invalidate {
1223        let mut outdated_excerpt_caches = HashSet::default();
1224        for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
1225            let excerpt_hints = excerpt_hints.read();
1226            if excerpt_hints.buffer_id == query.buffer_id
1227                && excerpt_id != &query.excerpt_id
1228                && buffer_snapshot
1229                    .version()
1230                    .changed_since(&excerpt_hints.buffer_version)
1231            {
1232                outdated_excerpt_caches.insert(*excerpt_id);
1233                splice
1234                    .to_remove
1235                    .extend(excerpt_hints.ordered_hints.iter().copied());
1236            }
1237        }
1238        cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
1239        editor
1240            .inlay_hint_cache
1241            .hints
1242            .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
1243    }
1244
1245    let InlaySplice {
1246        to_remove,
1247        to_insert,
1248    } = splice;
1249    let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
1250    if cached_inlays_changed || displayed_inlays_changed {
1251        editor.inlay_hint_cache.version += 1;
1252    }
1253    if displayed_inlays_changed {
1254        editor.splice_inlays(&to_remove, to_insert, cx)
1255    }
1256}
1257
1258#[cfg(test)]
1259pub mod tests {
1260    use crate::editor_tests::update_test_language_settings;
1261    use crate::scroll::ScrollAmount;
1262    use crate::{scroll::Autoscroll, test::editor_lsp_test_context::rust_lang, ExcerptRange};
1263    use futures::StreamExt;
1264    use gpui::{AppContext as _, Context, SemanticVersion, TestAppContext, WindowHandle};
1265    use itertools::Itertools as _;
1266    use language::{language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter};
1267    use language::{Language, LanguageConfig, LanguageMatcher};
1268    use lsp::FakeLanguageServer;
1269    use parking_lot::Mutex;
1270    use project::{FakeFs, Project};
1271    use serde_json::json;
1272    use settings::SettingsStore;
1273    use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
1274    use text::Point;
1275    use util::path;
1276
1277    use super::*;
1278
1279    #[gpui::test]
1280    async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
1281        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1282        init_test(cx, |settings| {
1283            settings.defaults.inlay_hints = Some(InlayHintSettings {
1284                enabled: true,
1285                edit_debounce_ms: 0,
1286                scroll_debounce_ms: 0,
1287                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1288                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1289                show_other_hints: allowed_hint_kinds.contains(&None),
1290                show_background: false,
1291            })
1292        });
1293        let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
1294            let lsp_request_count = Arc::new(AtomicU32::new(0));
1295            fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1296                let task_lsp_request_count = Arc::clone(&lsp_request_count);
1297                async move {
1298                    let i = task_lsp_request_count.fetch_add(1, Ordering::Release) + 1;
1299                    assert_eq!(
1300                        params.text_document.uri,
1301                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1302                    );
1303                    Ok(Some(vec![lsp::InlayHint {
1304                        position: lsp::Position::new(0, i),
1305                        label: lsp::InlayHintLabel::String(i.to_string()),
1306                        kind: None,
1307                        text_edits: None,
1308                        tooltip: None,
1309                        padding_left: None,
1310                        padding_right: None,
1311                        data: None,
1312                    }]))
1313                }
1314            });
1315        })
1316        .await;
1317        cx.executor().run_until_parked();
1318
1319        editor
1320            .update(cx, |editor, _window, cx| {
1321                let expected_hints = vec!["1".to_string()];
1322                assert_eq!(
1323                    expected_hints,
1324                    cached_hint_labels(editor),
1325                    "Should get its first hints when opening the editor"
1326                );
1327                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1328                let inlay_cache = editor.inlay_hint_cache();
1329                assert_eq!(
1330                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1331                    "Cache should use editor settings to get the allowed hint kinds"
1332                );
1333            })
1334            .unwrap();
1335
1336        editor
1337            .update(cx, |editor, window, cx| {
1338                editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1339                editor.handle_input("some change", window, cx);
1340            })
1341            .unwrap();
1342        cx.executor().run_until_parked();
1343        editor
1344            .update(cx, |editor, _window, cx| {
1345                let expected_hints = vec!["2".to_string()];
1346                assert_eq!(
1347                    expected_hints,
1348                    cached_hint_labels(editor),
1349                    "Should get new hints after an edit"
1350                );
1351                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1352                let inlay_cache = editor.inlay_hint_cache();
1353                assert_eq!(
1354                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1355                    "Cache should use editor settings to get the allowed hint kinds"
1356                );
1357            })
1358            .unwrap();
1359
1360        fake_server
1361            .request::<lsp::request::InlayHintRefreshRequest>(())
1362            .await
1363            .expect("inlay refresh request failed");
1364        cx.executor().run_until_parked();
1365        editor
1366            .update(cx, |editor, _window, cx| {
1367                let expected_hints = vec!["3".to_string()];
1368                assert_eq!(
1369                    expected_hints,
1370                    cached_hint_labels(editor),
1371                    "Should get new hints after hint refresh/ request"
1372                );
1373                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1374                let inlay_cache = editor.inlay_hint_cache();
1375                assert_eq!(
1376                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1377                    "Cache should use editor settings to get the allowed hint kinds"
1378                );
1379            })
1380            .unwrap();
1381    }
1382
1383    #[gpui::test]
1384    async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
1385        init_test(cx, |settings| {
1386            settings.defaults.inlay_hints = Some(InlayHintSettings {
1387                enabled: true,
1388                edit_debounce_ms: 0,
1389                scroll_debounce_ms: 0,
1390                show_type_hints: true,
1391                show_parameter_hints: true,
1392                show_other_hints: true,
1393                show_background: false,
1394            })
1395        });
1396
1397        let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
1398            let lsp_request_count = Arc::new(AtomicU32::new(0));
1399            fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1400                let task_lsp_request_count = Arc::clone(&lsp_request_count);
1401                async move {
1402                    assert_eq!(
1403                        params.text_document.uri,
1404                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1405                    );
1406                    let current_call_id =
1407                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1408                    Ok(Some(vec![lsp::InlayHint {
1409                        position: lsp::Position::new(0, current_call_id),
1410                        label: lsp::InlayHintLabel::String(current_call_id.to_string()),
1411                        kind: None,
1412                        text_edits: None,
1413                        tooltip: None,
1414                        padding_left: None,
1415                        padding_right: None,
1416                        data: None,
1417                    }]))
1418                }
1419            });
1420        })
1421        .await;
1422        cx.executor().run_until_parked();
1423
1424        editor
1425            .update(cx, |editor, _, cx| {
1426                let expected_hints = vec!["0".to_string()];
1427                assert_eq!(
1428                    expected_hints,
1429                    cached_hint_labels(editor),
1430                    "Should get its first hints when opening the editor"
1431                );
1432                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1433            })
1434            .unwrap();
1435
1436        let progress_token = "test_progress_token";
1437        fake_server
1438            .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1439                token: lsp::ProgressToken::String(progress_token.to_string()),
1440            })
1441            .await
1442            .expect("work done progress create request failed");
1443        cx.executor().run_until_parked();
1444        fake_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1445            token: lsp::ProgressToken::String(progress_token.to_string()),
1446            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
1447                lsp::WorkDoneProgressBegin::default(),
1448            )),
1449        });
1450        cx.executor().run_until_parked();
1451
1452        editor
1453            .update(cx, |editor, _, cx| {
1454                let expected_hints = vec!["0".to_string()];
1455                assert_eq!(
1456                    expected_hints,
1457                    cached_hint_labels(editor),
1458                    "Should not update hints while the work task is running"
1459                );
1460                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1461            })
1462            .unwrap();
1463
1464        fake_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1465            token: lsp::ProgressToken::String(progress_token.to_string()),
1466            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
1467                lsp::WorkDoneProgressEnd::default(),
1468            )),
1469        });
1470        cx.executor().run_until_parked();
1471
1472        editor
1473            .update(cx, |editor, _, cx| {
1474                let expected_hints = vec!["1".to_string()];
1475                assert_eq!(
1476                    expected_hints,
1477                    cached_hint_labels(editor),
1478                    "New hints should be queried after the work task is done"
1479                );
1480                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1481            })
1482            .unwrap();
1483    }
1484
1485    #[gpui::test]
1486    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1487        init_test(cx, |settings| {
1488            settings.defaults.inlay_hints = Some(InlayHintSettings {
1489                enabled: true,
1490                edit_debounce_ms: 0,
1491                scroll_debounce_ms: 0,
1492                show_type_hints: true,
1493                show_parameter_hints: true,
1494                show_other_hints: true,
1495                show_background: false,
1496            })
1497        });
1498
1499        let fs = FakeFs::new(cx.background_executor.clone());
1500        fs.insert_tree(
1501            path!("/a"),
1502            json!({
1503                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1504                "other.md": "Test md file with some text",
1505            }),
1506        )
1507        .await;
1508
1509        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1510
1511        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1512        let mut rs_fake_servers = None;
1513        let mut md_fake_servers = None;
1514        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1515            language_registry.add(Arc::new(Language::new(
1516                LanguageConfig {
1517                    name: name.into(),
1518                    matcher: LanguageMatcher {
1519                        path_suffixes: vec![path_suffix.to_string()],
1520                        ..Default::default()
1521                    },
1522                    ..Default::default()
1523                },
1524                Some(tree_sitter_rust::LANGUAGE.into()),
1525            )));
1526            let fake_servers = language_registry.register_fake_lsp(
1527                name,
1528                FakeLspAdapter {
1529                    name,
1530                    capabilities: lsp::ServerCapabilities {
1531                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1532                        ..Default::default()
1533                    },
1534                    initializer: Some(Box::new({
1535                        move |fake_server| {
1536                            let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1537                            let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1538                            fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(
1539                                move |params, _| {
1540                                    let i = match name {
1541                                        "Rust" => {
1542                                            assert_eq!(
1543                                                params.text_document.uri,
1544                                                lsp::Url::from_file_path(path!("/a/main.rs"))
1545                                                    .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(path!("/a/other.md"))
1553                                                    .unwrap(),
1554                                            );
1555                                            md_lsp_request_count.fetch_add(1, Ordering::Release) + 1
1556                                        }
1557                                        unexpected => panic!("Unexpected language: {unexpected}"),
1558                                    };
1559
1560                                    async move {
1561                                        let query_start = params.range.start;
1562                                        Ok(Some(vec![lsp::InlayHint {
1563                                            position: query_start,
1564                                            label: lsp::InlayHintLabel::String(i.to_string()),
1565                                            kind: None,
1566                                            text_edits: None,
1567                                            tooltip: None,
1568                                            padding_left: None,
1569                                            padding_right: None,
1570                                            data: None,
1571                                        }]))
1572                                    }
1573                                },
1574                            );
1575                        }
1576                    })),
1577                    ..Default::default()
1578                },
1579            );
1580            match name {
1581                "Rust" => rs_fake_servers = Some(fake_servers),
1582                "Markdown" => md_fake_servers = Some(fake_servers),
1583                _ => unreachable!(),
1584            }
1585        }
1586
1587        let rs_buffer = project
1588            .update(cx, |project, cx| {
1589                project.open_local_buffer(path!("/a/main.rs"), cx)
1590            })
1591            .await
1592            .unwrap();
1593        let rs_editor = cx.add_window(|window, cx| {
1594            Editor::for_buffer(rs_buffer, Some(project.clone()), window, cx)
1595        });
1596        cx.executor().run_until_parked();
1597
1598        let _rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1599        cx.executor().run_until_parked();
1600        rs_editor
1601            .update(cx, |editor, _window, cx| {
1602                let expected_hints = vec!["1".to_string()];
1603                assert_eq!(
1604                    expected_hints,
1605                    cached_hint_labels(editor),
1606                    "Should get its first hints when opening the editor"
1607                );
1608                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1609            })
1610            .unwrap();
1611
1612        cx.executor().run_until_parked();
1613        let md_buffer = project
1614            .update(cx, |project, cx| {
1615                project.open_local_buffer(path!("/a/other.md"), cx)
1616            })
1617            .await
1618            .unwrap();
1619        let md_editor =
1620            cx.add_window(|window, cx| Editor::for_buffer(md_buffer, Some(project), window, cx));
1621        cx.executor().run_until_parked();
1622
1623        let _md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1624        cx.executor().run_until_parked();
1625        md_editor
1626            .update(cx, |editor, _window, cx| {
1627                let expected_hints = vec!["1".to_string()];
1628                assert_eq!(
1629                    expected_hints,
1630                    cached_hint_labels(editor),
1631                    "Markdown editor should have a separate version, repeating Rust editor rules"
1632                );
1633                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1634            })
1635            .unwrap();
1636
1637        rs_editor
1638            .update(cx, |editor, window, cx| {
1639                editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1640                editor.handle_input("some rs change", window, cx);
1641            })
1642            .unwrap();
1643        cx.executor().run_until_parked();
1644        rs_editor
1645            .update(cx, |editor, _window, cx| {
1646                // TODO: Here, we do not get "2", because inserting another language server will trigger `RefreshInlayHints` event from the `LspStore`
1647                // A project is listened in every editor, so each of them will react to this event.
1648                //
1649                // We do not have language server IDs for remote projects, so cannot easily say on the editor level,
1650                // whether we should ignore a particular `RefreshInlayHints` event.
1651                let expected_hints = vec!["3".to_string()];
1652                assert_eq!(
1653                    expected_hints,
1654                    cached_hint_labels(editor),
1655                    "Rust inlay cache should change after the edit"
1656                );
1657                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1658            })
1659            .unwrap();
1660        md_editor
1661            .update(cx, |editor, _window, cx| {
1662                let expected_hints = vec!["1".to_string()];
1663                assert_eq!(
1664                    expected_hints,
1665                    cached_hint_labels(editor),
1666                    "Markdown editor should not be affected by Rust editor changes"
1667                );
1668                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1669            })
1670            .unwrap();
1671
1672        md_editor
1673            .update(cx, |editor, window, cx| {
1674                editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1675                editor.handle_input("some md change", window, cx);
1676            })
1677            .unwrap();
1678        cx.executor().run_until_parked();
1679        md_editor
1680            .update(cx, |editor, _window, cx| {
1681                let expected_hints = vec!["2".to_string()];
1682                assert_eq!(
1683                    expected_hints,
1684                    cached_hint_labels(editor),
1685                    "Rust editor should not be affected by Markdown editor changes"
1686                );
1687                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1688            })
1689            .unwrap();
1690        rs_editor
1691            .update(cx, |editor, _window, cx| {
1692                let expected_hints = vec!["3".to_string()];
1693                assert_eq!(
1694                    expected_hints,
1695                    cached_hint_labels(editor),
1696                    "Markdown editor should also change independently"
1697                );
1698                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1699            })
1700            .unwrap();
1701    }
1702
1703    #[gpui::test]
1704    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1705        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1706        init_test(cx, |settings| {
1707            settings.defaults.inlay_hints = Some(InlayHintSettings {
1708                enabled: true,
1709                edit_debounce_ms: 0,
1710                scroll_debounce_ms: 0,
1711                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1712                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1713                show_other_hints: allowed_hint_kinds.contains(&None),
1714                show_background: false,
1715            })
1716        });
1717
1718        let lsp_request_count = Arc::new(AtomicUsize::new(0));
1719        let (_, editor, fake_server) = prepare_test_objects(cx, {
1720            let lsp_request_count = lsp_request_count.clone();
1721            move |fake_server, file_with_hints| {
1722                let lsp_request_count = lsp_request_count.clone();
1723                fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(
1724                    move |params, _| {
1725                        lsp_request_count.fetch_add(1, Ordering::Release);
1726                        async move {
1727                            assert_eq!(
1728                                params.text_document.uri,
1729                                lsp::Url::from_file_path(file_with_hints).unwrap(),
1730                            );
1731                            Ok(Some(vec![
1732                                lsp::InlayHint {
1733                                    position: lsp::Position::new(0, 1),
1734                                    label: lsp::InlayHintLabel::String("type hint".to_string()),
1735                                    kind: Some(lsp::InlayHintKind::TYPE),
1736                                    text_edits: None,
1737                                    tooltip: None,
1738                                    padding_left: None,
1739                                    padding_right: None,
1740                                    data: None,
1741                                },
1742                                lsp::InlayHint {
1743                                    position: lsp::Position::new(0, 2),
1744                                    label: lsp::InlayHintLabel::String(
1745                                        "parameter hint".to_string(),
1746                                    ),
1747                                    kind: Some(lsp::InlayHintKind::PARAMETER),
1748                                    text_edits: None,
1749                                    tooltip: None,
1750                                    padding_left: None,
1751                                    padding_right: None,
1752                                    data: None,
1753                                },
1754                                lsp::InlayHint {
1755                                    position: lsp::Position::new(0, 3),
1756                                    label: lsp::InlayHintLabel::String("other hint".to_string()),
1757                                    kind: None,
1758                                    text_edits: None,
1759                                    tooltip: None,
1760                                    padding_left: None,
1761                                    padding_right: None,
1762                                    data: None,
1763                                },
1764                            ]))
1765                        }
1766                    },
1767                );
1768            }
1769        })
1770        .await;
1771        cx.executor().run_until_parked();
1772
1773        editor
1774            .update(cx, |editor, _, cx| {
1775                assert_eq!(
1776                    lsp_request_count.load(Ordering::Relaxed),
1777                    1,
1778                    "Should query new hints once"
1779                );
1780                assert_eq!(
1781                    vec![
1782                        "type hint".to_string(),
1783                        "parameter hint".to_string(),
1784                        "other hint".to_string(),
1785                    ],
1786                    cached_hint_labels(editor),
1787                    "Should get its first hints when opening the editor"
1788                );
1789                assert_eq!(
1790                    vec!["type hint".to_string(), "other hint".to_string()],
1791                    visible_hint_labels(editor, cx)
1792                );
1793                let inlay_cache = editor.inlay_hint_cache();
1794                assert_eq!(
1795                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1796                    "Cache should use editor settings to get the allowed hint kinds"
1797                );
1798            })
1799            .unwrap();
1800
1801        fake_server
1802            .request::<lsp::request::InlayHintRefreshRequest>(())
1803            .await
1804            .expect("inlay refresh request failed");
1805        cx.executor().run_until_parked();
1806        editor
1807            .update(cx, |editor, _, cx| {
1808                assert_eq!(
1809                    lsp_request_count.load(Ordering::Relaxed),
1810                    2,
1811                    "Should load new hints twice"
1812                );
1813                assert_eq!(
1814                    vec![
1815                        "type hint".to_string(),
1816                        "parameter hint".to_string(),
1817                        "other hint".to_string(),
1818                    ],
1819                    cached_hint_labels(editor),
1820                    "Cached hints should not change due to allowed hint kinds settings update"
1821                );
1822                assert_eq!(
1823                    vec!["type hint".to_string(), "other hint".to_string()],
1824                    visible_hint_labels(editor, cx)
1825                );
1826            })
1827            .unwrap();
1828
1829        for (new_allowed_hint_kinds, expected_visible_hints) in [
1830            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1831            (
1832                HashSet::from_iter([Some(InlayHintKind::Type)]),
1833                vec!["type hint".to_string()],
1834            ),
1835            (
1836                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1837                vec!["parameter hint".to_string()],
1838            ),
1839            (
1840                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1841                vec!["type hint".to_string(), "other hint".to_string()],
1842            ),
1843            (
1844                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1845                vec!["parameter hint".to_string(), "other hint".to_string()],
1846            ),
1847            (
1848                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1849                vec!["type hint".to_string(), "parameter hint".to_string()],
1850            ),
1851            (
1852                HashSet::from_iter([
1853                    None,
1854                    Some(InlayHintKind::Type),
1855                    Some(InlayHintKind::Parameter),
1856                ]),
1857                vec![
1858                    "type hint".to_string(),
1859                    "parameter hint".to_string(),
1860                    "other hint".to_string(),
1861                ],
1862            ),
1863        ] {
1864            update_test_language_settings(cx, |settings| {
1865                settings.defaults.inlay_hints = Some(InlayHintSettings {
1866                    enabled: true,
1867                    edit_debounce_ms: 0,
1868                    scroll_debounce_ms: 0,
1869                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1870                    show_parameter_hints: new_allowed_hint_kinds
1871                        .contains(&Some(InlayHintKind::Parameter)),
1872                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1873                    show_background: false,
1874                })
1875            });
1876            cx.executor().run_until_parked();
1877            editor.update(cx, |editor, _, cx| {
1878                assert_eq!(
1879                    lsp_request_count.load(Ordering::Relaxed),
1880                    2,
1881                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1882                );
1883                assert_eq!(
1884                    vec![
1885                        "type hint".to_string(),
1886                        "parameter hint".to_string(),
1887                        "other hint".to_string(),
1888                    ],
1889                    cached_hint_labels(editor),
1890                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1891                );
1892                assert_eq!(
1893                    expected_visible_hints,
1894                    visible_hint_labels(editor, cx),
1895                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1896                );
1897                let inlay_cache = editor.inlay_hint_cache();
1898                assert_eq!(
1899                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1900                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1901                );
1902            }).unwrap();
1903        }
1904
1905        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1906        update_test_language_settings(cx, |settings| {
1907            settings.defaults.inlay_hints = Some(InlayHintSettings {
1908                enabled: false,
1909                edit_debounce_ms: 0,
1910                scroll_debounce_ms: 0,
1911                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1912                show_parameter_hints: another_allowed_hint_kinds
1913                    .contains(&Some(InlayHintKind::Parameter)),
1914                show_other_hints: another_allowed_hint_kinds.contains(&None),
1915                show_background: false,
1916            })
1917        });
1918        cx.executor().run_until_parked();
1919        editor
1920            .update(cx, |editor, _, cx| {
1921                assert_eq!(
1922                    lsp_request_count.load(Ordering::Relaxed),
1923                    2,
1924                    "Should not load new hints when hints got disabled"
1925                );
1926                assert!(
1927                    cached_hint_labels(editor).is_empty(),
1928                    "Should clear the cache when hints got disabled"
1929                );
1930                assert!(
1931                    visible_hint_labels(editor, cx).is_empty(),
1932                    "Should clear visible hints when hints got disabled"
1933                );
1934                let inlay_cache = editor.inlay_hint_cache();
1935                assert_eq!(
1936                    inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1937                    "Should update its allowed hint kinds even when hints got disabled"
1938                );
1939            })
1940            .unwrap();
1941
1942        fake_server
1943            .request::<lsp::request::InlayHintRefreshRequest>(())
1944            .await
1945            .expect("inlay refresh request failed");
1946        cx.executor().run_until_parked();
1947        editor
1948            .update(cx, |editor, _window, cx| {
1949                assert_eq!(
1950                    lsp_request_count.load(Ordering::Relaxed),
1951                    2,
1952                    "Should not load new hints when they got disabled"
1953                );
1954                assert!(cached_hint_labels(editor).is_empty());
1955                assert!(visible_hint_labels(editor, cx).is_empty());
1956            })
1957            .unwrap();
1958
1959        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1960        update_test_language_settings(cx, |settings| {
1961            settings.defaults.inlay_hints = Some(InlayHintSettings {
1962                enabled: true,
1963                edit_debounce_ms: 0,
1964                scroll_debounce_ms: 0,
1965                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1966                show_parameter_hints: final_allowed_hint_kinds
1967                    .contains(&Some(InlayHintKind::Parameter)),
1968                show_other_hints: final_allowed_hint_kinds.contains(&None),
1969                show_background: false,
1970            })
1971        });
1972        cx.executor().run_until_parked();
1973        editor
1974            .update(cx, |editor, _, cx| {
1975                assert_eq!(
1976                    lsp_request_count.load(Ordering::Relaxed),
1977                    3,
1978                    "Should query for new hints when they got re-enabled"
1979                );
1980                assert_eq!(
1981                    vec![
1982                        "type hint".to_string(),
1983                        "parameter hint".to_string(),
1984                        "other hint".to_string(),
1985                    ],
1986                    cached_hint_labels(editor),
1987                    "Should get its cached hints fully repopulated after the hints got re-enabled"
1988                );
1989                assert_eq!(
1990                    vec!["parameter hint".to_string()],
1991                    visible_hint_labels(editor, cx),
1992                    "Should get its visible hints repopulated and filtered after the h"
1993                );
1994                let inlay_cache = editor.inlay_hint_cache();
1995                assert_eq!(
1996                    inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1997                    "Cache should update editor settings when hints got re-enabled"
1998                );
1999            })
2000            .unwrap();
2001
2002        fake_server
2003            .request::<lsp::request::InlayHintRefreshRequest>(())
2004            .await
2005            .expect("inlay refresh request failed");
2006        cx.executor().run_until_parked();
2007        editor
2008            .update(cx, |editor, _, cx| {
2009                assert_eq!(
2010                    lsp_request_count.load(Ordering::Relaxed),
2011                    4,
2012                    "Should query for new hints again"
2013                );
2014                assert_eq!(
2015                    vec![
2016                        "type hint".to_string(),
2017                        "parameter hint".to_string(),
2018                        "other hint".to_string(),
2019                    ],
2020                    cached_hint_labels(editor),
2021                );
2022                assert_eq!(
2023                    vec!["parameter hint".to_string()],
2024                    visible_hint_labels(editor, cx),
2025                );
2026            })
2027            .unwrap();
2028    }
2029
2030    #[gpui::test]
2031    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
2032        init_test(cx, |settings| {
2033            settings.defaults.inlay_hints = Some(InlayHintSettings {
2034                enabled: true,
2035                edit_debounce_ms: 0,
2036                scroll_debounce_ms: 0,
2037                show_type_hints: true,
2038                show_parameter_hints: true,
2039                show_other_hints: true,
2040                show_background: false,
2041            })
2042        });
2043
2044        let lsp_request_count = Arc::new(AtomicU32::new(0));
2045        let (_, editor, _) = prepare_test_objects(cx, {
2046            let lsp_request_count = lsp_request_count.clone();
2047            move |fake_server, file_with_hints| {
2048                let lsp_request_count = lsp_request_count.clone();
2049                fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(
2050                    move |params, _| {
2051                        let lsp_request_count = lsp_request_count.clone();
2052                        async move {
2053                            let i = lsp_request_count.fetch_add(1, Ordering::SeqCst) + 1;
2054                            assert_eq!(
2055                                params.text_document.uri,
2056                                lsp::Url::from_file_path(file_with_hints).unwrap(),
2057                            );
2058                            Ok(Some(vec![lsp::InlayHint {
2059                                position: lsp::Position::new(0, i),
2060                                label: lsp::InlayHintLabel::String(i.to_string()),
2061                                kind: None,
2062                                text_edits: None,
2063                                tooltip: None,
2064                                padding_left: None,
2065                                padding_right: None,
2066                                data: None,
2067                            }]))
2068                        }
2069                    },
2070                );
2071            }
2072        })
2073        .await;
2074
2075        let mut expected_changes = Vec::new();
2076        for change_after_opening in [
2077            "initial change #1",
2078            "initial change #2",
2079            "initial change #3",
2080        ] {
2081            editor
2082                .update(cx, |editor, window, cx| {
2083                    editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
2084                    editor.handle_input(change_after_opening, window, cx);
2085                })
2086                .unwrap();
2087            expected_changes.push(change_after_opening);
2088        }
2089
2090        cx.executor().run_until_parked();
2091
2092        editor
2093            .update(cx, |editor, _window, cx| {
2094                let current_text = editor.text(cx);
2095                for change in &expected_changes {
2096                    assert!(
2097                        current_text.contains(change),
2098                        "Should apply all changes made"
2099                    );
2100                }
2101                assert_eq!(
2102                    lsp_request_count.load(Ordering::Relaxed),
2103                    2,
2104                    "Should query new hints twice: for editor init and for the last edit that interrupted all others"
2105                );
2106                let expected_hints = vec!["2".to_string()];
2107                assert_eq!(
2108                    expected_hints,
2109                    cached_hint_labels(editor),
2110                    "Should get hints from the last edit landed only"
2111                );
2112                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2113            })
2114            .unwrap();
2115
2116        let mut edits = Vec::new();
2117        for async_later_change in [
2118            "another change #1",
2119            "another change #2",
2120            "another change #3",
2121        ] {
2122            expected_changes.push(async_later_change);
2123            let task_editor = editor;
2124            edits.push(cx.spawn(|mut cx| async move {
2125                task_editor
2126                    .update(&mut cx, |editor, window, cx| {
2127                        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
2128                        editor.handle_input(async_later_change, window, cx);
2129                    })
2130                    .unwrap();
2131            }));
2132        }
2133        let _ = future::join_all(edits).await;
2134        cx.executor().run_until_parked();
2135
2136        editor
2137            .update(cx, |editor, _, cx| {
2138                let current_text = editor.text(cx);
2139                for change in &expected_changes {
2140                    assert!(
2141                        current_text.contains(change),
2142                        "Should apply all changes made"
2143                    );
2144                }
2145                assert_eq!(
2146                    lsp_request_count.load(Ordering::SeqCst),
2147                    3,
2148                    "Should query new hints one more time, for the last edit only"
2149                );
2150                let expected_hints = vec!["3".to_string()];
2151                assert_eq!(
2152                    expected_hints,
2153                    cached_hint_labels(editor),
2154                    "Should get hints from the last edit landed only"
2155                );
2156                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2157            })
2158            .unwrap();
2159    }
2160
2161    #[gpui::test(iterations = 10)]
2162    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
2163        init_test(cx, |settings| {
2164            settings.defaults.inlay_hints = Some(InlayHintSettings {
2165                enabled: true,
2166                edit_debounce_ms: 0,
2167                scroll_debounce_ms: 0,
2168                show_type_hints: true,
2169                show_parameter_hints: true,
2170                show_other_hints: true,
2171                show_background: false,
2172            })
2173        });
2174
2175        let fs = FakeFs::new(cx.background_executor.clone());
2176        fs.insert_tree(
2177            path!("/a"),
2178            json!({
2179                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
2180                "other.rs": "// Test file",
2181            }),
2182        )
2183        .await;
2184
2185        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2186
2187        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2188        language_registry.add(rust_lang());
2189
2190        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
2191        let lsp_request_count = Arc::new(AtomicUsize::new(0));
2192        let mut fake_servers = language_registry.register_fake_lsp(
2193            "Rust",
2194            FakeLspAdapter {
2195                capabilities: lsp::ServerCapabilities {
2196                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2197                    ..Default::default()
2198                },
2199                initializer: Some(Box::new({
2200                    let lsp_request_ranges = lsp_request_ranges.clone();
2201                    let lsp_request_count = lsp_request_count.clone();
2202                    move |fake_server| {
2203                        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
2204                        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2205                        fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(
2206                            move |params, _| {
2207                                let task_lsp_request_ranges =
2208                                    Arc::clone(&closure_lsp_request_ranges);
2209                                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2210                                async move {
2211                                    assert_eq!(
2212                                        params.text_document.uri,
2213                                        lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2214                                    );
2215
2216                                    task_lsp_request_ranges.lock().push(params.range);
2217                                    task_lsp_request_count.fetch_add(1, Ordering::Release);
2218                                    Ok(Some(vec![lsp::InlayHint {
2219                                        position: params.range.end,
2220                                        label: lsp::InlayHintLabel::String(
2221                                            params.range.end.line.to_string(),
2222                                        ),
2223                                        kind: None,
2224                                        text_edits: None,
2225                                        tooltip: None,
2226                                        padding_left: None,
2227                                        padding_right: None,
2228                                        data: None,
2229                                    }]))
2230                                }
2231                            },
2232                        );
2233                    }
2234                })),
2235                ..Default::default()
2236            },
2237        );
2238
2239        let buffer = project
2240            .update(cx, |project, cx| {
2241                project.open_local_buffer(path!("/a/main.rs"), cx)
2242            })
2243            .await
2244            .unwrap();
2245        let editor =
2246            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
2247
2248        cx.executor().run_until_parked();
2249
2250        let _fake_server = fake_servers.next().await.unwrap();
2251
2252        // in large buffers, requests are made for more than visible range of a buffer.
2253        // invisible parts are queried later, to avoid excessive requests on quick typing.
2254        // wait the timeout needed to get all requests.
2255        cx.executor().advance_clock(Duration::from_millis(
2256            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2257        ));
2258        cx.executor().run_until_parked();
2259        let initial_visible_range = editor_visible_range(&editor, cx);
2260        let lsp_initial_visible_range = lsp::Range::new(
2261            lsp::Position::new(
2262                initial_visible_range.start.row,
2263                initial_visible_range.start.column,
2264            ),
2265            lsp::Position::new(
2266                initial_visible_range.end.row,
2267                initial_visible_range.end.column,
2268            ),
2269        );
2270        let expected_initial_query_range_end =
2271            lsp::Position::new(initial_visible_range.end.row * 2, 2);
2272        let mut expected_invisible_query_start = lsp_initial_visible_range.end;
2273        expected_invisible_query_start.character += 1;
2274        editor.update(cx, |editor, _window, cx| {
2275            let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2276            assert_eq!(ranges.len(), 2,
2277                "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:?}");
2278            let visible_query_range = &ranges[0];
2279            assert_eq!(visible_query_range.start, lsp_initial_visible_range.start);
2280            assert_eq!(visible_query_range.end, lsp_initial_visible_range.end);
2281            let invisible_query_range = &ranges[1];
2282
2283            assert_eq!(invisible_query_range.start, expected_invisible_query_start, "Should initially query visible edge of the document");
2284            assert_eq!(invisible_query_range.end, expected_initial_query_range_end, "Should initially query visible edge of the document");
2285
2286            let requests_count = lsp_request_count.load(Ordering::Acquire);
2287            assert_eq!(requests_count, 2, "Visible + invisible request");
2288            let expected_hints = vec!["47".to_string(), "94".to_string()];
2289            assert_eq!(
2290                expected_hints,
2291                cached_hint_labels(editor),
2292                "Should have hints from both LSP requests made for a big file"
2293            );
2294            assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
2295        }).unwrap();
2296
2297        editor
2298            .update(cx, |editor, window, cx| {
2299                editor.scroll_screen(&ScrollAmount::Page(1.0), window, cx);
2300            })
2301            .unwrap();
2302        cx.executor().run_until_parked();
2303        editor
2304            .update(cx, |editor, window, cx| {
2305                editor.scroll_screen(&ScrollAmount::Page(1.0), window, cx);
2306            })
2307            .unwrap();
2308        cx.executor().advance_clock(Duration::from_millis(
2309            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2310        ));
2311        cx.executor().run_until_parked();
2312        let visible_range_after_scrolls = editor_visible_range(&editor, cx);
2313        let visible_line_count = editor
2314            .update(cx, |editor, _window, _| {
2315                editor.visible_line_count().unwrap()
2316            })
2317            .unwrap();
2318        let selection_in_cached_range = editor
2319            .update(cx, |editor, _window, cx| {
2320                let ranges = lsp_request_ranges
2321                    .lock()
2322                    .drain(..)
2323                    .sorted_by_key(|r| r.start)
2324                    .collect::<Vec<_>>();
2325                assert_eq!(
2326                    ranges.len(),
2327                    2,
2328                    "Should query 2 ranges after both scrolls, but got: {ranges:?}"
2329                );
2330                let first_scroll = &ranges[0];
2331                let second_scroll = &ranges[1];
2332                assert_eq!(
2333                    first_scroll.end, second_scroll.start,
2334                    "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
2335                );
2336                assert_eq!(
2337                first_scroll.start, expected_initial_query_range_end,
2338                "First scroll should start the query right after the end of the original scroll",
2339            );
2340                assert_eq!(
2341                second_scroll.end,
2342                lsp::Position::new(
2343                    visible_range_after_scrolls.end.row
2344                        + visible_line_count.ceil() as u32,
2345                    1,
2346                ),
2347                "Second scroll should query one more screen down after the end of the visible range"
2348            );
2349
2350                let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2351                assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
2352                let expected_hints = vec![
2353                    "47".to_string(),
2354                    "94".to_string(),
2355                    "139".to_string(),
2356                    "184".to_string(),
2357                ];
2358                assert_eq!(
2359                    expected_hints,
2360                    cached_hint_labels(editor),
2361                    "Should have hints from the new LSP response after the edit"
2362                );
2363                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2364
2365                let mut selection_in_cached_range = visible_range_after_scrolls.end;
2366                selection_in_cached_range.row -= visible_line_count.ceil() as u32;
2367                selection_in_cached_range
2368            })
2369            .unwrap();
2370
2371        editor
2372            .update(cx, |editor, window, cx| {
2373                editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
2374                    s.select_ranges([selection_in_cached_range..selection_in_cached_range])
2375                });
2376            })
2377            .unwrap();
2378        cx.executor().advance_clock(Duration::from_millis(
2379            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2380        ));
2381        cx.executor().run_until_parked();
2382        editor.update(cx, |_, _, _| {
2383            let ranges = lsp_request_ranges
2384                .lock()
2385                .drain(..)
2386                .sorted_by_key(|r| r.start)
2387                .collect::<Vec<_>>();
2388            assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
2389            assert_eq!(lsp_request_count.load(Ordering::Acquire), 4);
2390        }).unwrap();
2391
2392        editor
2393            .update(cx, |editor, window, cx| {
2394                editor.handle_input("++++more text++++", window, cx);
2395            })
2396            .unwrap();
2397        cx.executor().advance_clock(Duration::from_millis(
2398            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2399        ));
2400        cx.executor().run_until_parked();
2401        editor.update(cx, |editor, _window, cx| {
2402            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2403            ranges.sort_by_key(|r| r.start);
2404
2405            assert_eq!(ranges.len(), 3,
2406                "On edit, should scroll to selection and query a range around it: visible + same range above and below. Instead, got query ranges {ranges:?}");
2407            let above_query_range = &ranges[0];
2408            let visible_query_range = &ranges[1];
2409            let below_query_range = &ranges[2];
2410            assert!(above_query_range.end.character < visible_query_range.start.character || above_query_range.end.line + 1 == visible_query_range.start.line,
2411                "Above range {above_query_range:?} should be before visible range {visible_query_range:?}");
2412            assert!(visible_query_range.end.character < below_query_range.start.character || visible_query_range.end.line  + 1 == below_query_range.start.line,
2413                "Visible range {visible_query_range:?} should be before below range {below_query_range:?}");
2414            assert!(above_query_range.start.line < selection_in_cached_range.row,
2415                "Hints should be queried with the selected range after the query range start");
2416            assert!(below_query_range.end.line > selection_in_cached_range.row,
2417                "Hints should be queried with the selected range before the query range end");
2418            assert!(above_query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
2419                "Hints query range should contain one more screen before");
2420            assert!(below_query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
2421                "Hints query range should contain one more screen after");
2422
2423            let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2424            assert_eq!(lsp_requests, 7, "There should be a visible range and two ranges above and below it queried");
2425            let expected_hints = vec!["67".to_string(), "115".to_string(), "163".to_string()];
2426            assert_eq!(expected_hints, cached_hint_labels(editor),
2427                "Should have hints from the new LSP response after the edit");
2428            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2429        }).unwrap();
2430    }
2431
2432    fn editor_visible_range(
2433        editor: &WindowHandle<Editor>,
2434        cx: &mut gpui::TestAppContext,
2435    ) -> Range<Point> {
2436        let ranges = editor
2437            .update(cx, |editor, _window, cx| {
2438                editor.excerpts_for_inlay_hints_query(None, cx)
2439            })
2440            .unwrap();
2441        assert_eq!(
2442            ranges.len(),
2443            1,
2444            "Single buffer should produce a single excerpt with visible range"
2445        );
2446        let (_, (excerpt_buffer, _, excerpt_visible_range)) = ranges.into_iter().next().unwrap();
2447        excerpt_buffer.update(cx, |buffer, _| {
2448            let snapshot = buffer.snapshot();
2449            let start = buffer
2450                .anchor_before(excerpt_visible_range.start)
2451                .to_point(&snapshot);
2452            let end = buffer
2453                .anchor_after(excerpt_visible_range.end)
2454                .to_point(&snapshot);
2455            start..end
2456        })
2457    }
2458
2459    #[gpui::test]
2460    async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
2461        init_test(cx, |settings| {
2462            settings.defaults.inlay_hints = Some(InlayHintSettings {
2463                enabled: true,
2464                edit_debounce_ms: 0,
2465                scroll_debounce_ms: 0,
2466                show_type_hints: true,
2467                show_parameter_hints: true,
2468                show_other_hints: true,
2469                show_background: false,
2470            })
2471        });
2472
2473        let fs = FakeFs::new(cx.background_executor.clone());
2474        fs.insert_tree(
2475                path!("/a"),
2476                json!({
2477                    "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2478                    "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2479                }),
2480            )
2481            .await;
2482
2483        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2484
2485        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2486        let language = rust_lang();
2487        language_registry.add(language);
2488        let mut fake_servers = language_registry.register_fake_lsp(
2489            "Rust",
2490            FakeLspAdapter {
2491                capabilities: lsp::ServerCapabilities {
2492                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2493                    ..Default::default()
2494                },
2495                ..Default::default()
2496            },
2497        );
2498
2499        let (buffer_1, _handle1) = project
2500            .update(cx, |project, cx| {
2501                project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
2502            })
2503            .await
2504            .unwrap();
2505        let (buffer_2, _handle2) = project
2506            .update(cx, |project, cx| {
2507                project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
2508            })
2509            .await
2510            .unwrap();
2511        let multibuffer = cx.new(|cx| {
2512            let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2513            multibuffer.push_excerpts(
2514                buffer_1.clone(),
2515                [
2516                    ExcerptRange {
2517                        context: Point::new(0, 0)..Point::new(2, 0),
2518                        primary: None,
2519                    },
2520                    ExcerptRange {
2521                        context: Point::new(4, 0)..Point::new(11, 0),
2522                        primary: None,
2523                    },
2524                    ExcerptRange {
2525                        context: Point::new(22, 0)..Point::new(33, 0),
2526                        primary: None,
2527                    },
2528                    ExcerptRange {
2529                        context: Point::new(44, 0)..Point::new(55, 0),
2530                        primary: None,
2531                    },
2532                    ExcerptRange {
2533                        context: Point::new(56, 0)..Point::new(66, 0),
2534                        primary: None,
2535                    },
2536                    ExcerptRange {
2537                        context: Point::new(67, 0)..Point::new(77, 0),
2538                        primary: None,
2539                    },
2540                ],
2541                cx,
2542            );
2543            multibuffer.push_excerpts(
2544                buffer_2.clone(),
2545                [
2546                    ExcerptRange {
2547                        context: Point::new(0, 1)..Point::new(2, 1),
2548                        primary: None,
2549                    },
2550                    ExcerptRange {
2551                        context: Point::new(4, 1)..Point::new(11, 1),
2552                        primary: None,
2553                    },
2554                    ExcerptRange {
2555                        context: Point::new(22, 1)..Point::new(33, 1),
2556                        primary: None,
2557                    },
2558                    ExcerptRange {
2559                        context: Point::new(44, 1)..Point::new(55, 1),
2560                        primary: None,
2561                    },
2562                    ExcerptRange {
2563                        context: Point::new(56, 1)..Point::new(66, 1),
2564                        primary: None,
2565                    },
2566                    ExcerptRange {
2567                        context: Point::new(67, 1)..Point::new(77, 1),
2568                        primary: None,
2569                    },
2570                ],
2571                cx,
2572            );
2573            multibuffer
2574        });
2575
2576        cx.executor().run_until_parked();
2577        let editor = cx.add_window(|window, cx| {
2578            Editor::for_multibuffer(multibuffer, Some(project.clone()), true, window, cx)
2579        });
2580
2581        let editor_edited = Arc::new(AtomicBool::new(false));
2582        let fake_server = fake_servers.next().await.unwrap();
2583        let closure_editor_edited = Arc::clone(&editor_edited);
2584        fake_server
2585            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2586                let task_editor_edited = Arc::clone(&closure_editor_edited);
2587                async move {
2588                    let hint_text = if params.text_document.uri
2589                        == lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
2590                    {
2591                        "main hint"
2592                    } else if params.text_document.uri
2593                        == lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
2594                    {
2595                        "other hint"
2596                    } else {
2597                        panic!("unexpected uri: {:?}", params.text_document.uri);
2598                    };
2599
2600                    // one hint per excerpt
2601                    let positions = [
2602                        lsp::Position::new(0, 2),
2603                        lsp::Position::new(4, 2),
2604                        lsp::Position::new(22, 2),
2605                        lsp::Position::new(44, 2),
2606                        lsp::Position::new(56, 2),
2607                        lsp::Position::new(67, 2),
2608                    ];
2609                    let out_of_range_hint = lsp::InlayHint {
2610                        position: lsp::Position::new(
2611                            params.range.start.line + 99,
2612                            params.range.start.character + 99,
2613                        ),
2614                        label: lsp::InlayHintLabel::String(
2615                            "out of excerpt range, should be ignored".to_string(),
2616                        ),
2617                        kind: None,
2618                        text_edits: None,
2619                        tooltip: None,
2620                        padding_left: None,
2621                        padding_right: None,
2622                        data: None,
2623                    };
2624
2625                    let edited = task_editor_edited.load(Ordering::Acquire);
2626                    Ok(Some(
2627                        std::iter::once(out_of_range_hint)
2628                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2629                                lsp::InlayHint {
2630                                    position,
2631                                    label: lsp::InlayHintLabel::String(format!(
2632                                        "{hint_text}{E} #{i}",
2633                                        E = if edited { "(edited)" } else { "" },
2634                                    )),
2635                                    kind: None,
2636                                    text_edits: None,
2637                                    tooltip: None,
2638                                    padding_left: None,
2639                                    padding_right: None,
2640                                    data: None,
2641                                }
2642                            }))
2643                            .collect(),
2644                    ))
2645                }
2646            })
2647            .next()
2648            .await;
2649        cx.executor().run_until_parked();
2650
2651        editor
2652            .update(cx, |editor, _window, cx| {
2653                let expected_hints = vec![
2654                    "main hint #0".to_string(),
2655                    "main hint #1".to_string(),
2656                    "main hint #2".to_string(),
2657                    "main hint #3".to_string(),
2658                    "main hint #4".to_string(),
2659                    "main hint #5".to_string(),
2660                ];
2661                assert_eq!(
2662                    expected_hints,
2663                    sorted_cached_hint_labels(editor),
2664                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2665                );
2666                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2667            })
2668            .unwrap();
2669
2670        editor
2671            .update(cx, |editor, window, cx| {
2672                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2673                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2674                });
2675                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2676                    s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2677                });
2678                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2679                    s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2680                });
2681            })
2682            .unwrap();
2683        cx.executor().run_until_parked();
2684        editor
2685            .update(cx, |editor, _window, cx| {
2686                let expected_hints = vec![
2687                    "main hint #0".to_string(),
2688                    "main hint #1".to_string(),
2689                    "main hint #2".to_string(),
2690                    "main hint #3".to_string(),
2691                    "main hint #4".to_string(),
2692                    "main hint #5".to_string(),
2693                    "other hint #0".to_string(),
2694                    "other hint #1".to_string(),
2695                    "other hint #2".to_string(),
2696                ];
2697                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2698                    "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2699                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2700            })
2701            .unwrap();
2702
2703        editor
2704            .update(cx, |editor, window, cx| {
2705                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2706                    s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2707                });
2708            })
2709            .unwrap();
2710        cx.executor().advance_clock(Duration::from_millis(
2711            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2712        ));
2713        cx.executor().run_until_parked();
2714        editor
2715            .update(cx, |editor, _window, cx| {
2716                let expected_hints = vec![
2717                    "main hint #0".to_string(),
2718                    "main hint #1".to_string(),
2719                    "main hint #2".to_string(),
2720                    "main hint #3".to_string(),
2721                    "main hint #4".to_string(),
2722                    "main hint #5".to_string(),
2723                    "other hint #0".to_string(),
2724                    "other hint #1".to_string(),
2725                    "other hint #2".to_string(),
2726                    "other hint #3".to_string(),
2727                    "other hint #4".to_string(),
2728                    "other hint #5".to_string(),
2729                ];
2730                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2731                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2732                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2733            })
2734            .unwrap();
2735
2736        editor
2737            .update(cx, |editor, window, cx| {
2738                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2739                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2740                });
2741            })
2742            .unwrap();
2743        cx.executor().advance_clock(Duration::from_millis(
2744            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2745        ));
2746        cx.executor().run_until_parked();
2747        editor
2748            .update(cx, |editor, _window, cx| {
2749                let expected_hints = vec![
2750                    "main hint #0".to_string(),
2751                    "main hint #1".to_string(),
2752                    "main hint #2".to_string(),
2753                    "main hint #3".to_string(),
2754                    "main hint #4".to_string(),
2755                    "main hint #5".to_string(),
2756                    "other hint #0".to_string(),
2757                    "other hint #1".to_string(),
2758                    "other hint #2".to_string(),
2759                    "other hint #3".to_string(),
2760                    "other hint #4".to_string(),
2761                    "other hint #5".to_string(),
2762                ];
2763                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2764                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2765                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2766            })
2767            .unwrap();
2768
2769        editor_edited.store(true, Ordering::Release);
2770        editor
2771            .update(cx, |editor, window, cx| {
2772                editor.change_selections(None, window, cx, |s| {
2773                    s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
2774                });
2775                editor.handle_input("++++more text++++", window, cx);
2776            })
2777            .unwrap();
2778        cx.executor().run_until_parked();
2779        editor
2780            .update(cx, |editor, _window, cx| {
2781                let expected_hints = vec![
2782                    "main hint #0".to_string(),
2783                    "main hint #1".to_string(),
2784                    "main hint #2".to_string(),
2785                    "main hint #3".to_string(),
2786                    "main hint #4".to_string(),
2787                    "main hint #5".to_string(),
2788                    "other hint(edited) #0".to_string(),
2789                    "other hint(edited) #1".to_string(),
2790                    "other hint(edited) #2".to_string(),
2791                ];
2792                assert_eq!(
2793                    expected_hints,
2794                    sorted_cached_hint_labels(editor),
2795                    "After multibuffer edit, editor gets scrolled back to the last selection; \
2796                all hints should be invalidated and required for all of its visible excerpts"
2797                );
2798                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2799            })
2800            .unwrap();
2801    }
2802
2803    #[gpui::test]
2804    async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
2805        init_test(cx, |settings| {
2806            settings.defaults.inlay_hints = Some(InlayHintSettings {
2807                enabled: true,
2808                edit_debounce_ms: 0,
2809                scroll_debounce_ms: 0,
2810                show_type_hints: false,
2811                show_parameter_hints: false,
2812                show_other_hints: false,
2813                show_background: false,
2814            })
2815        });
2816
2817        let fs = FakeFs::new(cx.background_executor.clone());
2818        fs.insert_tree(
2819            path!("/a"),
2820            json!({
2821                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2822                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2823            }),
2824        )
2825        .await;
2826
2827        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2828
2829        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2830        language_registry.add(rust_lang());
2831        let mut fake_servers = language_registry.register_fake_lsp(
2832            "Rust",
2833            FakeLspAdapter {
2834                capabilities: lsp::ServerCapabilities {
2835                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2836                    ..Default::default()
2837                },
2838                ..Default::default()
2839            },
2840        );
2841
2842        let (buffer_1, _handle) = project
2843            .update(cx, |project, cx| {
2844                project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
2845            })
2846            .await
2847            .unwrap();
2848        let (buffer_2, _handle2) = project
2849            .update(cx, |project, cx| {
2850                project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
2851            })
2852            .await
2853            .unwrap();
2854        let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2855        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2856            let buffer_1_excerpts = multibuffer.push_excerpts(
2857                buffer_1.clone(),
2858                [ExcerptRange {
2859                    context: Point::new(0, 0)..Point::new(2, 0),
2860                    primary: None,
2861                }],
2862                cx,
2863            );
2864            let buffer_2_excerpts = multibuffer.push_excerpts(
2865                buffer_2.clone(),
2866                [ExcerptRange {
2867                    context: Point::new(0, 1)..Point::new(2, 1),
2868                    primary: None,
2869                }],
2870                cx,
2871            );
2872            (buffer_1_excerpts, buffer_2_excerpts)
2873        });
2874
2875        assert!(!buffer_1_excerpts.is_empty());
2876        assert!(!buffer_2_excerpts.is_empty());
2877
2878        cx.executor().run_until_parked();
2879        let editor = cx.add_window(|window, cx| {
2880            Editor::for_multibuffer(multibuffer, Some(project.clone()), true, window, cx)
2881        });
2882        let editor_edited = Arc::new(AtomicBool::new(false));
2883        let fake_server = fake_servers.next().await.unwrap();
2884        let closure_editor_edited = Arc::clone(&editor_edited);
2885        fake_server
2886            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2887                let task_editor_edited = Arc::clone(&closure_editor_edited);
2888                async move {
2889                    let hint_text = if params.text_document.uri
2890                        == lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
2891                    {
2892                        "main hint"
2893                    } else if params.text_document.uri
2894                        == lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
2895                    {
2896                        "other hint"
2897                    } else {
2898                        panic!("unexpected uri: {:?}", params.text_document.uri);
2899                    };
2900
2901                    let positions = [
2902                        lsp::Position::new(0, 2),
2903                        lsp::Position::new(4, 2),
2904                        lsp::Position::new(22, 2),
2905                        lsp::Position::new(44, 2),
2906                        lsp::Position::new(56, 2),
2907                        lsp::Position::new(67, 2),
2908                    ];
2909                    let out_of_range_hint = lsp::InlayHint {
2910                        position: lsp::Position::new(
2911                            params.range.start.line + 99,
2912                            params.range.start.character + 99,
2913                        ),
2914                        label: lsp::InlayHintLabel::String(
2915                            "out of excerpt range, should be ignored".to_string(),
2916                        ),
2917                        kind: None,
2918                        text_edits: None,
2919                        tooltip: None,
2920                        padding_left: None,
2921                        padding_right: None,
2922                        data: None,
2923                    };
2924
2925                    let edited = task_editor_edited.load(Ordering::Acquire);
2926                    Ok(Some(
2927                        std::iter::once(out_of_range_hint)
2928                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2929                                lsp::InlayHint {
2930                                    position,
2931                                    label: lsp::InlayHintLabel::String(format!(
2932                                        "{hint_text}{} #{i}",
2933                                        if edited { "(edited)" } else { "" },
2934                                    )),
2935                                    kind: None,
2936                                    text_edits: None,
2937                                    tooltip: None,
2938                                    padding_left: None,
2939                                    padding_right: None,
2940                                    data: None,
2941                                }
2942                            }))
2943                            .collect(),
2944                    ))
2945                }
2946            })
2947            .next()
2948            .await;
2949        cx.executor().run_until_parked();
2950        editor
2951            .update(cx, |editor, _, cx| {
2952                assert_eq!(
2953                    vec!["main hint #0".to_string(), "other hint #0".to_string()],
2954                    sorted_cached_hint_labels(editor),
2955                    "Cache should update for both excerpts despite hints display was disabled"
2956                );
2957                assert!(
2958                visible_hint_labels(editor, cx).is_empty(),
2959                "All hints are disabled and should not be shown despite being present in the cache"
2960            );
2961            })
2962            .unwrap();
2963
2964        editor
2965            .update(cx, |editor, _, cx| {
2966                editor.buffer().update(cx, |multibuffer, cx| {
2967                    multibuffer.remove_excerpts(buffer_2_excerpts, cx)
2968                })
2969            })
2970            .unwrap();
2971        cx.executor().run_until_parked();
2972        editor
2973            .update(cx, |editor, _, cx| {
2974                assert_eq!(
2975                    vec!["main hint #0".to_string()],
2976                    cached_hint_labels(editor),
2977                    "For the removed excerpt, should clean corresponding cached hints"
2978                );
2979                assert!(
2980                visible_hint_labels(editor, cx).is_empty(),
2981                "All hints are disabled and should not be shown despite being present in the cache"
2982            );
2983            })
2984            .unwrap();
2985
2986        update_test_language_settings(cx, |settings| {
2987            settings.defaults.inlay_hints = Some(InlayHintSettings {
2988                enabled: true,
2989                edit_debounce_ms: 0,
2990                scroll_debounce_ms: 0,
2991                show_type_hints: true,
2992                show_parameter_hints: true,
2993                show_other_hints: true,
2994                show_background: false,
2995            })
2996        });
2997        cx.executor().run_until_parked();
2998        editor
2999            .update(cx, |editor, _, cx| {
3000                let expected_hints = vec!["main hint #0".to_string()];
3001                assert_eq!(
3002                    expected_hints,
3003                    cached_hint_labels(editor),
3004                    "Hint display settings change should not change the cache"
3005                );
3006                assert_eq!(
3007                    expected_hints,
3008                    visible_hint_labels(editor, cx),
3009                    "Settings change should make cached hints visible"
3010                );
3011            })
3012            .unwrap();
3013    }
3014
3015    #[gpui::test]
3016    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
3017        init_test(cx, |settings| {
3018            settings.defaults.inlay_hints = Some(InlayHintSettings {
3019                enabled: true,
3020                edit_debounce_ms: 0,
3021                scroll_debounce_ms: 0,
3022                show_type_hints: true,
3023                show_parameter_hints: true,
3024                show_other_hints: true,
3025                show_background: false,
3026            })
3027        });
3028
3029        let fs = FakeFs::new(cx.background_executor.clone());
3030        fs.insert_tree(
3031            path!("/a"),
3032            json!({
3033                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
3034                "other.rs": "// Test file",
3035            }),
3036        )
3037        .await;
3038
3039        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3040
3041        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3042        language_registry.add(rust_lang());
3043        language_registry.register_fake_lsp(
3044            "Rust",
3045            FakeLspAdapter {
3046                capabilities: lsp::ServerCapabilities {
3047                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3048                    ..Default::default()
3049                },
3050                initializer: Some(Box::new(move |fake_server| {
3051                    let lsp_request_count = Arc::new(AtomicU32::new(0));
3052                    fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(
3053                        move |params, _| {
3054                            let i = lsp_request_count.fetch_add(1, Ordering::Release) + 1;
3055                            async move {
3056                                assert_eq!(
3057                                    params.text_document.uri,
3058                                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3059                                );
3060                                let query_start = params.range.start;
3061                                Ok(Some(vec![lsp::InlayHint {
3062                                    position: query_start,
3063                                    label: lsp::InlayHintLabel::String(i.to_string()),
3064                                    kind: None,
3065                                    text_edits: None,
3066                                    tooltip: None,
3067                                    padding_left: None,
3068                                    padding_right: None,
3069                                    data: None,
3070                                }]))
3071                            }
3072                        },
3073                    );
3074                })),
3075                ..Default::default()
3076            },
3077        );
3078
3079        let buffer = project
3080            .update(cx, |project, cx| {
3081                project.open_local_buffer(path!("/a/main.rs"), cx)
3082            })
3083            .await
3084            .unwrap();
3085        let editor =
3086            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3087
3088        cx.executor().run_until_parked();
3089        editor
3090            .update(cx, |editor, window, cx| {
3091                editor.change_selections(None, window, cx, |s| {
3092                    s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3093                })
3094            })
3095            .unwrap();
3096        cx.executor().run_until_parked();
3097        editor
3098            .update(cx, |editor, _, cx| {
3099                let expected_hints = vec!["1".to_string()];
3100                assert_eq!(expected_hints, cached_hint_labels(editor));
3101                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3102            })
3103            .unwrap();
3104    }
3105
3106    #[gpui::test]
3107    async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) {
3108        init_test(cx, |settings| {
3109            settings.defaults.inlay_hints = Some(InlayHintSettings {
3110                enabled: false,
3111                edit_debounce_ms: 0,
3112                scroll_debounce_ms: 0,
3113                show_type_hints: true,
3114                show_parameter_hints: true,
3115                show_other_hints: true,
3116                show_background: false,
3117            })
3118        });
3119
3120        let (_, editor, _fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
3121            let lsp_request_count = Arc::new(AtomicU32::new(0));
3122            fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
3123                let lsp_request_count = lsp_request_count.clone();
3124                async move {
3125                    assert_eq!(
3126                        params.text_document.uri,
3127                        lsp::Url::from_file_path(file_with_hints).unwrap(),
3128                    );
3129
3130                    let i = lsp_request_count.fetch_add(1, Ordering::SeqCst) + 1;
3131                    Ok(Some(vec![lsp::InlayHint {
3132                        position: lsp::Position::new(0, i),
3133                        label: lsp::InlayHintLabel::String(i.to_string()),
3134                        kind: None,
3135                        text_edits: None,
3136                        tooltip: None,
3137                        padding_left: None,
3138                        padding_right: None,
3139                        data: None,
3140                    }]))
3141                }
3142            });
3143        })
3144        .await;
3145
3146        editor
3147            .update(cx, |editor, window, cx| {
3148                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3149            })
3150            .unwrap();
3151
3152        cx.executor().run_until_parked();
3153        editor
3154            .update(cx, |editor, _, cx| {
3155                let expected_hints = vec!["1".to_string()];
3156                assert_eq!(
3157                    expected_hints,
3158                    cached_hint_labels(editor),
3159                    "Should display inlays after toggle despite them disabled in settings"
3160                );
3161                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3162            })
3163            .unwrap();
3164
3165        editor
3166            .update(cx, |editor, window, cx| {
3167                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3168            })
3169            .unwrap();
3170        cx.executor().run_until_parked();
3171        editor
3172            .update(cx, |editor, _, cx| {
3173                assert!(
3174                    cached_hint_labels(editor).is_empty(),
3175                    "Should clear hints after 2nd toggle"
3176                );
3177                assert!(visible_hint_labels(editor, cx).is_empty());
3178            })
3179            .unwrap();
3180
3181        update_test_language_settings(cx, |settings| {
3182            settings.defaults.inlay_hints = Some(InlayHintSettings {
3183                enabled: true,
3184                edit_debounce_ms: 0,
3185                scroll_debounce_ms: 0,
3186                show_type_hints: true,
3187                show_parameter_hints: true,
3188                show_other_hints: true,
3189                show_background: false,
3190            })
3191        });
3192        cx.executor().run_until_parked();
3193        editor
3194            .update(cx, |editor, _, cx| {
3195                let expected_hints = vec!["2".to_string()];
3196                assert_eq!(
3197                    expected_hints,
3198                    cached_hint_labels(editor),
3199                    "Should query LSP hints for the 2nd time after enabling hints in settings"
3200                );
3201                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3202            })
3203            .unwrap();
3204
3205        editor
3206            .update(cx, |editor, window, cx| {
3207                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3208            })
3209            .unwrap();
3210        cx.executor().run_until_parked();
3211        editor
3212            .update(cx, |editor, _, cx| {
3213                assert!(
3214                    cached_hint_labels(editor).is_empty(),
3215                    "Should clear hints after enabling in settings and a 3rd toggle"
3216                );
3217                assert!(visible_hint_labels(editor, cx).is_empty());
3218            })
3219            .unwrap();
3220
3221        editor
3222            .update(cx, |editor, window, cx| {
3223                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3224            })
3225            .unwrap();
3226        cx.executor().run_until_parked();
3227        editor.update(cx, |editor, _, cx| {
3228            let expected_hints = vec!["3".to_string()];
3229            assert_eq!(
3230                expected_hints,
3231                cached_hint_labels(editor),
3232                "Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
3233            );
3234            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3235        }).unwrap();
3236    }
3237
3238    #[gpui::test]
3239    async fn test_inlays_at_the_same_place(cx: &mut gpui::TestAppContext) {
3240        init_test(cx, |settings| {
3241            settings.defaults.inlay_hints = Some(InlayHintSettings {
3242                enabled: true,
3243                edit_debounce_ms: 0,
3244                scroll_debounce_ms: 0,
3245                show_type_hints: true,
3246                show_parameter_hints: true,
3247                show_other_hints: true,
3248                show_background: false,
3249            })
3250        });
3251
3252        let fs = FakeFs::new(cx.background_executor.clone());
3253        fs.insert_tree(
3254            path!("/a"),
3255            json!({
3256                "main.rs": "fn main() {
3257                    let x = 42;
3258                    std::thread::scope(|s| {
3259                        s.spawn(|| {
3260                            let _x = x;
3261                        });
3262                    });
3263                }",
3264                "other.rs": "// Test file",
3265            }),
3266        )
3267        .await;
3268
3269        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3270
3271        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3272        language_registry.add(rust_lang());
3273        language_registry.register_fake_lsp(
3274            "Rust",
3275            FakeLspAdapter {
3276                capabilities: lsp::ServerCapabilities {
3277                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3278                    ..Default::default()
3279                },
3280                initializer: Some(Box::new(move |fake_server| {
3281                    fake_server.handle_request::<lsp::request::InlayHintRequest, _, _>(
3282                        move |params, _| async move {
3283                            assert_eq!(
3284                                params.text_document.uri,
3285                                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3286                            );
3287                            Ok(Some(
3288                                serde_json::from_value(json!([
3289                                    {
3290                                        "position": {
3291                                            "line": 3,
3292                                            "character": 16
3293                                        },
3294                                        "label": "move",
3295                                        "paddingLeft": false,
3296                                        "paddingRight": false
3297                                    },
3298                                    {
3299                                        "position": {
3300                                            "line": 3,
3301                                            "character": 16
3302                                        },
3303                                        "label": "(",
3304                                        "paddingLeft": false,
3305                                        "paddingRight": false
3306                                    },
3307                                    {
3308                                        "position": {
3309                                            "line": 3,
3310                                            "character": 16
3311                                        },
3312                                        "label": [
3313                                            {
3314                                                "value": "&x"
3315                                            }
3316                                        ],
3317                                        "paddingLeft": false,
3318                                        "paddingRight": false,
3319                                        "data": {
3320                                            "file_id": 0
3321                                        }
3322                                    },
3323                                    {
3324                                        "position": {
3325                                            "line": 3,
3326                                            "character": 16
3327                                        },
3328                                        "label": ")",
3329                                        "paddingLeft": false,
3330                                        "paddingRight": true
3331                                    },
3332                                    // not a correct syntax, but checks that same symbols at the same place
3333                                    // are not deduplicated
3334                                    {
3335                                        "position": {
3336                                            "line": 3,
3337                                            "character": 16
3338                                        },
3339                                        "label": ")",
3340                                        "paddingLeft": false,
3341                                        "paddingRight": true
3342                                    },
3343                                ]))
3344                                .unwrap(),
3345                            ))
3346                        },
3347                    );
3348                })),
3349                ..FakeLspAdapter::default()
3350            },
3351        );
3352
3353        let buffer = project
3354            .update(cx, |project, cx| {
3355                project.open_local_buffer(path!("/a/main.rs"), cx)
3356            })
3357            .await
3358            .unwrap();
3359        let editor =
3360            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3361
3362        cx.executor().run_until_parked();
3363        editor
3364            .update(cx, |editor, window, cx| {
3365                editor.change_selections(None, window, cx, |s| {
3366                    s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3367                })
3368            })
3369            .unwrap();
3370        cx.executor().run_until_parked();
3371        editor
3372            .update(cx, |editor, _window, cx| {
3373                let expected_hints = vec![
3374                    "move".to_string(),
3375                    "(".to_string(),
3376                    "&x".to_string(),
3377                    ") ".to_string(),
3378                    ") ".to_string(),
3379                ];
3380                assert_eq!(
3381                    expected_hints,
3382                    cached_hint_labels(editor),
3383                    "Editor inlay hints should repeat server's order when placed at the same spot"
3384                );
3385                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3386            })
3387            .unwrap();
3388    }
3389
3390    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
3391        cx.update(|cx| {
3392            let settings_store = SettingsStore::test(cx);
3393            cx.set_global(settings_store);
3394            theme::init(theme::LoadThemes::JustBase, cx);
3395            release_channel::init(SemanticVersion::default(), cx);
3396            client::init_settings(cx);
3397            language::init(cx);
3398            Project::init_settings(cx);
3399            workspace::init_settings(cx);
3400            crate::init(cx);
3401        });
3402
3403        update_test_language_settings(cx, f);
3404    }
3405
3406    async fn prepare_test_objects(
3407        cx: &mut TestAppContext,
3408        initialize: impl 'static + Send + Fn(&mut FakeLanguageServer, &'static str) + Send + Sync,
3409    ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
3410        let fs = FakeFs::new(cx.background_executor.clone());
3411        fs.insert_tree(
3412            path!("/a"),
3413            json!({
3414                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
3415                "other.rs": "// Test file",
3416            }),
3417        )
3418        .await;
3419
3420        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3421        let file_path = path!("/a/main.rs");
3422
3423        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3424        language_registry.add(rust_lang());
3425        let mut fake_servers = language_registry.register_fake_lsp(
3426            "Rust",
3427            FakeLspAdapter {
3428                capabilities: lsp::ServerCapabilities {
3429                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3430                    ..Default::default()
3431                },
3432                initializer: Some(Box::new(move |server| initialize(server, file_path))),
3433                ..Default::default()
3434            },
3435        );
3436
3437        let buffer = project
3438            .update(cx, |project, cx| {
3439                project.open_local_buffer(path!("/a/main.rs"), cx)
3440            })
3441            .await
3442            .unwrap();
3443        let editor =
3444            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3445
3446        editor
3447            .update(cx, |editor, _, cx| {
3448                assert!(cached_hint_labels(editor).is_empty());
3449                assert!(visible_hint_labels(editor, cx).is_empty());
3450            })
3451            .unwrap();
3452
3453        cx.executor().run_until_parked();
3454        let fake_server = fake_servers.next().await.unwrap();
3455        (file_path, editor, fake_server)
3456    }
3457
3458    // 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.
3459    // Ensure a stable order for testing.
3460    fn sorted_cached_hint_labels(editor: &Editor) -> Vec<String> {
3461        let mut labels = cached_hint_labels(editor);
3462        labels.sort();
3463        labels
3464    }
3465
3466    pub fn cached_hint_labels(editor: &Editor) -> Vec<String> {
3467        let mut labels = Vec::new();
3468        for excerpt_hints in editor.inlay_hint_cache().hints.values() {
3469            let excerpt_hints = excerpt_hints.read();
3470            for id in &excerpt_hints.ordered_hints {
3471                let hint = &excerpt_hints.hints_by_id[id];
3472                let mut label = hint.text();
3473                if hint.padding_left {
3474                    label.insert(0, ' ');
3475                }
3476                if hint.padding_right {
3477                    label.push_str(" ");
3478                }
3479                labels.push(label);
3480            }
3481        }
3482
3483        labels
3484    }
3485
3486    pub fn visible_hint_labels(editor: &Editor, cx: &Context<Editor>) -> Vec<String> {
3487        editor
3488            .visible_inlay_hints(cx)
3489            .into_iter()
3490            .map(|hint| hint.text.to_string())
3491            .collect()
3492    }
3493}