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