inlay_hint_cache.rs

   1/// Stores and updates all data received from LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint">textDocument/inlayHint</a> requests.
   2/// Has nothing to do with other inlays, e.g. copilot suggestions — those are stored elsewhere.
   3/// On every update, cache may query for more inlay hints and update inlays on the screen.
   4///
   5/// Inlays stored on screen are in [`crate::display_map::inlay_map`] and this cache is the only way to update any inlay hint data in the visible hints in the inlay map.
   6/// For determining the update to the `inlay_map`, the cache requires a list of visible inlay hints — all other hints are not relevant and their separate updates are not influencing the cache work.
   7///
   8/// Due to the way the data is stored for both visible inlays and the cache, every inlay (and inlay hint) collection is editor-specific, so a single buffer may have multiple sets of inlays of open on different panes.
   9use std::{
  10    cmp,
  11    ops::{ControlFlow, Range},
  12    sync::Arc,
  13    time::Duration,
  14};
  15
  16use crate::{
  17    Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot, display_map::Inlay,
  18};
  19use anyhow::Context as _;
  20use clock::Global;
  21use futures::future;
  22use gpui::{AppContext as _, AsyncApp, Context, Entity, Task, Window};
  23use language::{Buffer, BufferSnapshot, language_settings::InlayHintKind};
  24use parking_lot::RwLock;
  25use project::{InlayHint, ResolveState};
  26
  27use collections::{HashMap, HashSet, hash_map};
  28use language::language_settings::InlayHintSettings;
  29use smol::lock::Semaphore;
  30use sum_tree::Bias;
  31use text::{BufferId, ToOffset, ToPoint};
  32use util::{ResultExt, post_inc};
  33
  34pub struct InlayHintCache {
  35    hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
  36    allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
  37    version: usize,
  38    pub(super) enabled: bool,
  39    modifiers_override: bool,
  40    enabled_in_settings: bool,
  41    update_tasks: HashMap<ExcerptId, TasksForRanges>,
  42    refresh_task: Task<()>,
  43    invalidate_debounce: Option<Duration>,
  44    append_debounce: Option<Duration>,
  45    lsp_request_limiter: Arc<Semaphore>,
  46}
  47
  48#[derive(Debug)]
  49struct TasksForRanges {
  50    tasks: Vec<Task<()>>,
  51    sorted_ranges: Vec<Range<language::Anchor>>,
  52}
  53
  54#[derive(Debug)]
  55struct CachedExcerptHints {
  56    version: usize,
  57    buffer_version: Global,
  58    buffer_id: BufferId,
  59    ordered_hints: Vec<InlayId>,
  60    hints_by_id: HashMap<InlayId, InlayHint>,
  61}
  62
  63/// A logic to apply when querying for new inlay hints and deciding what to do with the old entries in the cache in case of conflicts.
  64#[derive(Debug, Clone, Copy)]
  65pub(super) enum InvalidationStrategy {
  66    /// Hints reset is <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlayHint_refresh">requested</a> by the LSP server.
  67    /// Demands to re-query all inlay hints needed and invalidate all cached entries, but does not require instant update with invalidation.
  68    ///
  69    /// Despite nothing forbids language server from sending this request on every edit, it is expected to be sent only when certain internal server state update, invisible for the editor otherwise.
  70    RefreshRequested,
  71    /// Multibuffer excerpt(s) and/or singleton buffer(s) were edited at least on one place.
  72    /// Neither editor nor LSP is able to tell which open file hints' are not affected, so all of them have to be invalidated, re-queried and do that fast enough to avoid being slow, but also debounce to avoid loading hints on every fast keystroke sequence.
  73    BufferEdited,
  74    /// A new file got opened/new excerpt was added to a multibuffer/a [multi]buffer was scrolled to a new position.
  75    /// No invalidation should be done at all, all new hints are added to the cache.
  76    ///
  77    /// A special case is the settings change: in addition to LSP capabilities, Zed allows omitting certain hint kinds (defined by the corresponding LSP part: type/parameter/other).
  78    /// This does not lead to cache invalidation, but would require cache usage for determining which hints are not displayed and issuing an update to inlays on the screen.
  79    None,
  80}
  81
  82/// A splice to send into the `inlay_map` for updating the visible inlays on the screen.
  83/// "Visible" inlays may not be displayed in the buffer right away, but those are ready to be displayed on further buffer scroll, pane item activations, etc. right away without additional LSP queries or settings changes.
  84/// The data in the cache is never used directly for displaying inlays on the screen, to avoid races with updates from LSP queries and sync overhead.
  85/// Splice is picked to help avoid extra hint flickering and "jumps" on the screen.
  86#[derive(Debug, Default)]
  87pub(super) struct InlaySplice {
  88    pub to_remove: Vec<InlayId>,
  89    pub to_insert: Vec<Inlay>,
  90}
  91
  92#[derive(Debug)]
  93struct ExcerptHintsUpdate {
  94    excerpt_id: ExcerptId,
  95    remove_from_visible: HashSet<InlayId>,
  96    remove_from_cache: HashSet<InlayId>,
  97    add_to_cache: Vec<InlayHint>,
  98}
  99
 100#[derive(Debug, Clone, Copy)]
 101struct ExcerptQuery {
 102    buffer_id: BufferId,
 103    excerpt_id: ExcerptId,
 104    cache_version: usize,
 105    invalidate: InvalidationStrategy,
 106    reason: &'static str,
 107}
 108
 109impl InvalidationStrategy {
 110    fn should_invalidate(&self) -> bool {
 111        matches!(
 112            self,
 113            InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited
 114        )
 115    }
 116}
 117
 118impl TasksForRanges {
 119    fn new(query_ranges: QueryRanges, task: Task<()>) -> Self {
 120        Self {
 121            tasks: vec![task],
 122            sorted_ranges: query_ranges.into_sorted_query_ranges(),
 123        }
 124    }
 125
 126    fn update_cached_tasks(
 127        &mut self,
 128        buffer_snapshot: &BufferSnapshot,
 129        query_ranges: QueryRanges,
 130        invalidate: InvalidationStrategy,
 131        spawn_task: impl FnOnce(QueryRanges) -> Task<()>,
 132    ) {
 133        let query_ranges = if invalidate.should_invalidate() {
 134            self.tasks.clear();
 135            self.sorted_ranges = query_ranges.clone().into_sorted_query_ranges();
 136            query_ranges
 137        } else {
 138            let mut non_cached_query_ranges = query_ranges;
 139            non_cached_query_ranges.before_visible = non_cached_query_ranges
 140                .before_visible
 141                .into_iter()
 142                .flat_map(|query_range| {
 143                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
 144                })
 145                .collect();
 146            non_cached_query_ranges.visible = non_cached_query_ranges
 147                .visible
 148                .into_iter()
 149                .flat_map(|query_range| {
 150                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
 151                })
 152                .collect();
 153            non_cached_query_ranges.after_visible = non_cached_query_ranges
 154                .after_visible
 155                .into_iter()
 156                .flat_map(|query_range| {
 157                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
 158                })
 159                .collect();
 160            non_cached_query_ranges
 161        };
 162
 163        if !query_ranges.is_empty() {
 164            self.tasks.push(spawn_task(query_ranges));
 165        }
 166    }
 167
 168    fn remove_cached_ranges_from_query(
 169        &mut self,
 170        buffer_snapshot: &BufferSnapshot,
 171        query_range: Range<language::Anchor>,
 172    ) -> Vec<Range<language::Anchor>> {
 173        let mut ranges_to_query = Vec::new();
 174        let mut latest_cached_range = None::<&mut Range<language::Anchor>>;
 175        for cached_range in self
 176            .sorted_ranges
 177            .iter_mut()
 178            .skip_while(|cached_range| {
 179                cached_range
 180                    .end
 181                    .cmp(&query_range.start, buffer_snapshot)
 182                    .is_lt()
 183            })
 184            .take_while(|cached_range| {
 185                cached_range
 186                    .start
 187                    .cmp(&query_range.end, buffer_snapshot)
 188                    .is_le()
 189            })
 190        {
 191            match latest_cached_range {
 192                Some(latest_cached_range) => {
 193                    if latest_cached_range.end.offset.saturating_add(1) < cached_range.start.offset
 194                    {
 195                        ranges_to_query.push(latest_cached_range.end..cached_range.start);
 196                        cached_range.start = latest_cached_range.end;
 197                    }
 198                }
 199                None => {
 200                    if query_range
 201                        .start
 202                        .cmp(&cached_range.start, buffer_snapshot)
 203                        .is_lt()
 204                    {
 205                        ranges_to_query.push(query_range.start..cached_range.start);
 206                        cached_range.start = query_range.start;
 207                    }
 208                }
 209            }
 210            latest_cached_range = Some(cached_range);
 211        }
 212
 213        match latest_cached_range {
 214            Some(latest_cached_range) => {
 215                if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset {
 216                    ranges_to_query.push(latest_cached_range.end..query_range.end);
 217                    latest_cached_range.end = query_range.end;
 218                }
 219            }
 220            None => {
 221                ranges_to_query.push(query_range.clone());
 222                self.sorted_ranges.push(query_range);
 223                self.sorted_ranges
 224                    .sort_by(|range_a, range_b| range_a.start.cmp(&range_b.start, buffer_snapshot));
 225            }
 226        }
 227
 228        ranges_to_query
 229    }
 230
 231    fn invalidate_range(&mut self, buffer: &BufferSnapshot, range: &Range<language::Anchor>) {
 232        self.sorted_ranges = self
 233            .sorted_ranges
 234            .drain(..)
 235            .filter_map(|mut cached_range| {
 236                if cached_range.start.cmp(&range.end, buffer).is_gt()
 237                    || cached_range.end.cmp(&range.start, buffer).is_lt()
 238                {
 239                    Some(vec![cached_range])
 240                } else if cached_range.start.cmp(&range.start, buffer).is_ge()
 241                    && cached_range.end.cmp(&range.end, buffer).is_le()
 242                {
 243                    None
 244                } else if range.start.cmp(&cached_range.start, buffer).is_ge()
 245                    && range.end.cmp(&cached_range.end, buffer).is_le()
 246                {
 247                    Some(vec![
 248                        cached_range.start..range.start,
 249                        range.end..cached_range.end,
 250                    ])
 251                } else if cached_range.start.cmp(&range.start, buffer).is_ge() {
 252                    cached_range.start = range.end;
 253                    Some(vec![cached_range])
 254                } else {
 255                    cached_range.end = range.start;
 256                    Some(vec![cached_range])
 257                }
 258            })
 259            .flatten()
 260            .collect();
 261    }
 262}
 263
 264impl InlayHintCache {
 265    pub(super) fn new(inlay_hint_settings: InlayHintSettings) -> Self {
 266        Self {
 267            allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
 268            enabled: inlay_hint_settings.enabled,
 269            modifiers_override: false,
 270            enabled_in_settings: inlay_hint_settings.enabled,
 271            hints: HashMap::default(),
 272            update_tasks: HashMap::default(),
 273            refresh_task: Task::ready(()),
 274            invalidate_debounce: debounce_value(inlay_hint_settings.edit_debounce_ms),
 275            append_debounce: debounce_value(inlay_hint_settings.scroll_debounce_ms),
 276            version: 0,
 277            lsp_request_limiter: Arc::new(Semaphore::new(MAX_CONCURRENT_LSP_REQUESTS)),
 278        }
 279    }
 280
 281    /// Checks inlay hint settings for enabled hint kinds and general enabled state.
 282    /// Generates corresponding inlay_map splice updates on settings changes.
 283    /// Does not update inlay hint cache state on disabling or inlay hint kinds change: only reenabling forces new LSP queries.
 284    pub(super) fn update_settings(
 285        &mut self,
 286        multi_buffer: &Entity<MultiBuffer>,
 287        new_hint_settings: InlayHintSettings,
 288        visible_hints: Vec<Inlay>,
 289        cx: &mut Context<Editor>,
 290    ) -> ControlFlow<Option<InlaySplice>> {
 291        let old_enabled = self.enabled;
 292        // If the setting for inlay hints has changed, update `enabled`. This condition avoids inlay
 293        // hint visibility changes when other settings change (such as theme).
 294        //
 295        // Another option might be to store whether the user has manually toggled inlay hint
 296        // visibility, and prefer this. This could lead to confusion as it means inlay hint
 297        // visibility would not change when updating the setting if they were ever toggled.
 298        if new_hint_settings.enabled != self.enabled_in_settings {
 299            self.enabled = new_hint_settings.enabled;
 300            self.enabled_in_settings = new_hint_settings.enabled;
 301            self.modifiers_override = false;
 302        };
 303        self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms);
 304        self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms);
 305        let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
 306        match (old_enabled, self.enabled) {
 307            (false, false) => {
 308                self.allowed_hint_kinds = new_allowed_hint_kinds;
 309                ControlFlow::Break(None)
 310            }
 311            (true, true) => {
 312                if new_allowed_hint_kinds == self.allowed_hint_kinds {
 313                    ControlFlow::Break(None)
 314                } else {
 315                    let new_splice = self.new_allowed_hint_kinds_splice(
 316                        multi_buffer,
 317                        &visible_hints,
 318                        &new_allowed_hint_kinds,
 319                        cx,
 320                    );
 321                    if new_splice.is_some() {
 322                        self.version += 1;
 323                        self.allowed_hint_kinds = new_allowed_hint_kinds;
 324                    }
 325                    ControlFlow::Break(new_splice)
 326                }
 327            }
 328            (true, false) => {
 329                self.modifiers_override = false;
 330                self.allowed_hint_kinds = new_allowed_hint_kinds;
 331                if self.hints.is_empty() {
 332                    ControlFlow::Break(None)
 333                } else {
 334                    self.clear();
 335                    ControlFlow::Break(Some(InlaySplice {
 336                        to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(),
 337                        to_insert: Vec::new(),
 338                    }))
 339                }
 340            }
 341            (false, true) => {
 342                self.modifiers_override = false;
 343                self.allowed_hint_kinds = new_allowed_hint_kinds;
 344                ControlFlow::Continue(())
 345            }
 346        }
 347    }
 348
 349    pub(super) fn modifiers_override(&mut self, new_override: bool) -> Option<bool> {
 350        if self.modifiers_override == new_override {
 351            return None;
 352        }
 353        self.modifiers_override = new_override;
 354        if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
 355        {
 356            self.clear();
 357            Some(false)
 358        } else {
 359            Some(true)
 360        }
 361    }
 362
 363    pub(super) fn toggle(&mut self, enabled: bool) -> bool {
 364        if self.enabled == enabled {
 365            return false;
 366        }
 367        self.enabled = enabled;
 368        self.modifiers_override = false;
 369        if !enabled {
 370            self.clear();
 371        }
 372        true
 373    }
 374
 375    /// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
 376    /// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
 377    /// followed by the delayed queries of the same range above and below the visible one.
 378    /// This way, subsequent refresh invocations are less likely to trigger LSP queries for the invisible ranges.
 379    pub(super) fn spawn_hint_refresh(
 380        &mut self,
 381        reason_description: &'static str,
 382        excerpts_to_query: HashMap<ExcerptId, (Entity<Buffer>, Global, Range<usize>)>,
 383        invalidate: InvalidationStrategy,
 384        ignore_debounce: bool,
 385        cx: &mut Context<Editor>,
 386    ) -> Option<InlaySplice> {
 387        if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
 388        {
 389            return None;
 390        }
 391        let mut invalidated_hints = Vec::new();
 392        if invalidate.should_invalidate() {
 393            self.update_tasks
 394                .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
 395            self.hints.retain(|cached_excerpt, cached_hints| {
 396                let retain = excerpts_to_query.contains_key(cached_excerpt);
 397                if !retain {
 398                    invalidated_hints.extend(cached_hints.read().ordered_hints.iter().copied());
 399                }
 400                retain
 401            });
 402        }
 403        if excerpts_to_query.is_empty() && invalidated_hints.is_empty() {
 404            return None;
 405        }
 406
 407        let cache_version = self.version + 1;
 408        let debounce_duration = if ignore_debounce {
 409            None
 410        } else if invalidate.should_invalidate() {
 411            self.invalidate_debounce
 412        } else {
 413            self.append_debounce
 414        };
 415        self.refresh_task = cx.spawn(async move |editor, cx| {
 416            if let Some(debounce_duration) = debounce_duration {
 417                cx.background_executor().timer(debounce_duration).await;
 418            }
 419
 420            editor
 421                .update(cx, |editor, cx| {
 422                    spawn_new_update_tasks(
 423                        editor,
 424                        reason_description,
 425                        excerpts_to_query,
 426                        invalidate,
 427                        cache_version,
 428                        cx,
 429                    )
 430                })
 431                .ok();
 432        });
 433
 434        if invalidated_hints.is_empty() {
 435            None
 436        } else {
 437            Some(InlaySplice {
 438                to_remove: invalidated_hints,
 439                to_insert: Vec::new(),
 440            })
 441        }
 442    }
 443
 444    fn new_allowed_hint_kinds_splice(
 445        &self,
 446        multi_buffer: &Entity<MultiBuffer>,
 447        visible_hints: &[Inlay],
 448        new_kinds: &HashSet<Option<InlayHintKind>>,
 449        cx: &mut Context<Editor>,
 450    ) -> Option<InlaySplice> {
 451        let old_kinds = &self.allowed_hint_kinds;
 452        if new_kinds == old_kinds {
 453            return None;
 454        }
 455
 456        let mut to_remove = Vec::new();
 457        let mut to_insert = Vec::new();
 458        let mut shown_hints_to_remove = visible_hints.iter().fold(
 459            HashMap::<ExcerptId, Vec<(Anchor, InlayId)>>::default(),
 460            |mut current_hints, inlay| {
 461                current_hints
 462                    .entry(inlay.position.excerpt_id)
 463                    .or_default()
 464                    .push((inlay.position, inlay.id));
 465                current_hints
 466            },
 467        );
 468
 469        let multi_buffer = multi_buffer.read(cx);
 470        let multi_buffer_snapshot = multi_buffer.snapshot(cx);
 471
 472        for (excerpt_id, excerpt_cached_hints) in &self.hints {
 473            let shown_excerpt_hints_to_remove =
 474                shown_hints_to_remove.entry(*excerpt_id).or_default();
 475            let excerpt_cached_hints = excerpt_cached_hints.read();
 476            let mut excerpt_cache = excerpt_cached_hints.ordered_hints.iter().fuse().peekable();
 477            shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
 478                let Some(buffer) = shown_anchor
 479                    .buffer_id
 480                    .and_then(|buffer_id| multi_buffer.buffer(buffer_id))
 481                else {
 482                    return false;
 483                };
 484                let buffer_snapshot = buffer.read(cx).snapshot();
 485                loop {
 486                    match excerpt_cache.peek() {
 487                        Some(&cached_hint_id) => {
 488                            let cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id];
 489                            if cached_hint_id == shown_hint_id {
 490                                excerpt_cache.next();
 491                                return !new_kinds.contains(&cached_hint.kind);
 492                            }
 493
 494                            match cached_hint
 495                                .position
 496                                .cmp(&shown_anchor.text_anchor, &buffer_snapshot)
 497                            {
 498                                cmp::Ordering::Less | cmp::Ordering::Equal => {
 499                                    if !old_kinds.contains(&cached_hint.kind)
 500                                        && new_kinds.contains(&cached_hint.kind)
 501                                    {
 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::{ExcerptRange, scroll::Autoscroll, test::editor_lsp_test_context::rust_lang};
1296    use futures::StreamExt;
1297    use gpui::{AppContext as _, Context, SemanticVersion, TestAppContext, WindowHandle};
1298    use itertools::Itertools as _;
1299    use language::{Capability, FakeLspAdapter, language_settings::AllLanguageSettingsContent};
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.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
1330                move |params, _| {
1331                    let task_lsp_request_count = Arc::clone(&lsp_request_count);
1332                    async move {
1333                        let i = task_lsp_request_count.fetch_add(1, Ordering::Release) + 1;
1334                        assert_eq!(
1335                            params.text_document.uri,
1336                            lsp::Url::from_file_path(file_with_hints).unwrap(),
1337                        );
1338                        Ok(Some(vec![lsp::InlayHint {
1339                            position: lsp::Position::new(0, i),
1340                            label: lsp::InlayHintLabel::String(i.to_string()),
1341                            kind: None,
1342                            text_edits: None,
1343                            tooltip: None,
1344                            padding_left: None,
1345                            padding_right: None,
1346                            data: None,
1347                        }]))
1348                    }
1349                },
1350            );
1351        })
1352        .await;
1353        cx.executor().run_until_parked();
1354
1355        editor
1356            .update(cx, |editor, _window, cx| {
1357                let expected_hints = vec!["1".to_string()];
1358                assert_eq!(
1359                    expected_hints,
1360                    cached_hint_labels(editor),
1361                    "Should get its first hints when opening the editor"
1362                );
1363                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1364                let inlay_cache = editor.inlay_hint_cache();
1365                assert_eq!(
1366                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1367                    "Cache should use editor settings to get the allowed hint kinds"
1368                );
1369            })
1370            .unwrap();
1371
1372        editor
1373            .update(cx, |editor, window, cx| {
1374                editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1375                editor.handle_input("some change", window, cx);
1376            })
1377            .unwrap();
1378        cx.executor().run_until_parked();
1379        editor
1380            .update(cx, |editor, _window, cx| {
1381                let expected_hints = vec!["2".to_string()];
1382                assert_eq!(
1383                    expected_hints,
1384                    cached_hint_labels(editor),
1385                    "Should get new hints after an edit"
1386                );
1387                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1388                let inlay_cache = editor.inlay_hint_cache();
1389                assert_eq!(
1390                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1391                    "Cache should use editor settings to get the allowed hint kinds"
1392                );
1393            })
1394            .unwrap();
1395
1396        fake_server
1397            .request::<lsp::request::InlayHintRefreshRequest>(())
1398            .await
1399            .expect("inlay refresh request failed");
1400        cx.executor().run_until_parked();
1401        editor
1402            .update(cx, |editor, _window, cx| {
1403                let expected_hints = vec!["3".to_string()];
1404                assert_eq!(
1405                    expected_hints,
1406                    cached_hint_labels(editor),
1407                    "Should get new hints after hint refresh/ request"
1408                );
1409                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1410                let inlay_cache = editor.inlay_hint_cache();
1411                assert_eq!(
1412                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1413                    "Cache should use editor settings to get the allowed hint kinds"
1414                );
1415            })
1416            .unwrap();
1417    }
1418
1419    #[gpui::test]
1420    async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
1421        init_test(cx, |settings| {
1422            settings.defaults.inlay_hints = Some(InlayHintSettings {
1423                enabled: true,
1424                edit_debounce_ms: 0,
1425                scroll_debounce_ms: 0,
1426                show_type_hints: true,
1427                show_parameter_hints: true,
1428                show_other_hints: true,
1429                show_background: false,
1430                toggle_on_modifiers_press: None,
1431            })
1432        });
1433
1434        let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
1435            let lsp_request_count = Arc::new(AtomicU32::new(0));
1436            fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
1437                move |params, _| {
1438                    let task_lsp_request_count = Arc::clone(&lsp_request_count);
1439                    async move {
1440                        assert_eq!(
1441                            params.text_document.uri,
1442                            lsp::Url::from_file_path(file_with_hints).unwrap(),
1443                        );
1444                        let current_call_id =
1445                            Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1446                        Ok(Some(vec![lsp::InlayHint {
1447                            position: lsp::Position::new(0, current_call_id),
1448                            label: lsp::InlayHintLabel::String(current_call_id.to_string()),
1449                            kind: None,
1450                            text_edits: None,
1451                            tooltip: None,
1452                            padding_left: None,
1453                            padding_right: None,
1454                            data: None,
1455                        }]))
1456                    }
1457                },
1458            );
1459        })
1460        .await;
1461        cx.executor().run_until_parked();
1462
1463        editor
1464            .update(cx, |editor, _, cx| {
1465                let expected_hints = vec!["0".to_string()];
1466                assert_eq!(
1467                    expected_hints,
1468                    cached_hint_labels(editor),
1469                    "Should get its first hints when opening the editor"
1470                );
1471                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1472            })
1473            .unwrap();
1474
1475        let progress_token = "test_progress_token";
1476        fake_server
1477            .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1478                token: lsp::ProgressToken::String(progress_token.to_string()),
1479            })
1480            .await
1481            .expect("work done progress create request failed");
1482        cx.executor().run_until_parked();
1483        fake_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1484            token: lsp::ProgressToken::String(progress_token.to_string()),
1485            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
1486                lsp::WorkDoneProgressBegin::default(),
1487            )),
1488        });
1489        cx.executor().run_until_parked();
1490
1491        editor
1492            .update(cx, |editor, _, cx| {
1493                let expected_hints = vec!["0".to_string()];
1494                assert_eq!(
1495                    expected_hints,
1496                    cached_hint_labels(editor),
1497                    "Should not update hints while the work task is running"
1498                );
1499                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1500            })
1501            .unwrap();
1502
1503        fake_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
1504            token: lsp::ProgressToken::String(progress_token.to_string()),
1505            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
1506                lsp::WorkDoneProgressEnd::default(),
1507            )),
1508        });
1509        cx.executor().run_until_parked();
1510
1511        editor
1512            .update(cx, |editor, _, cx| {
1513                let expected_hints = vec!["1".to_string()];
1514                assert_eq!(
1515                    expected_hints,
1516                    cached_hint_labels(editor),
1517                    "New hints should be queried after the work task is done"
1518                );
1519                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1520            })
1521            .unwrap();
1522    }
1523
1524    #[gpui::test]
1525    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1526        init_test(cx, |settings| {
1527            settings.defaults.inlay_hints = Some(InlayHintSettings {
1528                enabled: true,
1529                edit_debounce_ms: 0,
1530                scroll_debounce_ms: 0,
1531                show_type_hints: true,
1532                show_parameter_hints: true,
1533                show_other_hints: true,
1534                show_background: false,
1535                toggle_on_modifiers_press: None,
1536            })
1537        });
1538
1539        let fs = FakeFs::new(cx.background_executor.clone());
1540        fs.insert_tree(
1541            path!("/a"),
1542            json!({
1543                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1544                "other.md": "Test md file with some text",
1545            }),
1546        )
1547        .await;
1548
1549        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1550
1551        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1552        let mut rs_fake_servers = None;
1553        let mut md_fake_servers = None;
1554        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1555            language_registry.add(Arc::new(Language::new(
1556                LanguageConfig {
1557                    name: name.into(),
1558                    matcher: LanguageMatcher {
1559                        path_suffixes: vec![path_suffix.to_string()],
1560                        ..Default::default()
1561                    },
1562                    ..Default::default()
1563                },
1564                Some(tree_sitter_rust::LANGUAGE.into()),
1565            )));
1566            let fake_servers = language_registry.register_fake_lsp(
1567                name,
1568                FakeLspAdapter {
1569                    name,
1570                    capabilities: lsp::ServerCapabilities {
1571                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1572                        ..Default::default()
1573                    },
1574                    initializer: Some(Box::new({
1575                        move |fake_server| {
1576                            let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1577                            let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1578                            fake_server
1579                                .set_request_handler::<lsp::request::InlayHintRequest, _, _>(
1580                                    move |params, _| {
1581                                        let i = match name {
1582                                            "Rust" => {
1583                                                assert_eq!(
1584                                                    params.text_document.uri,
1585                                                    lsp::Url::from_file_path(path!("/a/main.rs"))
1586                                                        .unwrap(),
1587                                                );
1588                                                rs_lsp_request_count.fetch_add(1, Ordering::Release)
1589                                                    + 1
1590                                            }
1591                                            "Markdown" => {
1592                                                assert_eq!(
1593                                                    params.text_document.uri,
1594                                                    lsp::Url::from_file_path(path!("/a/other.md"))
1595                                                        .unwrap(),
1596                                                );
1597                                                md_lsp_request_count.fetch_add(1, Ordering::Release)
1598                                                    + 1
1599                                            }
1600                                            unexpected => {
1601                                                panic!("Unexpected language: {unexpected}")
1602                                            }
1603                                        };
1604
1605                                        async move {
1606                                            let query_start = params.range.start;
1607                                            Ok(Some(vec![lsp::InlayHint {
1608                                                position: query_start,
1609                                                label: lsp::InlayHintLabel::String(i.to_string()),
1610                                                kind: None,
1611                                                text_edits: None,
1612                                                tooltip: None,
1613                                                padding_left: None,
1614                                                padding_right: None,
1615                                                data: None,
1616                                            }]))
1617                                        }
1618                                    },
1619                                );
1620                        }
1621                    })),
1622                    ..Default::default()
1623                },
1624            );
1625            match name {
1626                "Rust" => rs_fake_servers = Some(fake_servers),
1627                "Markdown" => md_fake_servers = Some(fake_servers),
1628                _ => unreachable!(),
1629            }
1630        }
1631
1632        let rs_buffer = project
1633            .update(cx, |project, cx| {
1634                project.open_local_buffer(path!("/a/main.rs"), cx)
1635            })
1636            .await
1637            .unwrap();
1638        let rs_editor = cx.add_window(|window, cx| {
1639            Editor::for_buffer(rs_buffer, Some(project.clone()), window, cx)
1640        });
1641        cx.executor().run_until_parked();
1642
1643        let _rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1644        cx.executor().run_until_parked();
1645        rs_editor
1646            .update(cx, |editor, _window, cx| {
1647                let expected_hints = vec!["1".to_string()];
1648                assert_eq!(
1649                    expected_hints,
1650                    cached_hint_labels(editor),
1651                    "Should get its first hints when opening the editor"
1652                );
1653                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1654            })
1655            .unwrap();
1656
1657        cx.executor().run_until_parked();
1658        let md_buffer = project
1659            .update(cx, |project, cx| {
1660                project.open_local_buffer(path!("/a/other.md"), cx)
1661            })
1662            .await
1663            .unwrap();
1664        let md_editor =
1665            cx.add_window(|window, cx| Editor::for_buffer(md_buffer, Some(project), window, cx));
1666        cx.executor().run_until_parked();
1667
1668        let _md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1669        cx.executor().run_until_parked();
1670        md_editor
1671            .update(cx, |editor, _window, cx| {
1672                let expected_hints = vec!["1".to_string()];
1673                assert_eq!(
1674                    expected_hints,
1675                    cached_hint_labels(editor),
1676                    "Markdown editor should have a separate version, repeating Rust editor rules"
1677                );
1678                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1679            })
1680            .unwrap();
1681
1682        rs_editor
1683            .update(cx, |editor, window, cx| {
1684                editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1685                editor.handle_input("some rs change", window, cx);
1686            })
1687            .unwrap();
1688        cx.executor().run_until_parked();
1689        rs_editor
1690            .update(cx, |editor, _window, cx| {
1691                // TODO: Here, we do not get "2", because inserting another language server will trigger `RefreshInlayHints` event from the `LspStore`
1692                // A project is listened in every editor, so each of them will react to this event.
1693                //
1694                // We do not have language server IDs for remote projects, so cannot easily say on the editor level,
1695                // whether we should ignore a particular `RefreshInlayHints` event.
1696                let expected_hints = vec!["3".to_string()];
1697                assert_eq!(
1698                    expected_hints,
1699                    cached_hint_labels(editor),
1700                    "Rust inlay cache should change after the edit"
1701                );
1702                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1703            })
1704            .unwrap();
1705        md_editor
1706            .update(cx, |editor, _window, cx| {
1707                let expected_hints = vec!["1".to_string()];
1708                assert_eq!(
1709                    expected_hints,
1710                    cached_hint_labels(editor),
1711                    "Markdown editor should not be affected by Rust editor changes"
1712                );
1713                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1714            })
1715            .unwrap();
1716
1717        md_editor
1718            .update(cx, |editor, window, cx| {
1719                editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
1720                editor.handle_input("some md change", window, cx);
1721            })
1722            .unwrap();
1723        cx.executor().run_until_parked();
1724        md_editor
1725            .update(cx, |editor, _window, cx| {
1726                let expected_hints = vec!["2".to_string()];
1727                assert_eq!(
1728                    expected_hints,
1729                    cached_hint_labels(editor),
1730                    "Rust editor should not be affected by Markdown editor changes"
1731                );
1732                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1733            })
1734            .unwrap();
1735        rs_editor
1736            .update(cx, |editor, _window, cx| {
1737                let expected_hints = vec!["3".to_string()];
1738                assert_eq!(
1739                    expected_hints,
1740                    cached_hint_labels(editor),
1741                    "Markdown editor should also change independently"
1742                );
1743                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1744            })
1745            .unwrap();
1746    }
1747
1748    #[gpui::test]
1749    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1750        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1751        init_test(cx, |settings| {
1752            settings.defaults.inlay_hints = Some(InlayHintSettings {
1753                enabled: true,
1754                edit_debounce_ms: 0,
1755                scroll_debounce_ms: 0,
1756                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1757                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1758                show_other_hints: allowed_hint_kinds.contains(&None),
1759                show_background: false,
1760                toggle_on_modifiers_press: None,
1761            })
1762        });
1763
1764        let lsp_request_count = Arc::new(AtomicUsize::new(0));
1765        let (_, editor, fake_server) = prepare_test_objects(cx, {
1766            let lsp_request_count = lsp_request_count.clone();
1767            move |fake_server, file_with_hints| {
1768                let lsp_request_count = lsp_request_count.clone();
1769                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
1770                    move |params, _| {
1771                        lsp_request_count.fetch_add(1, Ordering::Release);
1772                        async move {
1773                            assert_eq!(
1774                                params.text_document.uri,
1775                                lsp::Url::from_file_path(file_with_hints).unwrap(),
1776                            );
1777                            Ok(Some(vec![
1778                                lsp::InlayHint {
1779                                    position: lsp::Position::new(0, 1),
1780                                    label: lsp::InlayHintLabel::String("type hint".to_string()),
1781                                    kind: Some(lsp::InlayHintKind::TYPE),
1782                                    text_edits: None,
1783                                    tooltip: None,
1784                                    padding_left: None,
1785                                    padding_right: None,
1786                                    data: None,
1787                                },
1788                                lsp::InlayHint {
1789                                    position: lsp::Position::new(0, 2),
1790                                    label: lsp::InlayHintLabel::String(
1791                                        "parameter hint".to_string(),
1792                                    ),
1793                                    kind: Some(lsp::InlayHintKind::PARAMETER),
1794                                    text_edits: None,
1795                                    tooltip: None,
1796                                    padding_left: None,
1797                                    padding_right: None,
1798                                    data: None,
1799                                },
1800                                lsp::InlayHint {
1801                                    position: lsp::Position::new(0, 3),
1802                                    label: lsp::InlayHintLabel::String("other hint".to_string()),
1803                                    kind: None,
1804                                    text_edits: None,
1805                                    tooltip: None,
1806                                    padding_left: None,
1807                                    padding_right: None,
1808                                    data: None,
1809                                },
1810                            ]))
1811                        }
1812                    },
1813                );
1814            }
1815        })
1816        .await;
1817        cx.executor().run_until_parked();
1818
1819        editor
1820            .update(cx, |editor, _, cx| {
1821                assert_eq!(
1822                    lsp_request_count.load(Ordering::Relaxed),
1823                    1,
1824                    "Should query new hints once"
1825                );
1826                assert_eq!(
1827                    vec![
1828                        "type hint".to_string(),
1829                        "parameter hint".to_string(),
1830                        "other hint".to_string(),
1831                    ],
1832                    cached_hint_labels(editor),
1833                    "Should get its first hints when opening the editor"
1834                );
1835                assert_eq!(
1836                    vec!["type hint".to_string(), "other hint".to_string()],
1837                    visible_hint_labels(editor, cx)
1838                );
1839                let inlay_cache = editor.inlay_hint_cache();
1840                assert_eq!(
1841                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1842                    "Cache should use editor settings to get the allowed hint kinds"
1843                );
1844            })
1845            .unwrap();
1846
1847        fake_server
1848            .request::<lsp::request::InlayHintRefreshRequest>(())
1849            .await
1850            .expect("inlay refresh request failed");
1851        cx.executor().run_until_parked();
1852        editor
1853            .update(cx, |editor, _, cx| {
1854                assert_eq!(
1855                    lsp_request_count.load(Ordering::Relaxed),
1856                    2,
1857                    "Should load new hints twice"
1858                );
1859                assert_eq!(
1860                    vec![
1861                        "type hint".to_string(),
1862                        "parameter hint".to_string(),
1863                        "other hint".to_string(),
1864                    ],
1865                    cached_hint_labels(editor),
1866                    "Cached hints should not change due to allowed hint kinds settings update"
1867                );
1868                assert_eq!(
1869                    vec!["type hint".to_string(), "other hint".to_string()],
1870                    visible_hint_labels(editor, cx)
1871                );
1872            })
1873            .unwrap();
1874
1875        for (new_allowed_hint_kinds, expected_visible_hints) in [
1876            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1877            (
1878                HashSet::from_iter([Some(InlayHintKind::Type)]),
1879                vec!["type hint".to_string()],
1880            ),
1881            (
1882                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1883                vec!["parameter hint".to_string()],
1884            ),
1885            (
1886                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1887                vec!["type hint".to_string(), "other hint".to_string()],
1888            ),
1889            (
1890                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1891                vec!["parameter hint".to_string(), "other hint".to_string()],
1892            ),
1893            (
1894                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1895                vec!["type hint".to_string(), "parameter hint".to_string()],
1896            ),
1897            (
1898                HashSet::from_iter([
1899                    None,
1900                    Some(InlayHintKind::Type),
1901                    Some(InlayHintKind::Parameter),
1902                ]),
1903                vec![
1904                    "type hint".to_string(),
1905                    "parameter hint".to_string(),
1906                    "other hint".to_string(),
1907                ],
1908            ),
1909        ] {
1910            update_test_language_settings(cx, |settings| {
1911                settings.defaults.inlay_hints = Some(InlayHintSettings {
1912                    enabled: true,
1913                    edit_debounce_ms: 0,
1914                    scroll_debounce_ms: 0,
1915                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1916                    show_parameter_hints: new_allowed_hint_kinds
1917                        .contains(&Some(InlayHintKind::Parameter)),
1918                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1919                    show_background: false,
1920                    toggle_on_modifiers_press: None,
1921                })
1922            });
1923            cx.executor().run_until_parked();
1924            editor.update(cx, |editor, _, cx| {
1925                assert_eq!(
1926                    lsp_request_count.load(Ordering::Relaxed),
1927                    2,
1928                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1929                );
1930                assert_eq!(
1931                    vec![
1932                        "type hint".to_string(),
1933                        "parameter hint".to_string(),
1934                        "other hint".to_string(),
1935                    ],
1936                    cached_hint_labels(editor),
1937                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1938                );
1939                assert_eq!(
1940                    expected_visible_hints,
1941                    visible_hint_labels(editor, cx),
1942                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1943                );
1944                let inlay_cache = editor.inlay_hint_cache();
1945                assert_eq!(
1946                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1947                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1948                );
1949            }).unwrap();
1950        }
1951
1952        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1953        update_test_language_settings(cx, |settings| {
1954            settings.defaults.inlay_hints = Some(InlayHintSettings {
1955                enabled: false,
1956                edit_debounce_ms: 0,
1957                scroll_debounce_ms: 0,
1958                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1959                show_parameter_hints: another_allowed_hint_kinds
1960                    .contains(&Some(InlayHintKind::Parameter)),
1961                show_other_hints: another_allowed_hint_kinds.contains(&None),
1962                show_background: false,
1963                toggle_on_modifiers_press: None,
1964            })
1965        });
1966        cx.executor().run_until_parked();
1967        editor
1968            .update(cx, |editor, _, cx| {
1969                assert_eq!(
1970                    lsp_request_count.load(Ordering::Relaxed),
1971                    2,
1972                    "Should not load new hints when hints got disabled"
1973                );
1974                assert!(
1975                    cached_hint_labels(editor).is_empty(),
1976                    "Should clear the cache when hints got disabled"
1977                );
1978                assert!(
1979                    visible_hint_labels(editor, cx).is_empty(),
1980                    "Should clear visible hints when hints got disabled"
1981                );
1982                let inlay_cache = editor.inlay_hint_cache();
1983                assert_eq!(
1984                    inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1985                    "Should update its allowed hint kinds even when hints got disabled"
1986                );
1987            })
1988            .unwrap();
1989
1990        fake_server
1991            .request::<lsp::request::InlayHintRefreshRequest>(())
1992            .await
1993            .expect("inlay refresh request failed");
1994        cx.executor().run_until_parked();
1995        editor
1996            .update(cx, |editor, _window, cx| {
1997                assert_eq!(
1998                    lsp_request_count.load(Ordering::Relaxed),
1999                    2,
2000                    "Should not load new hints when they got disabled"
2001                );
2002                assert!(cached_hint_labels(editor).is_empty());
2003                assert!(visible_hint_labels(editor, cx).is_empty());
2004            })
2005            .unwrap();
2006
2007        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
2008        update_test_language_settings(cx, |settings| {
2009            settings.defaults.inlay_hints = Some(InlayHintSettings {
2010                enabled: true,
2011                edit_debounce_ms: 0,
2012                scroll_debounce_ms: 0,
2013                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
2014                show_parameter_hints: final_allowed_hint_kinds
2015                    .contains(&Some(InlayHintKind::Parameter)),
2016                show_other_hints: final_allowed_hint_kinds.contains(&None),
2017                show_background: false,
2018                toggle_on_modifiers_press: None,
2019            })
2020        });
2021        cx.executor().run_until_parked();
2022        editor
2023            .update(cx, |editor, _, cx| {
2024                assert_eq!(
2025                    lsp_request_count.load(Ordering::Relaxed),
2026                    3,
2027                    "Should query for new hints when they got re-enabled"
2028                );
2029                assert_eq!(
2030                    vec![
2031                        "type hint".to_string(),
2032                        "parameter hint".to_string(),
2033                        "other hint".to_string(),
2034                    ],
2035                    cached_hint_labels(editor),
2036                    "Should get its cached hints fully repopulated after the hints got re-enabled"
2037                );
2038                assert_eq!(
2039                    vec!["parameter hint".to_string()],
2040                    visible_hint_labels(editor, cx),
2041                    "Should get its visible hints repopulated and filtered after the h"
2042                );
2043                let inlay_cache = editor.inlay_hint_cache();
2044                assert_eq!(
2045                    inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
2046                    "Cache should update editor settings when hints got re-enabled"
2047                );
2048            })
2049            .unwrap();
2050
2051        fake_server
2052            .request::<lsp::request::InlayHintRefreshRequest>(())
2053            .await
2054            .expect("inlay refresh request failed");
2055        cx.executor().run_until_parked();
2056        editor
2057            .update(cx, |editor, _, cx| {
2058                assert_eq!(
2059                    lsp_request_count.load(Ordering::Relaxed),
2060                    4,
2061                    "Should query for new hints again"
2062                );
2063                assert_eq!(
2064                    vec![
2065                        "type hint".to_string(),
2066                        "parameter hint".to_string(),
2067                        "other hint".to_string(),
2068                    ],
2069                    cached_hint_labels(editor),
2070                );
2071                assert_eq!(
2072                    vec!["parameter hint".to_string()],
2073                    visible_hint_labels(editor, cx),
2074                );
2075            })
2076            .unwrap();
2077    }
2078
2079    #[gpui::test]
2080    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
2081        init_test(cx, |settings| {
2082            settings.defaults.inlay_hints = Some(InlayHintSettings {
2083                enabled: true,
2084                edit_debounce_ms: 0,
2085                scroll_debounce_ms: 0,
2086                show_type_hints: true,
2087                show_parameter_hints: true,
2088                show_other_hints: true,
2089                show_background: false,
2090                toggle_on_modifiers_press: None,
2091            })
2092        });
2093
2094        let lsp_request_count = Arc::new(AtomicU32::new(0));
2095        let (_, editor, _) = prepare_test_objects(cx, {
2096            let lsp_request_count = lsp_request_count.clone();
2097            move |fake_server, file_with_hints| {
2098                let lsp_request_count = lsp_request_count.clone();
2099                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
2100                    move |params, _| {
2101                        let lsp_request_count = lsp_request_count.clone();
2102                        async move {
2103                            let i = lsp_request_count.fetch_add(1, Ordering::SeqCst) + 1;
2104                            assert_eq!(
2105                                params.text_document.uri,
2106                                lsp::Url::from_file_path(file_with_hints).unwrap(),
2107                            );
2108                            Ok(Some(vec![lsp::InlayHint {
2109                                position: lsp::Position::new(0, i),
2110                                label: lsp::InlayHintLabel::String(i.to_string()),
2111                                kind: None,
2112                                text_edits: None,
2113                                tooltip: None,
2114                                padding_left: None,
2115                                padding_right: None,
2116                                data: None,
2117                            }]))
2118                        }
2119                    },
2120                );
2121            }
2122        })
2123        .await;
2124
2125        let mut expected_changes = Vec::new();
2126        for change_after_opening in [
2127            "initial change #1",
2128            "initial change #2",
2129            "initial change #3",
2130        ] {
2131            editor
2132                .update(cx, |editor, window, cx| {
2133                    editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
2134                    editor.handle_input(change_after_opening, window, cx);
2135                })
2136                .unwrap();
2137            expected_changes.push(change_after_opening);
2138        }
2139
2140        cx.executor().run_until_parked();
2141
2142        editor
2143            .update(cx, |editor, _window, cx| {
2144                let current_text = editor.text(cx);
2145                for change in &expected_changes {
2146                    assert!(
2147                        current_text.contains(change),
2148                        "Should apply all changes made"
2149                    );
2150                }
2151                assert_eq!(
2152                    lsp_request_count.load(Ordering::Relaxed),
2153                    2,
2154                    "Should query new hints twice: for editor init and for the last edit that interrupted all others"
2155                );
2156                let expected_hints = vec!["2".to_string()];
2157                assert_eq!(
2158                    expected_hints,
2159                    cached_hint_labels(editor),
2160                    "Should get hints from the last edit landed only"
2161                );
2162                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2163            })
2164            .unwrap();
2165
2166        let mut edits = Vec::new();
2167        for async_later_change in [
2168            "another change #1",
2169            "another change #2",
2170            "another change #3",
2171        ] {
2172            expected_changes.push(async_later_change);
2173            let task_editor = editor;
2174            edits.push(cx.spawn(|mut cx| async move {
2175                task_editor
2176                    .update(&mut cx, |editor, window, cx| {
2177                        editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
2178                        editor.handle_input(async_later_change, window, cx);
2179                    })
2180                    .unwrap();
2181            }));
2182        }
2183        let _ = future::join_all(edits).await;
2184        cx.executor().run_until_parked();
2185
2186        editor
2187            .update(cx, |editor, _, cx| {
2188                let current_text = editor.text(cx);
2189                for change in &expected_changes {
2190                    assert!(
2191                        current_text.contains(change),
2192                        "Should apply all changes made"
2193                    );
2194                }
2195                assert_eq!(
2196                    lsp_request_count.load(Ordering::SeqCst),
2197                    3,
2198                    "Should query new hints one more time, for the last edit only"
2199                );
2200                let expected_hints = vec!["3".to_string()];
2201                assert_eq!(
2202                    expected_hints,
2203                    cached_hint_labels(editor),
2204                    "Should get hints from the last edit landed only"
2205                );
2206                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2207            })
2208            .unwrap();
2209    }
2210
2211    #[gpui::test(iterations = 10)]
2212    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
2213        init_test(cx, |settings| {
2214            settings.defaults.inlay_hints = Some(InlayHintSettings {
2215                enabled: true,
2216                edit_debounce_ms: 0,
2217                scroll_debounce_ms: 0,
2218                show_type_hints: true,
2219                show_parameter_hints: true,
2220                show_other_hints: true,
2221                show_background: false,
2222                toggle_on_modifiers_press: None,
2223            })
2224        });
2225
2226        let fs = FakeFs::new(cx.background_executor.clone());
2227        fs.insert_tree(
2228            path!("/a"),
2229            json!({
2230                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
2231                "other.rs": "// Test file",
2232            }),
2233        )
2234        .await;
2235
2236        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2237
2238        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2239        language_registry.add(rust_lang());
2240
2241        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
2242        let lsp_request_count = Arc::new(AtomicUsize::new(0));
2243        let mut fake_servers = language_registry.register_fake_lsp(
2244            "Rust",
2245            FakeLspAdapter {
2246                capabilities: lsp::ServerCapabilities {
2247                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2248                    ..Default::default()
2249                },
2250                initializer: Some(Box::new({
2251                    let lsp_request_ranges = lsp_request_ranges.clone();
2252                    let lsp_request_count = lsp_request_count.clone();
2253                    move |fake_server| {
2254                        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
2255                        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2256                        fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
2257                            move |params, _| {
2258                                let task_lsp_request_ranges =
2259                                    Arc::clone(&closure_lsp_request_ranges);
2260                                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2261                                async move {
2262                                    assert_eq!(
2263                                        params.text_document.uri,
2264                                        lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
2265                                    );
2266
2267                                    task_lsp_request_ranges.lock().push(params.range);
2268                                    task_lsp_request_count.fetch_add(1, Ordering::Release);
2269                                    Ok(Some(vec![lsp::InlayHint {
2270                                        position: params.range.end,
2271                                        label: lsp::InlayHintLabel::String(
2272                                            params.range.end.line.to_string(),
2273                                        ),
2274                                        kind: None,
2275                                        text_edits: None,
2276                                        tooltip: None,
2277                                        padding_left: None,
2278                                        padding_right: None,
2279                                        data: None,
2280                                    }]))
2281                                }
2282                            },
2283                        );
2284                    }
2285                })),
2286                ..Default::default()
2287            },
2288        );
2289
2290        let buffer = project
2291            .update(cx, |project, cx| {
2292                project.open_local_buffer(path!("/a/main.rs"), cx)
2293            })
2294            .await
2295            .unwrap();
2296        let editor =
2297            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
2298
2299        cx.executor().run_until_parked();
2300
2301        let _fake_server = fake_servers.next().await.unwrap();
2302
2303        // in large buffers, requests are made for more than visible range of a buffer.
2304        // invisible parts are queried later, to avoid excessive requests on quick typing.
2305        // wait the timeout needed to get all requests.
2306        cx.executor().advance_clock(Duration::from_millis(
2307            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2308        ));
2309        cx.executor().run_until_parked();
2310        let initial_visible_range = editor_visible_range(&editor, cx);
2311        let lsp_initial_visible_range = lsp::Range::new(
2312            lsp::Position::new(
2313                initial_visible_range.start.row,
2314                initial_visible_range.start.column,
2315            ),
2316            lsp::Position::new(
2317                initial_visible_range.end.row,
2318                initial_visible_range.end.column,
2319            ),
2320        );
2321        let expected_initial_query_range_end =
2322            lsp::Position::new(initial_visible_range.end.row * 2, 2);
2323        let mut expected_invisible_query_start = lsp_initial_visible_range.end;
2324        expected_invisible_query_start.character += 1;
2325        editor.update(cx, |editor, _window, cx| {
2326            let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2327            assert_eq!(ranges.len(), 2,
2328                "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:?}");
2329            let visible_query_range = &ranges[0];
2330            assert_eq!(visible_query_range.start, lsp_initial_visible_range.start);
2331            assert_eq!(visible_query_range.end, lsp_initial_visible_range.end);
2332            let invisible_query_range = &ranges[1];
2333
2334            assert_eq!(invisible_query_range.start, expected_invisible_query_start, "Should initially query visible edge of the document");
2335            assert_eq!(invisible_query_range.end, expected_initial_query_range_end, "Should initially query visible edge of the document");
2336
2337            let requests_count = lsp_request_count.load(Ordering::Acquire);
2338            assert_eq!(requests_count, 2, "Visible + invisible request");
2339            let expected_hints = vec!["47".to_string(), "94".to_string()];
2340            assert_eq!(
2341                expected_hints,
2342                cached_hint_labels(editor),
2343                "Should have hints from both LSP requests made for a big file"
2344            );
2345            assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
2346        }).unwrap();
2347
2348        editor
2349            .update(cx, |editor, window, cx| {
2350                editor.scroll_screen(&ScrollAmount::Page(1.0), window, cx);
2351            })
2352            .unwrap();
2353        cx.executor().run_until_parked();
2354        editor
2355            .update(cx, |editor, window, cx| {
2356                editor.scroll_screen(&ScrollAmount::Page(1.0), window, cx);
2357            })
2358            .unwrap();
2359        cx.executor().advance_clock(Duration::from_millis(
2360            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2361        ));
2362        cx.executor().run_until_parked();
2363        let visible_range_after_scrolls = editor_visible_range(&editor, cx);
2364        let visible_line_count = editor
2365            .update(cx, |editor, _window, _| {
2366                editor.visible_line_count().unwrap()
2367            })
2368            .unwrap();
2369        let selection_in_cached_range = editor
2370            .update(cx, |editor, _window, cx| {
2371                let ranges = lsp_request_ranges
2372                    .lock()
2373                    .drain(..)
2374                    .sorted_by_key(|r| r.start)
2375                    .collect::<Vec<_>>();
2376                assert_eq!(
2377                    ranges.len(),
2378                    2,
2379                    "Should query 2 ranges after both scrolls, but got: {ranges:?}"
2380                );
2381                let first_scroll = &ranges[0];
2382                let second_scroll = &ranges[1];
2383                assert_eq!(
2384                    first_scroll.end, second_scroll.start,
2385                    "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
2386                );
2387                assert_eq!(
2388                first_scroll.start, expected_initial_query_range_end,
2389                "First scroll should start the query right after the end of the original scroll",
2390            );
2391                assert_eq!(
2392                second_scroll.end,
2393                lsp::Position::new(
2394                    visible_range_after_scrolls.end.row
2395                        + visible_line_count.ceil() as u32,
2396                    1,
2397                ),
2398                "Second scroll should query one more screen down after the end of the visible range"
2399            );
2400
2401                let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2402                assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
2403                let expected_hints = vec![
2404                    "47".to_string(),
2405                    "94".to_string(),
2406                    "139".to_string(),
2407                    "184".to_string(),
2408                ];
2409                assert_eq!(
2410                    expected_hints,
2411                    cached_hint_labels(editor),
2412                    "Should have hints from the new LSP response after the edit"
2413                );
2414                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2415
2416                let mut selection_in_cached_range = visible_range_after_scrolls.end;
2417                selection_in_cached_range.row -= visible_line_count.ceil() as u32;
2418                selection_in_cached_range
2419            })
2420            .unwrap();
2421
2422        editor
2423            .update(cx, |editor, window, cx| {
2424                editor.change_selections(Some(Autoscroll::center()), window, cx, |s| {
2425                    s.select_ranges([selection_in_cached_range..selection_in_cached_range])
2426                });
2427            })
2428            .unwrap();
2429        cx.executor().advance_clock(Duration::from_millis(
2430            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2431        ));
2432        cx.executor().run_until_parked();
2433        editor.update(cx, |_, _, _| {
2434            let ranges = lsp_request_ranges
2435                .lock()
2436                .drain(..)
2437                .sorted_by_key(|r| r.start)
2438                .collect::<Vec<_>>();
2439            assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
2440            assert_eq!(lsp_request_count.load(Ordering::Acquire), 4);
2441        }).unwrap();
2442
2443        editor
2444            .update(cx, |editor, window, cx| {
2445                editor.handle_input("++++more text++++", window, cx);
2446            })
2447            .unwrap();
2448        cx.executor().advance_clock(Duration::from_millis(
2449            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2450        ));
2451        cx.executor().run_until_parked();
2452        editor.update(cx, |editor, _window, cx| {
2453            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2454            ranges.sort_by_key(|r| r.start);
2455
2456            assert_eq!(ranges.len(), 3,
2457                "On edit, should scroll to selection and query a range around it: visible + same range above and below. Instead, got query ranges {ranges:?}");
2458            let above_query_range = &ranges[0];
2459            let visible_query_range = &ranges[1];
2460            let below_query_range = &ranges[2];
2461            assert!(above_query_range.end.character < visible_query_range.start.character || above_query_range.end.line + 1 == visible_query_range.start.line,
2462                "Above range {above_query_range:?} should be before visible range {visible_query_range:?}");
2463            assert!(visible_query_range.end.character < below_query_range.start.character || visible_query_range.end.line  + 1 == below_query_range.start.line,
2464                "Visible range {visible_query_range:?} should be before below range {below_query_range:?}");
2465            assert!(above_query_range.start.line < selection_in_cached_range.row,
2466                "Hints should be queried with the selected range after the query range start");
2467            assert!(below_query_range.end.line > selection_in_cached_range.row,
2468                "Hints should be queried with the selected range before the query range end");
2469            assert!(above_query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
2470                "Hints query range should contain one more screen before");
2471            assert!(below_query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
2472                "Hints query range should contain one more screen after");
2473
2474            let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2475            assert_eq!(lsp_requests, 7, "There should be a visible range and two ranges above and below it queried");
2476            let expected_hints = vec!["67".to_string(), "115".to_string(), "163".to_string()];
2477            assert_eq!(expected_hints, cached_hint_labels(editor),
2478                "Should have hints from the new LSP response after the edit");
2479            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2480        }).unwrap();
2481    }
2482
2483    fn editor_visible_range(
2484        editor: &WindowHandle<Editor>,
2485        cx: &mut gpui::TestAppContext,
2486    ) -> Range<Point> {
2487        let ranges = editor
2488            .update(cx, |editor, _window, cx| {
2489                editor.excerpts_for_inlay_hints_query(None, cx)
2490            })
2491            .unwrap();
2492        assert_eq!(
2493            ranges.len(),
2494            1,
2495            "Single buffer should produce a single excerpt with visible range"
2496        );
2497        let (_, (excerpt_buffer, _, excerpt_visible_range)) = ranges.into_iter().next().unwrap();
2498        excerpt_buffer.update(cx, |buffer, _| {
2499            let snapshot = buffer.snapshot();
2500            let start = buffer
2501                .anchor_before(excerpt_visible_range.start)
2502                .to_point(&snapshot);
2503            let end = buffer
2504                .anchor_after(excerpt_visible_range.end)
2505                .to_point(&snapshot);
2506            start..end
2507        })
2508    }
2509
2510    #[gpui::test]
2511    async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
2512        init_test(cx, |settings| {
2513            settings.defaults.inlay_hints = Some(InlayHintSettings {
2514                enabled: true,
2515                edit_debounce_ms: 0,
2516                scroll_debounce_ms: 0,
2517                show_type_hints: true,
2518                show_parameter_hints: true,
2519                show_other_hints: true,
2520                show_background: false,
2521                toggle_on_modifiers_press: None,
2522            })
2523        });
2524
2525        let fs = FakeFs::new(cx.background_executor.clone());
2526        fs.insert_tree(
2527                path!("/a"),
2528                json!({
2529                    "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2530                    "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2531                }),
2532            )
2533            .await;
2534
2535        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2536
2537        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2538        let language = rust_lang();
2539        language_registry.add(language);
2540        let mut fake_servers = language_registry.register_fake_lsp(
2541            "Rust",
2542            FakeLspAdapter {
2543                capabilities: lsp::ServerCapabilities {
2544                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2545                    ..Default::default()
2546                },
2547                ..Default::default()
2548            },
2549        );
2550
2551        let (buffer_1, _handle1) = project
2552            .update(cx, |project, cx| {
2553                project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
2554            })
2555            .await
2556            .unwrap();
2557        let (buffer_2, _handle2) = project
2558            .update(cx, |project, cx| {
2559                project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
2560            })
2561            .await
2562            .unwrap();
2563        let multibuffer = cx.new(|cx| {
2564            let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2565            multibuffer.push_excerpts(
2566                buffer_1.clone(),
2567                [
2568                    ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
2569                    ExcerptRange::new(Point::new(4, 0)..Point::new(11, 0)),
2570                    ExcerptRange::new(Point::new(22, 0)..Point::new(33, 0)),
2571                    ExcerptRange::new(Point::new(44, 0)..Point::new(55, 0)),
2572                    ExcerptRange::new(Point::new(56, 0)..Point::new(66, 0)),
2573                    ExcerptRange::new(Point::new(67, 0)..Point::new(77, 0)),
2574                ],
2575                cx,
2576            );
2577            multibuffer.push_excerpts(
2578                buffer_2.clone(),
2579                [
2580                    ExcerptRange::new(Point::new(0, 1)..Point::new(2, 1)),
2581                    ExcerptRange::new(Point::new(4, 1)..Point::new(11, 1)),
2582                    ExcerptRange::new(Point::new(22, 1)..Point::new(33, 1)),
2583                    ExcerptRange::new(Point::new(44, 1)..Point::new(55, 1)),
2584                    ExcerptRange::new(Point::new(56, 1)..Point::new(66, 1)),
2585                    ExcerptRange::new(Point::new(67, 1)..Point::new(77, 1)),
2586                ],
2587                cx,
2588            );
2589            multibuffer
2590        });
2591
2592        cx.executor().run_until_parked();
2593        let editor = cx.add_window(|window, cx| {
2594            Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
2595        });
2596
2597        let editor_edited = Arc::new(AtomicBool::new(false));
2598        let fake_server = fake_servers.next().await.unwrap();
2599        let closure_editor_edited = Arc::clone(&editor_edited);
2600        fake_server
2601            .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2602                let task_editor_edited = Arc::clone(&closure_editor_edited);
2603                async move {
2604                    let hint_text = if params.text_document.uri
2605                        == lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
2606                    {
2607                        "main hint"
2608                    } else if params.text_document.uri
2609                        == lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
2610                    {
2611                        "other hint"
2612                    } else {
2613                        panic!("unexpected uri: {:?}", params.text_document.uri);
2614                    };
2615
2616                    // one hint per excerpt
2617                    let positions = [
2618                        lsp::Position::new(0, 2),
2619                        lsp::Position::new(4, 2),
2620                        lsp::Position::new(22, 2),
2621                        lsp::Position::new(44, 2),
2622                        lsp::Position::new(56, 2),
2623                        lsp::Position::new(67, 2),
2624                    ];
2625                    let out_of_range_hint = lsp::InlayHint {
2626                        position: lsp::Position::new(
2627                            params.range.start.line + 99,
2628                            params.range.start.character + 99,
2629                        ),
2630                        label: lsp::InlayHintLabel::String(
2631                            "out of excerpt range, should be ignored".to_string(),
2632                        ),
2633                        kind: None,
2634                        text_edits: None,
2635                        tooltip: None,
2636                        padding_left: None,
2637                        padding_right: None,
2638                        data: None,
2639                    };
2640
2641                    let edited = task_editor_edited.load(Ordering::Acquire);
2642                    Ok(Some(
2643                        std::iter::once(out_of_range_hint)
2644                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2645                                lsp::InlayHint {
2646                                    position,
2647                                    label: lsp::InlayHintLabel::String(format!(
2648                                        "{hint_text}{E} #{i}",
2649                                        E = if edited { "(edited)" } else { "" },
2650                                    )),
2651                                    kind: None,
2652                                    text_edits: None,
2653                                    tooltip: None,
2654                                    padding_left: None,
2655                                    padding_right: None,
2656                                    data: None,
2657                                }
2658                            }))
2659                            .collect(),
2660                    ))
2661                }
2662            })
2663            .next()
2664            .await;
2665        cx.executor().run_until_parked();
2666
2667        editor
2668            .update(cx, |editor, _window, cx| {
2669                let expected_hints = vec![
2670                    "main hint #0".to_string(),
2671                    "main hint #1".to_string(),
2672                    "main hint #2".to_string(),
2673                    "main hint #3".to_string(),
2674                    "main hint #4".to_string(),
2675                    "main hint #5".to_string(),
2676                ];
2677                assert_eq!(
2678                    expected_hints,
2679                    sorted_cached_hint_labels(editor),
2680                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2681                );
2682                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2683            })
2684            .unwrap();
2685
2686        editor
2687            .update(cx, |editor, window, cx| {
2688                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2689                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2690                });
2691                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2692                    s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2693                });
2694                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2695                    s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2696                });
2697            })
2698            .unwrap();
2699        cx.executor().run_until_parked();
2700        editor
2701            .update(cx, |editor, _window, cx| {
2702                let expected_hints = vec![
2703                    "main hint #0".to_string(),
2704                    "main hint #1".to_string(),
2705                    "main hint #2".to_string(),
2706                    "main hint #3".to_string(),
2707                    "main hint #4".to_string(),
2708                    "main hint #5".to_string(),
2709                    "other hint #0".to_string(),
2710                    "other hint #1".to_string(),
2711                    "other hint #2".to_string(),
2712                ];
2713                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2714                    "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2715                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2716            })
2717            .unwrap();
2718
2719        editor
2720            .update(cx, |editor, window, cx| {
2721                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2722                    s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2723                });
2724            })
2725            .unwrap();
2726        cx.executor().advance_clock(Duration::from_millis(
2727            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2728        ));
2729        cx.executor().run_until_parked();
2730        editor
2731            .update(cx, |editor, _window, cx| {
2732                let expected_hints = vec![
2733                    "main hint #0".to_string(),
2734                    "main hint #1".to_string(),
2735                    "main hint #2".to_string(),
2736                    "main hint #3".to_string(),
2737                    "main hint #4".to_string(),
2738                    "main hint #5".to_string(),
2739                    "other hint #0".to_string(),
2740                    "other hint #1".to_string(),
2741                    "other hint #2".to_string(),
2742                    "other hint #3".to_string(),
2743                    "other hint #4".to_string(),
2744                    "other hint #5".to_string(),
2745                ];
2746                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2747                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2748                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2749            })
2750            .unwrap();
2751
2752        editor
2753            .update(cx, |editor, window, cx| {
2754                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2755                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2756                });
2757            })
2758            .unwrap();
2759        cx.executor().advance_clock(Duration::from_millis(
2760            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2761        ));
2762        cx.executor().run_until_parked();
2763        editor
2764            .update(cx, |editor, _window, cx| {
2765                let expected_hints = vec![
2766                    "main hint #0".to_string(),
2767                    "main hint #1".to_string(),
2768                    "main hint #2".to_string(),
2769                    "main hint #3".to_string(),
2770                    "main hint #4".to_string(),
2771                    "main hint #5".to_string(),
2772                    "other hint #0".to_string(),
2773                    "other hint #1".to_string(),
2774                    "other hint #2".to_string(),
2775                    "other hint #3".to_string(),
2776                    "other hint #4".to_string(),
2777                    "other hint #5".to_string(),
2778                ];
2779                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2780                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2781                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2782            })
2783            .unwrap();
2784
2785        editor_edited.store(true, Ordering::Release);
2786        editor
2787            .update(cx, |editor, window, cx| {
2788                editor.change_selections(None, window, cx, |s| {
2789                    s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
2790                });
2791                editor.handle_input("++++more text++++", window, cx);
2792            })
2793            .unwrap();
2794        cx.executor().run_until_parked();
2795        editor
2796            .update(cx, |editor, _window, cx| {
2797                let expected_hints = vec![
2798                    "main hint #0".to_string(),
2799                    "main hint #1".to_string(),
2800                    "main hint #2".to_string(),
2801                    "main hint #3".to_string(),
2802                    "main hint #4".to_string(),
2803                    "main hint #5".to_string(),
2804                    "other hint(edited) #0".to_string(),
2805                    "other hint(edited) #1".to_string(),
2806                ];
2807                assert_eq!(
2808                    expected_hints,
2809                    sorted_cached_hint_labels(editor),
2810                    "After multibuffer edit, editor gets scrolled back to the last selection; \
2811                all hints should be invalidated and required for all of its visible excerpts"
2812                );
2813                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2814            })
2815            .unwrap();
2816    }
2817
2818    #[gpui::test]
2819    async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
2820        init_test(cx, |settings| {
2821            settings.defaults.inlay_hints = Some(InlayHintSettings {
2822                enabled: true,
2823                edit_debounce_ms: 0,
2824                scroll_debounce_ms: 0,
2825                show_type_hints: false,
2826                show_parameter_hints: false,
2827                show_other_hints: false,
2828                show_background: false,
2829                toggle_on_modifiers_press: None,
2830            })
2831        });
2832
2833        let fs = FakeFs::new(cx.background_executor.clone());
2834        fs.insert_tree(
2835            path!("/a"),
2836            json!({
2837                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2838                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2839            }),
2840        )
2841        .await;
2842
2843        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2844
2845        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2846        language_registry.add(rust_lang());
2847        let mut fake_servers = language_registry.register_fake_lsp(
2848            "Rust",
2849            FakeLspAdapter {
2850                capabilities: lsp::ServerCapabilities {
2851                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2852                    ..Default::default()
2853                },
2854                ..Default::default()
2855            },
2856        );
2857
2858        let (buffer_1, _handle) = project
2859            .update(cx, |project, cx| {
2860                project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
2861            })
2862            .await
2863            .unwrap();
2864        let (buffer_2, _handle2) = project
2865            .update(cx, |project, cx| {
2866                project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
2867            })
2868            .await
2869            .unwrap();
2870        let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2871        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2872            let buffer_1_excerpts = multibuffer.push_excerpts(
2873                buffer_1.clone(),
2874                [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
2875                cx,
2876            );
2877            let buffer_2_excerpts = multibuffer.push_excerpts(
2878                buffer_2.clone(),
2879                [ExcerptRange::new(Point::new(0, 1)..Point::new(2, 1))],
2880                cx,
2881            );
2882            (buffer_1_excerpts, buffer_2_excerpts)
2883        });
2884
2885        assert!(!buffer_1_excerpts.is_empty());
2886        assert!(!buffer_2_excerpts.is_empty());
2887
2888        cx.executor().run_until_parked();
2889        let editor = cx.add_window(|window, cx| {
2890            Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
2891        });
2892        let editor_edited = Arc::new(AtomicBool::new(false));
2893        let fake_server = fake_servers.next().await.unwrap();
2894        let closure_editor_edited = Arc::clone(&editor_edited);
2895        fake_server
2896            .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2897                let task_editor_edited = Arc::clone(&closure_editor_edited);
2898                async move {
2899                    let hint_text = if params.text_document.uri
2900                        == lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
2901                    {
2902                        "main hint"
2903                    } else if params.text_document.uri
2904                        == lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
2905                    {
2906                        "other hint"
2907                    } else {
2908                        panic!("unexpected uri: {:?}", params.text_document.uri);
2909                    };
2910
2911                    let positions = [
2912                        lsp::Position::new(0, 2),
2913                        lsp::Position::new(4, 2),
2914                        lsp::Position::new(22, 2),
2915                        lsp::Position::new(44, 2),
2916                        lsp::Position::new(56, 2),
2917                        lsp::Position::new(67, 2),
2918                    ];
2919                    let out_of_range_hint = lsp::InlayHint {
2920                        position: lsp::Position::new(
2921                            params.range.start.line + 99,
2922                            params.range.start.character + 99,
2923                        ),
2924                        label: lsp::InlayHintLabel::String(
2925                            "out of excerpt range, should be ignored".to_string(),
2926                        ),
2927                        kind: None,
2928                        text_edits: None,
2929                        tooltip: None,
2930                        padding_left: None,
2931                        padding_right: None,
2932                        data: None,
2933                    };
2934
2935                    let edited = task_editor_edited.load(Ordering::Acquire);
2936                    Ok(Some(
2937                        std::iter::once(out_of_range_hint)
2938                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2939                                lsp::InlayHint {
2940                                    position,
2941                                    label: lsp::InlayHintLabel::String(format!(
2942                                        "{hint_text}{} #{i}",
2943                                        if edited { "(edited)" } else { "" },
2944                                    )),
2945                                    kind: None,
2946                                    text_edits: None,
2947                                    tooltip: None,
2948                                    padding_left: None,
2949                                    padding_right: None,
2950                                    data: None,
2951                                }
2952                            }))
2953                            .collect(),
2954                    ))
2955                }
2956            })
2957            .next()
2958            .await;
2959        cx.executor().run_until_parked();
2960        editor
2961            .update(cx, |editor, _, cx| {
2962                assert_eq!(
2963                    vec!["main hint #0".to_string(), "other hint #0".to_string()],
2964                    sorted_cached_hint_labels(editor),
2965                    "Cache should update for both excerpts despite hints display was disabled"
2966                );
2967                assert!(
2968                visible_hint_labels(editor, cx).is_empty(),
2969                "All hints are disabled and should not be shown despite being present in the cache"
2970            );
2971            })
2972            .unwrap();
2973
2974        editor
2975            .update(cx, |editor, _, cx| {
2976                editor.buffer().update(cx, |multibuffer, cx| {
2977                    multibuffer.remove_excerpts(buffer_2_excerpts, cx)
2978                })
2979            })
2980            .unwrap();
2981        cx.executor().run_until_parked();
2982        editor
2983            .update(cx, |editor, _, cx| {
2984                assert_eq!(
2985                    vec!["main hint #0".to_string()],
2986                    cached_hint_labels(editor),
2987                    "For the removed excerpt, should clean corresponding cached hints"
2988                );
2989                assert!(
2990                visible_hint_labels(editor, cx).is_empty(),
2991                "All hints are disabled and should not be shown despite being present in the cache"
2992            );
2993            })
2994            .unwrap();
2995
2996        update_test_language_settings(cx, |settings| {
2997            settings.defaults.inlay_hints = Some(InlayHintSettings {
2998                enabled: true,
2999                edit_debounce_ms: 0,
3000                scroll_debounce_ms: 0,
3001                show_type_hints: true,
3002                show_parameter_hints: true,
3003                show_other_hints: true,
3004                show_background: false,
3005                toggle_on_modifiers_press: None,
3006            })
3007        });
3008        cx.executor().run_until_parked();
3009        editor
3010            .update(cx, |editor, _, cx| {
3011                let expected_hints = vec!["main hint #0".to_string()];
3012                assert_eq!(
3013                    expected_hints,
3014                    cached_hint_labels(editor),
3015                    "Hint display settings change should not change the cache"
3016                );
3017                assert_eq!(
3018                    expected_hints,
3019                    visible_hint_labels(editor, cx),
3020                    "Settings change should make cached hints visible"
3021                );
3022            })
3023            .unwrap();
3024    }
3025
3026    #[gpui::test]
3027    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
3028        init_test(cx, |settings| {
3029            settings.defaults.inlay_hints = Some(InlayHintSettings {
3030                enabled: true,
3031                edit_debounce_ms: 0,
3032                scroll_debounce_ms: 0,
3033                show_type_hints: true,
3034                show_parameter_hints: true,
3035                show_other_hints: true,
3036                show_background: false,
3037                toggle_on_modifiers_press: None,
3038            })
3039        });
3040
3041        let fs = FakeFs::new(cx.background_executor.clone());
3042        fs.insert_tree(
3043            path!("/a"),
3044            json!({
3045                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
3046                "other.rs": "// Test file",
3047            }),
3048        )
3049        .await;
3050
3051        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3052
3053        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3054        language_registry.add(rust_lang());
3055        language_registry.register_fake_lsp(
3056            "Rust",
3057            FakeLspAdapter {
3058                capabilities: lsp::ServerCapabilities {
3059                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3060                    ..Default::default()
3061                },
3062                initializer: Some(Box::new(move |fake_server| {
3063                    let lsp_request_count = Arc::new(AtomicU32::new(0));
3064                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3065                        move |params, _| {
3066                            let i = lsp_request_count.fetch_add(1, Ordering::Release) + 1;
3067                            async move {
3068                                assert_eq!(
3069                                    params.text_document.uri,
3070                                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3071                                );
3072                                let query_start = params.range.start;
3073                                Ok(Some(vec![lsp::InlayHint {
3074                                    position: query_start,
3075                                    label: lsp::InlayHintLabel::String(i.to_string()),
3076                                    kind: None,
3077                                    text_edits: None,
3078                                    tooltip: None,
3079                                    padding_left: None,
3080                                    padding_right: None,
3081                                    data: None,
3082                                }]))
3083                            }
3084                        },
3085                    );
3086                })),
3087                ..Default::default()
3088            },
3089        );
3090
3091        let buffer = project
3092            .update(cx, |project, cx| {
3093                project.open_local_buffer(path!("/a/main.rs"), cx)
3094            })
3095            .await
3096            .unwrap();
3097        let editor =
3098            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3099
3100        cx.executor().run_until_parked();
3101        editor
3102            .update(cx, |editor, window, cx| {
3103                editor.change_selections(None, window, cx, |s| {
3104                    s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3105                })
3106            })
3107            .unwrap();
3108        cx.executor().run_until_parked();
3109        editor
3110            .update(cx, |editor, _, cx| {
3111                let expected_hints = vec!["1".to_string()];
3112                assert_eq!(expected_hints, cached_hint_labels(editor));
3113                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3114            })
3115            .unwrap();
3116    }
3117
3118    #[gpui::test]
3119    async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) {
3120        init_test(cx, |settings| {
3121            settings.defaults.inlay_hints = Some(InlayHintSettings {
3122                enabled: false,
3123                edit_debounce_ms: 0,
3124                scroll_debounce_ms: 0,
3125                show_type_hints: true,
3126                show_parameter_hints: true,
3127                show_other_hints: true,
3128                show_background: false,
3129                toggle_on_modifiers_press: None,
3130            })
3131        });
3132
3133        let (_, editor, _fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
3134            let lsp_request_count = Arc::new(AtomicU32::new(0));
3135            fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3136                move |params, _| {
3137                    let lsp_request_count = lsp_request_count.clone();
3138                    async move {
3139                        assert_eq!(
3140                            params.text_document.uri,
3141                            lsp::Url::from_file_path(file_with_hints).unwrap(),
3142                        );
3143
3144                        let i = lsp_request_count.fetch_add(1, Ordering::SeqCst) + 1;
3145                        Ok(Some(vec![lsp::InlayHint {
3146                            position: lsp::Position::new(0, i),
3147                            label: lsp::InlayHintLabel::String(i.to_string()),
3148                            kind: None,
3149                            text_edits: None,
3150                            tooltip: None,
3151                            padding_left: None,
3152                            padding_right: None,
3153                            data: None,
3154                        }]))
3155                    }
3156                },
3157            );
3158        })
3159        .await;
3160
3161        editor
3162            .update(cx, |editor, window, cx| {
3163                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3164            })
3165            .unwrap();
3166
3167        cx.executor().run_until_parked();
3168        editor
3169            .update(cx, |editor, _, cx| {
3170                let expected_hints = vec!["1".to_string()];
3171                assert_eq!(
3172                    expected_hints,
3173                    cached_hint_labels(editor),
3174                    "Should display inlays after toggle despite them disabled in settings"
3175                );
3176                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3177            })
3178            .unwrap();
3179
3180        editor
3181            .update(cx, |editor, window, cx| {
3182                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3183            })
3184            .unwrap();
3185        cx.executor().run_until_parked();
3186        editor
3187            .update(cx, |editor, _, cx| {
3188                assert!(
3189                    cached_hint_labels(editor).is_empty(),
3190                    "Should clear hints after 2nd toggle"
3191                );
3192                assert!(visible_hint_labels(editor, cx).is_empty());
3193            })
3194            .unwrap();
3195
3196        update_test_language_settings(cx, |settings| {
3197            settings.defaults.inlay_hints = Some(InlayHintSettings {
3198                enabled: true,
3199                edit_debounce_ms: 0,
3200                scroll_debounce_ms: 0,
3201                show_type_hints: true,
3202                show_parameter_hints: true,
3203                show_other_hints: true,
3204                show_background: false,
3205                toggle_on_modifiers_press: None,
3206            })
3207        });
3208        cx.executor().run_until_parked();
3209        editor
3210            .update(cx, |editor, _, cx| {
3211                let expected_hints = vec!["2".to_string()];
3212                assert_eq!(
3213                    expected_hints,
3214                    cached_hint_labels(editor),
3215                    "Should query LSP hints for the 2nd time after enabling hints in settings"
3216                );
3217                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3218            })
3219            .unwrap();
3220
3221        editor
3222            .update(cx, |editor, window, cx| {
3223                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3224            })
3225            .unwrap();
3226        cx.executor().run_until_parked();
3227        editor
3228            .update(cx, |editor, _, cx| {
3229                assert!(
3230                    cached_hint_labels(editor).is_empty(),
3231                    "Should clear hints after enabling in settings and a 3rd toggle"
3232                );
3233                assert!(visible_hint_labels(editor, cx).is_empty());
3234            })
3235            .unwrap();
3236
3237        editor
3238            .update(cx, |editor, window, cx| {
3239                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3240            })
3241            .unwrap();
3242        cx.executor().run_until_parked();
3243        editor.update(cx, |editor, _, cx| {
3244            let expected_hints = vec!["3".to_string()];
3245            assert_eq!(
3246                expected_hints,
3247                cached_hint_labels(editor),
3248                "Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
3249            );
3250            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3251        }).unwrap();
3252    }
3253
3254    #[gpui::test]
3255    async fn test_inlays_at_the_same_place(cx: &mut gpui::TestAppContext) {
3256        init_test(cx, |settings| {
3257            settings.defaults.inlay_hints = Some(InlayHintSettings {
3258                enabled: true,
3259                edit_debounce_ms: 0,
3260                scroll_debounce_ms: 0,
3261                show_type_hints: true,
3262                show_parameter_hints: true,
3263                show_other_hints: true,
3264                show_background: false,
3265                toggle_on_modifiers_press: None,
3266            })
3267        });
3268
3269        let fs = FakeFs::new(cx.background_executor.clone());
3270        fs.insert_tree(
3271            path!("/a"),
3272            json!({
3273                "main.rs": "fn main() {
3274                    let x = 42;
3275                    std::thread::scope(|s| {
3276                        s.spawn(|| {
3277                            let _x = x;
3278                        });
3279                    });
3280                }",
3281                "other.rs": "// Test file",
3282            }),
3283        )
3284        .await;
3285
3286        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3287
3288        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3289        language_registry.add(rust_lang());
3290        language_registry.register_fake_lsp(
3291            "Rust",
3292            FakeLspAdapter {
3293                capabilities: lsp::ServerCapabilities {
3294                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3295                    ..Default::default()
3296                },
3297                initializer: Some(Box::new(move |fake_server| {
3298                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3299                        move |params, _| async move {
3300                            assert_eq!(
3301                                params.text_document.uri,
3302                                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3303                            );
3304                            Ok(Some(
3305                                serde_json::from_value(json!([
3306                                    {
3307                                        "position": {
3308                                            "line": 3,
3309                                            "character": 16
3310                                        },
3311                                        "label": "move",
3312                                        "paddingLeft": false,
3313                                        "paddingRight": false
3314                                    },
3315                                    {
3316                                        "position": {
3317                                            "line": 3,
3318                                            "character": 16
3319                                        },
3320                                        "label": "(",
3321                                        "paddingLeft": false,
3322                                        "paddingRight": false
3323                                    },
3324                                    {
3325                                        "position": {
3326                                            "line": 3,
3327                                            "character": 16
3328                                        },
3329                                        "label": [
3330                                            {
3331                                                "value": "&x"
3332                                            }
3333                                        ],
3334                                        "paddingLeft": false,
3335                                        "paddingRight": false,
3336                                        "data": {
3337                                            "file_id": 0
3338                                        }
3339                                    },
3340                                    {
3341                                        "position": {
3342                                            "line": 3,
3343                                            "character": 16
3344                                        },
3345                                        "label": ")",
3346                                        "paddingLeft": false,
3347                                        "paddingRight": true
3348                                    },
3349                                    // not a correct syntax, but checks that same symbols at the same place
3350                                    // are not deduplicated
3351                                    {
3352                                        "position": {
3353                                            "line": 3,
3354                                            "character": 16
3355                                        },
3356                                        "label": ")",
3357                                        "paddingLeft": false,
3358                                        "paddingRight": true
3359                                    },
3360                                ]))
3361                                .unwrap(),
3362                            ))
3363                        },
3364                    );
3365                })),
3366                ..FakeLspAdapter::default()
3367            },
3368        );
3369
3370        let buffer = project
3371            .update(cx, |project, cx| {
3372                project.open_local_buffer(path!("/a/main.rs"), cx)
3373            })
3374            .await
3375            .unwrap();
3376        let editor =
3377            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3378
3379        cx.executor().run_until_parked();
3380        editor
3381            .update(cx, |editor, window, cx| {
3382                editor.change_selections(None, window, cx, |s| {
3383                    s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3384                })
3385            })
3386            .unwrap();
3387        cx.executor().run_until_parked();
3388        editor
3389            .update(cx, |editor, _window, cx| {
3390                let expected_hints = vec![
3391                    "move".to_string(),
3392                    "(".to_string(),
3393                    "&x".to_string(),
3394                    ") ".to_string(),
3395                    ") ".to_string(),
3396                ];
3397                assert_eq!(
3398                    expected_hints,
3399                    cached_hint_labels(editor),
3400                    "Editor inlay hints should repeat server's order when placed at the same spot"
3401                );
3402                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3403            })
3404            .unwrap();
3405    }
3406
3407    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
3408        cx.update(|cx| {
3409            let settings_store = SettingsStore::test(cx);
3410            cx.set_global(settings_store);
3411            theme::init(theme::LoadThemes::JustBase, cx);
3412            release_channel::init(SemanticVersion::default(), cx);
3413            client::init_settings(cx);
3414            language::init(cx);
3415            Project::init_settings(cx);
3416            workspace::init_settings(cx);
3417            crate::init(cx);
3418        });
3419
3420        update_test_language_settings(cx, f);
3421    }
3422
3423    async fn prepare_test_objects(
3424        cx: &mut TestAppContext,
3425        initialize: impl 'static + Send + Fn(&mut FakeLanguageServer, &'static str) + Send + Sync,
3426    ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
3427        let fs = FakeFs::new(cx.background_executor.clone());
3428        fs.insert_tree(
3429            path!("/a"),
3430            json!({
3431                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
3432                "other.rs": "// Test file",
3433            }),
3434        )
3435        .await;
3436
3437        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3438        let file_path = path!("/a/main.rs");
3439
3440        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3441        language_registry.add(rust_lang());
3442        let mut fake_servers = language_registry.register_fake_lsp(
3443            "Rust",
3444            FakeLspAdapter {
3445                capabilities: lsp::ServerCapabilities {
3446                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3447                    ..Default::default()
3448                },
3449                initializer: Some(Box::new(move |server| initialize(server, file_path))),
3450                ..Default::default()
3451            },
3452        );
3453
3454        let buffer = project
3455            .update(cx, |project, cx| {
3456                project.open_local_buffer(path!("/a/main.rs"), cx)
3457            })
3458            .await
3459            .unwrap();
3460        let editor =
3461            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3462
3463        editor
3464            .update(cx, |editor, _, cx| {
3465                assert!(cached_hint_labels(editor).is_empty());
3466                assert!(visible_hint_labels(editor, cx).is_empty());
3467            })
3468            .unwrap();
3469
3470        cx.executor().run_until_parked();
3471        let fake_server = fake_servers.next().await.unwrap();
3472        (file_path, editor, fake_server)
3473    }
3474
3475    // 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.
3476    // Ensure a stable order for testing.
3477    fn sorted_cached_hint_labels(editor: &Editor) -> Vec<String> {
3478        let mut labels = cached_hint_labels(editor);
3479        labels.sort();
3480        labels
3481    }
3482
3483    pub fn cached_hint_labels(editor: &Editor) -> Vec<String> {
3484        let mut labels = Vec::new();
3485        for excerpt_hints in editor.inlay_hint_cache().hints.values() {
3486            let excerpt_hints = excerpt_hints.read();
3487            for id in &excerpt_hints.ordered_hints {
3488                let hint = &excerpt_hints.hints_by_id[id];
3489                let mut label = hint.text();
3490                if hint.padding_left {
3491                    label.insert(0, ' ');
3492                }
3493                if hint.padding_right {
3494                    label.push_str(" ");
3495                }
3496                labels.push(label);
3497            }
3498        }
3499
3500        labels
3501    }
3502
3503    pub fn visible_hint_labels(editor: &Editor, cx: &Context<Editor>) -> Vec<String> {
3504        editor
3505            .visible_inlay_hints(cx)
3506            .into_iter()
3507            .map(|hint| hint.text.to_string())
3508            .collect()
3509    }
3510}