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