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