inlay_hints.rs

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