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