inlay_hints.rs

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