inlay_hint_cache.rs

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