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 {
2569                        context: Point::new(0, 0)..Point::new(2, 0),
2570                        primary: None,
2571                    },
2572                    ExcerptRange {
2573                        context: Point::new(4, 0)..Point::new(11, 0),
2574                        primary: None,
2575                    },
2576                    ExcerptRange {
2577                        context: Point::new(22, 0)..Point::new(33, 0),
2578                        primary: None,
2579                    },
2580                    ExcerptRange {
2581                        context: Point::new(44, 0)..Point::new(55, 0),
2582                        primary: None,
2583                    },
2584                    ExcerptRange {
2585                        context: Point::new(56, 0)..Point::new(66, 0),
2586                        primary: None,
2587                    },
2588                    ExcerptRange {
2589                        context: Point::new(67, 0)..Point::new(77, 0),
2590                        primary: None,
2591                    },
2592                ],
2593                cx,
2594            );
2595            multibuffer.push_excerpts(
2596                buffer_2.clone(),
2597                [
2598                    ExcerptRange {
2599                        context: Point::new(0, 1)..Point::new(2, 1),
2600                        primary: None,
2601                    },
2602                    ExcerptRange {
2603                        context: Point::new(4, 1)..Point::new(11, 1),
2604                        primary: None,
2605                    },
2606                    ExcerptRange {
2607                        context: Point::new(22, 1)..Point::new(33, 1),
2608                        primary: None,
2609                    },
2610                    ExcerptRange {
2611                        context: Point::new(44, 1)..Point::new(55, 1),
2612                        primary: None,
2613                    },
2614                    ExcerptRange {
2615                        context: Point::new(56, 1)..Point::new(66, 1),
2616                        primary: None,
2617                    },
2618                    ExcerptRange {
2619                        context: Point::new(67, 1)..Point::new(77, 1),
2620                        primary: None,
2621                    },
2622                ],
2623                cx,
2624            );
2625            multibuffer
2626        });
2627
2628        cx.executor().run_until_parked();
2629        let editor = cx.add_window(|window, cx| {
2630            Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
2631        });
2632
2633        let editor_edited = Arc::new(AtomicBool::new(false));
2634        let fake_server = fake_servers.next().await.unwrap();
2635        let closure_editor_edited = Arc::clone(&editor_edited);
2636        fake_server
2637            .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2638                let task_editor_edited = Arc::clone(&closure_editor_edited);
2639                async move {
2640                    let hint_text = if params.text_document.uri
2641                        == lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
2642                    {
2643                        "main hint"
2644                    } else if params.text_document.uri
2645                        == lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
2646                    {
2647                        "other hint"
2648                    } else {
2649                        panic!("unexpected uri: {:?}", params.text_document.uri);
2650                    };
2651
2652                    // one hint per excerpt
2653                    let positions = [
2654                        lsp::Position::new(0, 2),
2655                        lsp::Position::new(4, 2),
2656                        lsp::Position::new(22, 2),
2657                        lsp::Position::new(44, 2),
2658                        lsp::Position::new(56, 2),
2659                        lsp::Position::new(67, 2),
2660                    ];
2661                    let out_of_range_hint = lsp::InlayHint {
2662                        position: lsp::Position::new(
2663                            params.range.start.line + 99,
2664                            params.range.start.character + 99,
2665                        ),
2666                        label: lsp::InlayHintLabel::String(
2667                            "out of excerpt range, should be ignored".to_string(),
2668                        ),
2669                        kind: None,
2670                        text_edits: None,
2671                        tooltip: None,
2672                        padding_left: None,
2673                        padding_right: None,
2674                        data: None,
2675                    };
2676
2677                    let edited = task_editor_edited.load(Ordering::Acquire);
2678                    Ok(Some(
2679                        std::iter::once(out_of_range_hint)
2680                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2681                                lsp::InlayHint {
2682                                    position,
2683                                    label: lsp::InlayHintLabel::String(format!(
2684                                        "{hint_text}{E} #{i}",
2685                                        E = if edited { "(edited)" } else { "" },
2686                                    )),
2687                                    kind: None,
2688                                    text_edits: None,
2689                                    tooltip: None,
2690                                    padding_left: None,
2691                                    padding_right: None,
2692                                    data: None,
2693                                }
2694                            }))
2695                            .collect(),
2696                    ))
2697                }
2698            })
2699            .next()
2700            .await;
2701        cx.executor().run_until_parked();
2702
2703        editor
2704            .update(cx, |editor, _window, cx| {
2705                let expected_hints = vec![
2706                    "main hint #0".to_string(),
2707                    "main hint #1".to_string(),
2708                    "main hint #2".to_string(),
2709                    "main hint #3".to_string(),
2710                    "main hint #4".to_string(),
2711                    "main hint #5".to_string(),
2712                ];
2713                assert_eq!(
2714                    expected_hints,
2715                    sorted_cached_hint_labels(editor),
2716                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2717                );
2718                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2719            })
2720            .unwrap();
2721
2722        editor
2723            .update(cx, |editor, window, cx| {
2724                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2725                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2726                });
2727                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2728                    s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2729                });
2730                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2731                    s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2732                });
2733            })
2734            .unwrap();
2735        cx.executor().run_until_parked();
2736        editor
2737            .update(cx, |editor, _window, cx| {
2738                let expected_hints = vec![
2739                    "main hint #0".to_string(),
2740                    "main hint #1".to_string(),
2741                    "main hint #2".to_string(),
2742                    "main hint #3".to_string(),
2743                    "main hint #4".to_string(),
2744                    "main hint #5".to_string(),
2745                    "other hint #0".to_string(),
2746                    "other hint #1".to_string(),
2747                    "other hint #2".to_string(),
2748                ];
2749                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2750                    "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2751                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2752            })
2753            .unwrap();
2754
2755        editor
2756            .update(cx, |editor, window, cx| {
2757                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2758                    s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2759                });
2760            })
2761            .unwrap();
2762        cx.executor().advance_clock(Duration::from_millis(
2763            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2764        ));
2765        cx.executor().run_until_parked();
2766        editor
2767            .update(cx, |editor, _window, cx| {
2768                let expected_hints = vec![
2769                    "main hint #0".to_string(),
2770                    "main hint #1".to_string(),
2771                    "main hint #2".to_string(),
2772                    "main hint #3".to_string(),
2773                    "main hint #4".to_string(),
2774                    "main hint #5".to_string(),
2775                    "other hint #0".to_string(),
2776                    "other hint #1".to_string(),
2777                    "other hint #2".to_string(),
2778                    "other hint #3".to_string(),
2779                    "other hint #4".to_string(),
2780                    "other hint #5".to_string(),
2781                ];
2782                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2783                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2784                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2785            })
2786            .unwrap();
2787
2788        editor
2789            .update(cx, |editor, window, cx| {
2790                editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
2791                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2792                });
2793            })
2794            .unwrap();
2795        cx.executor().advance_clock(Duration::from_millis(
2796            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2797        ));
2798        cx.executor().run_until_parked();
2799        editor
2800            .update(cx, |editor, _window, cx| {
2801                let expected_hints = vec![
2802                    "main hint #0".to_string(),
2803                    "main hint #1".to_string(),
2804                    "main hint #2".to_string(),
2805                    "main hint #3".to_string(),
2806                    "main hint #4".to_string(),
2807                    "main hint #5".to_string(),
2808                    "other hint #0".to_string(),
2809                    "other hint #1".to_string(),
2810                    "other hint #2".to_string(),
2811                    "other hint #3".to_string(),
2812                    "other hint #4".to_string(),
2813                    "other hint #5".to_string(),
2814                ];
2815                assert_eq!(expected_hints, sorted_cached_hint_labels(editor),
2816                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2817                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2818            })
2819            .unwrap();
2820
2821        editor_edited.store(true, Ordering::Release);
2822        editor
2823            .update(cx, |editor, window, cx| {
2824                editor.change_selections(None, window, cx, |s| {
2825                    s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
2826                });
2827                editor.handle_input("++++more text++++", window, cx);
2828            })
2829            .unwrap();
2830        cx.executor().run_until_parked();
2831        editor
2832            .update(cx, |editor, _window, cx| {
2833                let expected_hints = vec![
2834                    "main hint #0".to_string(),
2835                    "main hint #1".to_string(),
2836                    "main hint #2".to_string(),
2837                    "main hint #3".to_string(),
2838                    "main hint #4".to_string(),
2839                    "main hint #5".to_string(),
2840                    "other hint(edited) #0".to_string(),
2841                    "other hint(edited) #1".to_string(),
2842                ];
2843                assert_eq!(
2844                    expected_hints,
2845                    sorted_cached_hint_labels(editor),
2846                    "After multibuffer edit, editor gets scrolled back to the last selection; \
2847                all hints should be invalidated and required for all of its visible excerpts"
2848                );
2849                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2850            })
2851            .unwrap();
2852    }
2853
2854    #[gpui::test]
2855    async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
2856        init_test(cx, |settings| {
2857            settings.defaults.inlay_hints = Some(InlayHintSettings {
2858                enabled: true,
2859                edit_debounce_ms: 0,
2860                scroll_debounce_ms: 0,
2861                show_type_hints: false,
2862                show_parameter_hints: false,
2863                show_other_hints: false,
2864                show_background: false,
2865                toggle_on_modifiers_press: None,
2866            })
2867        });
2868
2869        let fs = FakeFs::new(cx.background_executor.clone());
2870        fs.insert_tree(
2871            path!("/a"),
2872            json!({
2873                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2874                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2875            }),
2876        )
2877        .await;
2878
2879        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2880
2881        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2882        language_registry.add(rust_lang());
2883        let mut fake_servers = language_registry.register_fake_lsp(
2884            "Rust",
2885            FakeLspAdapter {
2886                capabilities: lsp::ServerCapabilities {
2887                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2888                    ..Default::default()
2889                },
2890                ..Default::default()
2891            },
2892        );
2893
2894        let (buffer_1, _handle) = project
2895            .update(cx, |project, cx| {
2896                project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
2897            })
2898            .await
2899            .unwrap();
2900        let (buffer_2, _handle2) = project
2901            .update(cx, |project, cx| {
2902                project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
2903            })
2904            .await
2905            .unwrap();
2906        let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2907        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2908            let buffer_1_excerpts = multibuffer.push_excerpts(
2909                buffer_1.clone(),
2910                [ExcerptRange {
2911                    context: Point::new(0, 0)..Point::new(2, 0),
2912                    primary: None,
2913                }],
2914                cx,
2915            );
2916            let buffer_2_excerpts = multibuffer.push_excerpts(
2917                buffer_2.clone(),
2918                [ExcerptRange {
2919                    context: Point::new(0, 1)..Point::new(2, 1),
2920                    primary: None,
2921                }],
2922                cx,
2923            );
2924            (buffer_1_excerpts, buffer_2_excerpts)
2925        });
2926
2927        assert!(!buffer_1_excerpts.is_empty());
2928        assert!(!buffer_2_excerpts.is_empty());
2929
2930        cx.executor().run_until_parked();
2931        let editor = cx.add_window(|window, cx| {
2932            Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
2933        });
2934        let editor_edited = Arc::new(AtomicBool::new(false));
2935        let fake_server = fake_servers.next().await.unwrap();
2936        let closure_editor_edited = Arc::clone(&editor_edited);
2937        fake_server
2938            .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2939                let task_editor_edited = Arc::clone(&closure_editor_edited);
2940                async move {
2941                    let hint_text = if params.text_document.uri
2942                        == lsp::Url::from_file_path(path!("/a/main.rs")).unwrap()
2943                    {
2944                        "main hint"
2945                    } else if params.text_document.uri
2946                        == lsp::Url::from_file_path(path!("/a/other.rs")).unwrap()
2947                    {
2948                        "other hint"
2949                    } else {
2950                        panic!("unexpected uri: {:?}", params.text_document.uri);
2951                    };
2952
2953                    let positions = [
2954                        lsp::Position::new(0, 2),
2955                        lsp::Position::new(4, 2),
2956                        lsp::Position::new(22, 2),
2957                        lsp::Position::new(44, 2),
2958                        lsp::Position::new(56, 2),
2959                        lsp::Position::new(67, 2),
2960                    ];
2961                    let out_of_range_hint = lsp::InlayHint {
2962                        position: lsp::Position::new(
2963                            params.range.start.line + 99,
2964                            params.range.start.character + 99,
2965                        ),
2966                        label: lsp::InlayHintLabel::String(
2967                            "out of excerpt range, should be ignored".to_string(),
2968                        ),
2969                        kind: None,
2970                        text_edits: None,
2971                        tooltip: None,
2972                        padding_left: None,
2973                        padding_right: None,
2974                        data: None,
2975                    };
2976
2977                    let edited = task_editor_edited.load(Ordering::Acquire);
2978                    Ok(Some(
2979                        std::iter::once(out_of_range_hint)
2980                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2981                                lsp::InlayHint {
2982                                    position,
2983                                    label: lsp::InlayHintLabel::String(format!(
2984                                        "{hint_text}{} #{i}",
2985                                        if edited { "(edited)" } else { "" },
2986                                    )),
2987                                    kind: None,
2988                                    text_edits: None,
2989                                    tooltip: None,
2990                                    padding_left: None,
2991                                    padding_right: None,
2992                                    data: None,
2993                                }
2994                            }))
2995                            .collect(),
2996                    ))
2997                }
2998            })
2999            .next()
3000            .await;
3001        cx.executor().run_until_parked();
3002        editor
3003            .update(cx, |editor, _, cx| {
3004                assert_eq!(
3005                    vec!["main hint #0".to_string(), "other hint #0".to_string()],
3006                    sorted_cached_hint_labels(editor),
3007                    "Cache should update for both excerpts despite hints display was disabled"
3008                );
3009                assert!(
3010                visible_hint_labels(editor, cx).is_empty(),
3011                "All hints are disabled and should not be shown despite being present in the cache"
3012            );
3013            })
3014            .unwrap();
3015
3016        editor
3017            .update(cx, |editor, _, cx| {
3018                editor.buffer().update(cx, |multibuffer, cx| {
3019                    multibuffer.remove_excerpts(buffer_2_excerpts, cx)
3020                })
3021            })
3022            .unwrap();
3023        cx.executor().run_until_parked();
3024        editor
3025            .update(cx, |editor, _, cx| {
3026                assert_eq!(
3027                    vec!["main hint #0".to_string()],
3028                    cached_hint_labels(editor),
3029                    "For the removed excerpt, should clean corresponding cached hints"
3030                );
3031                assert!(
3032                visible_hint_labels(editor, cx).is_empty(),
3033                "All hints are disabled and should not be shown despite being present in the cache"
3034            );
3035            })
3036            .unwrap();
3037
3038        update_test_language_settings(cx, |settings| {
3039            settings.defaults.inlay_hints = Some(InlayHintSettings {
3040                enabled: true,
3041                edit_debounce_ms: 0,
3042                scroll_debounce_ms: 0,
3043                show_type_hints: true,
3044                show_parameter_hints: true,
3045                show_other_hints: true,
3046                show_background: false,
3047                toggle_on_modifiers_press: None,
3048            })
3049        });
3050        cx.executor().run_until_parked();
3051        editor
3052            .update(cx, |editor, _, cx| {
3053                let expected_hints = vec!["main hint #0".to_string()];
3054                assert_eq!(
3055                    expected_hints,
3056                    cached_hint_labels(editor),
3057                    "Hint display settings change should not change the cache"
3058                );
3059                assert_eq!(
3060                    expected_hints,
3061                    visible_hint_labels(editor, cx),
3062                    "Settings change should make cached hints visible"
3063                );
3064            })
3065            .unwrap();
3066    }
3067
3068    #[gpui::test]
3069    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
3070        init_test(cx, |settings| {
3071            settings.defaults.inlay_hints = Some(InlayHintSettings {
3072                enabled: true,
3073                edit_debounce_ms: 0,
3074                scroll_debounce_ms: 0,
3075                show_type_hints: true,
3076                show_parameter_hints: true,
3077                show_other_hints: true,
3078                show_background: false,
3079                toggle_on_modifiers_press: None,
3080            })
3081        });
3082
3083        let fs = FakeFs::new(cx.background_executor.clone());
3084        fs.insert_tree(
3085            path!("/a"),
3086            json!({
3087                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
3088                "other.rs": "// Test file",
3089            }),
3090        )
3091        .await;
3092
3093        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3094
3095        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3096        language_registry.add(rust_lang());
3097        language_registry.register_fake_lsp(
3098            "Rust",
3099            FakeLspAdapter {
3100                capabilities: lsp::ServerCapabilities {
3101                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3102                    ..Default::default()
3103                },
3104                initializer: Some(Box::new(move |fake_server| {
3105                    let lsp_request_count = Arc::new(AtomicU32::new(0));
3106                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3107                        move |params, _| {
3108                            let i = lsp_request_count.fetch_add(1, Ordering::Release) + 1;
3109                            async move {
3110                                assert_eq!(
3111                                    params.text_document.uri,
3112                                    lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3113                                );
3114                                let query_start = params.range.start;
3115                                Ok(Some(vec![lsp::InlayHint {
3116                                    position: query_start,
3117                                    label: lsp::InlayHintLabel::String(i.to_string()),
3118                                    kind: None,
3119                                    text_edits: None,
3120                                    tooltip: None,
3121                                    padding_left: None,
3122                                    padding_right: None,
3123                                    data: None,
3124                                }]))
3125                            }
3126                        },
3127                    );
3128                })),
3129                ..Default::default()
3130            },
3131        );
3132
3133        let buffer = project
3134            .update(cx, |project, cx| {
3135                project.open_local_buffer(path!("/a/main.rs"), cx)
3136            })
3137            .await
3138            .unwrap();
3139        let editor =
3140            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3141
3142        cx.executor().run_until_parked();
3143        editor
3144            .update(cx, |editor, window, cx| {
3145                editor.change_selections(None, window, cx, |s| {
3146                    s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3147                })
3148            })
3149            .unwrap();
3150        cx.executor().run_until_parked();
3151        editor
3152            .update(cx, |editor, _, cx| {
3153                let expected_hints = vec!["1".to_string()];
3154                assert_eq!(expected_hints, cached_hint_labels(editor));
3155                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3156            })
3157            .unwrap();
3158    }
3159
3160    #[gpui::test]
3161    async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) {
3162        init_test(cx, |settings| {
3163            settings.defaults.inlay_hints = Some(InlayHintSettings {
3164                enabled: false,
3165                edit_debounce_ms: 0,
3166                scroll_debounce_ms: 0,
3167                show_type_hints: true,
3168                show_parameter_hints: true,
3169                show_other_hints: true,
3170                show_background: false,
3171                toggle_on_modifiers_press: None,
3172            })
3173        });
3174
3175        let (_, editor, _fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
3176            let lsp_request_count = Arc::new(AtomicU32::new(0));
3177            fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3178                move |params, _| {
3179                    let lsp_request_count = lsp_request_count.clone();
3180                    async move {
3181                        assert_eq!(
3182                            params.text_document.uri,
3183                            lsp::Url::from_file_path(file_with_hints).unwrap(),
3184                        );
3185
3186                        let i = lsp_request_count.fetch_add(1, Ordering::SeqCst) + 1;
3187                        Ok(Some(vec![lsp::InlayHint {
3188                            position: lsp::Position::new(0, i),
3189                            label: lsp::InlayHintLabel::String(i.to_string()),
3190                            kind: None,
3191                            text_edits: None,
3192                            tooltip: None,
3193                            padding_left: None,
3194                            padding_right: None,
3195                            data: None,
3196                        }]))
3197                    }
3198                },
3199            );
3200        })
3201        .await;
3202
3203        editor
3204            .update(cx, |editor, window, cx| {
3205                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3206            })
3207            .unwrap();
3208
3209        cx.executor().run_until_parked();
3210        editor
3211            .update(cx, |editor, _, cx| {
3212                let expected_hints = vec!["1".to_string()];
3213                assert_eq!(
3214                    expected_hints,
3215                    cached_hint_labels(editor),
3216                    "Should display inlays after toggle despite them disabled in settings"
3217                );
3218                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3219            })
3220            .unwrap();
3221
3222        editor
3223            .update(cx, |editor, window, cx| {
3224                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3225            })
3226            .unwrap();
3227        cx.executor().run_until_parked();
3228        editor
3229            .update(cx, |editor, _, cx| {
3230                assert!(
3231                    cached_hint_labels(editor).is_empty(),
3232                    "Should clear hints after 2nd toggle"
3233                );
3234                assert!(visible_hint_labels(editor, cx).is_empty());
3235            })
3236            .unwrap();
3237
3238        update_test_language_settings(cx, |settings| {
3239            settings.defaults.inlay_hints = Some(InlayHintSettings {
3240                enabled: true,
3241                edit_debounce_ms: 0,
3242                scroll_debounce_ms: 0,
3243                show_type_hints: true,
3244                show_parameter_hints: true,
3245                show_other_hints: true,
3246                show_background: false,
3247                toggle_on_modifiers_press: None,
3248            })
3249        });
3250        cx.executor().run_until_parked();
3251        editor
3252            .update(cx, |editor, _, cx| {
3253                let expected_hints = vec!["2".to_string()];
3254                assert_eq!(
3255                    expected_hints,
3256                    cached_hint_labels(editor),
3257                    "Should query LSP hints for the 2nd time after enabling hints in settings"
3258                );
3259                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3260            })
3261            .unwrap();
3262
3263        editor
3264            .update(cx, |editor, window, cx| {
3265                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3266            })
3267            .unwrap();
3268        cx.executor().run_until_parked();
3269        editor
3270            .update(cx, |editor, _, cx| {
3271                assert!(
3272                    cached_hint_labels(editor).is_empty(),
3273                    "Should clear hints after enabling in settings and a 3rd toggle"
3274                );
3275                assert!(visible_hint_labels(editor, cx).is_empty());
3276            })
3277            .unwrap();
3278
3279        editor
3280            .update(cx, |editor, window, cx| {
3281                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3282            })
3283            .unwrap();
3284        cx.executor().run_until_parked();
3285        editor.update(cx, |editor, _, cx| {
3286            let expected_hints = vec!["3".to_string()];
3287            assert_eq!(
3288                expected_hints,
3289                cached_hint_labels(editor),
3290                "Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
3291            );
3292            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3293        }).unwrap();
3294    }
3295
3296    #[gpui::test]
3297    async fn test_inlays_at_the_same_place(cx: &mut gpui::TestAppContext) {
3298        init_test(cx, |settings| {
3299            settings.defaults.inlay_hints = Some(InlayHintSettings {
3300                enabled: true,
3301                edit_debounce_ms: 0,
3302                scroll_debounce_ms: 0,
3303                show_type_hints: true,
3304                show_parameter_hints: true,
3305                show_other_hints: true,
3306                show_background: false,
3307                toggle_on_modifiers_press: None,
3308            })
3309        });
3310
3311        let fs = FakeFs::new(cx.background_executor.clone());
3312        fs.insert_tree(
3313            path!("/a"),
3314            json!({
3315                "main.rs": "fn main() {
3316                    let x = 42;
3317                    std::thread::scope(|s| {
3318                        s.spawn(|| {
3319                            let _x = x;
3320                        });
3321                    });
3322                }",
3323                "other.rs": "// Test file",
3324            }),
3325        )
3326        .await;
3327
3328        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3329
3330        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3331        language_registry.add(rust_lang());
3332        language_registry.register_fake_lsp(
3333            "Rust",
3334            FakeLspAdapter {
3335                capabilities: lsp::ServerCapabilities {
3336                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3337                    ..Default::default()
3338                },
3339                initializer: Some(Box::new(move |fake_server| {
3340                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3341                        move |params, _| async move {
3342                            assert_eq!(
3343                                params.text_document.uri,
3344                                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
3345                            );
3346                            Ok(Some(
3347                                serde_json::from_value(json!([
3348                                    {
3349                                        "position": {
3350                                            "line": 3,
3351                                            "character": 16
3352                                        },
3353                                        "label": "move",
3354                                        "paddingLeft": false,
3355                                        "paddingRight": false
3356                                    },
3357                                    {
3358                                        "position": {
3359                                            "line": 3,
3360                                            "character": 16
3361                                        },
3362                                        "label": "(",
3363                                        "paddingLeft": false,
3364                                        "paddingRight": false
3365                                    },
3366                                    {
3367                                        "position": {
3368                                            "line": 3,
3369                                            "character": 16
3370                                        },
3371                                        "label": [
3372                                            {
3373                                                "value": "&x"
3374                                            }
3375                                        ],
3376                                        "paddingLeft": false,
3377                                        "paddingRight": false,
3378                                        "data": {
3379                                            "file_id": 0
3380                                        }
3381                                    },
3382                                    {
3383                                        "position": {
3384                                            "line": 3,
3385                                            "character": 16
3386                                        },
3387                                        "label": ")",
3388                                        "paddingLeft": false,
3389                                        "paddingRight": true
3390                                    },
3391                                    // not a correct syntax, but checks that same symbols at the same place
3392                                    // are not deduplicated
3393                                    {
3394                                        "position": {
3395                                            "line": 3,
3396                                            "character": 16
3397                                        },
3398                                        "label": ")",
3399                                        "paddingLeft": false,
3400                                        "paddingRight": true
3401                                    },
3402                                ]))
3403                                .unwrap(),
3404                            ))
3405                        },
3406                    );
3407                })),
3408                ..FakeLspAdapter::default()
3409            },
3410        );
3411
3412        let buffer = project
3413            .update(cx, |project, cx| {
3414                project.open_local_buffer(path!("/a/main.rs"), cx)
3415            })
3416            .await
3417            .unwrap();
3418        let editor =
3419            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3420
3421        cx.executor().run_until_parked();
3422        editor
3423            .update(cx, |editor, window, cx| {
3424                editor.change_selections(None, window, cx, |s| {
3425                    s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3426                })
3427            })
3428            .unwrap();
3429        cx.executor().run_until_parked();
3430        editor
3431            .update(cx, |editor, _window, cx| {
3432                let expected_hints = vec![
3433                    "move".to_string(),
3434                    "(".to_string(),
3435                    "&x".to_string(),
3436                    ") ".to_string(),
3437                    ") ".to_string(),
3438                ];
3439                assert_eq!(
3440                    expected_hints,
3441                    cached_hint_labels(editor),
3442                    "Editor inlay hints should repeat server's order when placed at the same spot"
3443                );
3444                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3445            })
3446            .unwrap();
3447    }
3448
3449    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
3450        cx.update(|cx| {
3451            let settings_store = SettingsStore::test(cx);
3452            cx.set_global(settings_store);
3453            theme::init(theme::LoadThemes::JustBase, cx);
3454            release_channel::init(SemanticVersion::default(), cx);
3455            client::init_settings(cx);
3456            language::init(cx);
3457            Project::init_settings(cx);
3458            workspace::init_settings(cx);
3459            crate::init(cx);
3460        });
3461
3462        update_test_language_settings(cx, f);
3463    }
3464
3465    async fn prepare_test_objects(
3466        cx: &mut TestAppContext,
3467        initialize: impl 'static + Send + Fn(&mut FakeLanguageServer, &'static str) + Send + Sync,
3468    ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
3469        let fs = FakeFs::new(cx.background_executor.clone());
3470        fs.insert_tree(
3471            path!("/a"),
3472            json!({
3473                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
3474                "other.rs": "// Test file",
3475            }),
3476        )
3477        .await;
3478
3479        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3480        let file_path = path!("/a/main.rs");
3481
3482        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3483        language_registry.add(rust_lang());
3484        let mut fake_servers = language_registry.register_fake_lsp(
3485            "Rust",
3486            FakeLspAdapter {
3487                capabilities: lsp::ServerCapabilities {
3488                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3489                    ..Default::default()
3490                },
3491                initializer: Some(Box::new(move |server| initialize(server, file_path))),
3492                ..Default::default()
3493            },
3494        );
3495
3496        let buffer = project
3497            .update(cx, |project, cx| {
3498                project.open_local_buffer(path!("/a/main.rs"), cx)
3499            })
3500            .await
3501            .unwrap();
3502        let editor =
3503            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3504
3505        editor
3506            .update(cx, |editor, _, cx| {
3507                assert!(cached_hint_labels(editor).is_empty());
3508                assert!(visible_hint_labels(editor, cx).is_empty());
3509            })
3510            .unwrap();
3511
3512        cx.executor().run_until_parked();
3513        let fake_server = fake_servers.next().await.unwrap();
3514        (file_path, editor, fake_server)
3515    }
3516
3517    // 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.
3518    // Ensure a stable order for testing.
3519    fn sorted_cached_hint_labels(editor: &Editor) -> Vec<String> {
3520        let mut labels = cached_hint_labels(editor);
3521        labels.sort();
3522        labels
3523    }
3524
3525    pub fn cached_hint_labels(editor: &Editor) -> Vec<String> {
3526        let mut labels = Vec::new();
3527        for excerpt_hints in editor.inlay_hint_cache().hints.values() {
3528            let excerpt_hints = excerpt_hints.read();
3529            for id in &excerpt_hints.ordered_hints {
3530                let hint = &excerpt_hints.hints_by_id[id];
3531                let mut label = hint.text();
3532                if hint.padding_left {
3533                    label.insert(0, ' ');
3534                }
3535                if hint.padding_right {
3536                    label.push_str(" ");
3537                }
3538                labels.push(label);
3539            }
3540        }
3541
3542        labels
3543    }
3544
3545    pub fn visible_hint_labels(editor: &Editor, cx: &Context<Editor>) -> Vec<String> {
3546        editor
3547            .visible_inlay_hints(cx)
3548            .into_iter()
3549            .map(|hint| hint.text.to_string())
3550            .collect()
3551    }
3552}