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