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