inlay_hint_cache.rs

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