inlay_hint_cache.rs

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