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