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