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