inlay_hints.rs

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