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