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