inlay_hint_cache.rs

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