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::Autoscroll;
 964    use crate::scroll::ScrollAmount;
 965    use crate::{Editor, SelectionEffects};
 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, PathKey};
 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.set_excerpts_for_path(
2326                PathKey::sorted(0),
2327                buffer_1.clone(),
2328                [
2329                    Point::new(0, 0)..Point::new(2, 0),
2330                    Point::new(4, 0)..Point::new(11, 0),
2331                    Point::new(22, 0)..Point::new(33, 0),
2332                    Point::new(44, 0)..Point::new(55, 0),
2333                    Point::new(56, 0)..Point::new(66, 0),
2334                    Point::new(67, 0)..Point::new(77, 0),
2335                ],
2336                0,
2337                cx,
2338            );
2339            multibuffer.set_excerpts_for_path(
2340                PathKey::sorted(1),
2341                buffer_2.clone(),
2342                [
2343                    Point::new(0, 1)..Point::new(2, 1),
2344                    Point::new(4, 1)..Point::new(11, 1),
2345                    Point::new(22, 1)..Point::new(33, 1),
2346                    Point::new(44, 1)..Point::new(55, 1),
2347                    Point::new(56, 1)..Point::new(66, 1),
2348                    Point::new(67, 1)..Point::new(77, 1),
2349                ],
2350                0,
2351                cx,
2352            );
2353            multibuffer
2354        });
2355
2356        cx.executor().run_until_parked();
2357        let editor = cx.add_window(|window, cx| {
2358            Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
2359        });
2360
2361        let editor_edited = Arc::new(AtomicBool::new(false));
2362        let fake_server = fake_servers.next().await.unwrap();
2363        let closure_editor_edited = Arc::clone(&editor_edited);
2364        fake_server
2365            .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2366                let task_editor_edited = Arc::clone(&closure_editor_edited);
2367                async move {
2368                    let hint_text = if params.text_document.uri
2369                        == lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
2370                    {
2371                        "main hint"
2372                    } else if params.text_document.uri
2373                        == lsp::Uri::from_file_path(path!("/a/other.rs")).unwrap()
2374                    {
2375                        "other hint"
2376                    } else {
2377                        panic!("unexpected uri: {:?}", params.text_document.uri);
2378                    };
2379
2380                    // one hint per excerpt
2381                    let positions = [
2382                        lsp::Position::new(0, 2),
2383                        lsp::Position::new(4, 2),
2384                        lsp::Position::new(22, 2),
2385                        lsp::Position::new(44, 2),
2386                        lsp::Position::new(56, 2),
2387                        lsp::Position::new(67, 2),
2388                    ];
2389                    let out_of_range_hint = lsp::InlayHint {
2390                        position: lsp::Position::new(
2391                            params.range.start.line + 99,
2392                            params.range.start.character + 99,
2393                        ),
2394                        label: lsp::InlayHintLabel::String(
2395                            "out of excerpt range, should be ignored".to_string(),
2396                        ),
2397                        kind: None,
2398                        text_edits: None,
2399                        tooltip: None,
2400                        padding_left: None,
2401                        padding_right: None,
2402                        data: None,
2403                    };
2404
2405                    let edited = task_editor_edited.load(Ordering::Acquire);
2406                    Ok(Some(
2407                        std::iter::once(out_of_range_hint)
2408                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2409                                lsp::InlayHint {
2410                                    position,
2411                                    label: lsp::InlayHintLabel::String(format!(
2412                                        "{hint_text}{E} #{i}",
2413                                        E = if edited { "(edited)" } else { "" },
2414                                    )),
2415                                    kind: None,
2416                                    text_edits: None,
2417                                    tooltip: None,
2418                                    padding_left: None,
2419                                    padding_right: None,
2420                                    data: None,
2421                                }
2422                            }))
2423                            .collect(),
2424                    ))
2425                }
2426            })
2427            .next()
2428            .await;
2429        cx.executor().run_until_parked();
2430
2431        editor
2432            .update(cx, |editor, _window, cx| {
2433                let expected_hints = vec![
2434                    "main hint #0".to_string(),
2435                    "main hint #1".to_string(),
2436                    "main hint #2".to_string(),
2437                    "main hint #3".to_string(),
2438                    "main hint #4".to_string(),
2439                    "main hint #5".to_string(),
2440                ];
2441                assert_eq!(
2442                    expected_hints,
2443                    sorted_cached_hint_labels(editor, cx),
2444                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2445                );
2446                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2447            })
2448            .unwrap();
2449
2450        editor
2451            .update(cx, |editor, window, cx| {
2452                editor.change_selections(
2453                    SelectionEffects::scroll(Autoscroll::Next),
2454                    window,
2455                    cx,
2456                    |s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]),
2457                );
2458                editor.change_selections(
2459                    SelectionEffects::scroll(Autoscroll::Next),
2460                    window,
2461                    cx,
2462                    |s| s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]),
2463                );
2464                editor.change_selections(
2465                    SelectionEffects::scroll(Autoscroll::Next),
2466                    window,
2467                    cx,
2468                    |s| s.select_ranges([Point::new(57, 0)..Point::new(57, 0)]),
2469                );
2470            })
2471            .unwrap();
2472        cx.executor().run_until_parked();
2473        editor
2474            .update(cx, |editor, _window, cx| {
2475                let expected_hints = vec![
2476                    "main hint #0".to_string(),
2477                    "main hint #1".to_string(),
2478                    "main hint #2".to_string(),
2479                    "main hint #3".to_string(),
2480                    "main hint #4".to_string(),
2481                    "main hint #5".to_string(),
2482                ];
2483                assert_eq!(expected_hints, sorted_cached_hint_labels(editor, cx),
2484                    "New hints are not shown right after scrolling, we need to wait for the buffer to be registered");
2485                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2486            })
2487            .unwrap();
2488        cx.executor().advance_clock(Duration::from_millis(100));
2489        cx.executor().run_until_parked();
2490        editor
2491            .update(cx, |editor, _window, cx| {
2492                let expected_hints = vec![
2493                    "main hint #0".to_string(),
2494                    "main hint #1".to_string(),
2495                    "main hint #2".to_string(),
2496                    "main hint #3".to_string(),
2497                    "main hint #4".to_string(),
2498                    "main hint #5".to_string(),
2499                    "other hint #0".to_string(),
2500                    "other hint #1".to_string(),
2501                    "other hint #2".to_string(),
2502                    "other hint #3".to_string(),
2503                ];
2504                assert_eq!(
2505                    expected_hints,
2506                    sorted_cached_hint_labels(editor, cx),
2507                    "After scrolling to the new buffer and waiting for it to be registered, new hints should appear");
2508                assert_eq!(
2509                    expected_hints,
2510                    visible_hint_labels(editor, cx),
2511                    "Editor should show only visible hints",
2512                );
2513            })
2514            .unwrap();
2515
2516        editor
2517            .update(cx, |editor, window, cx| {
2518                editor.change_selections(
2519                    SelectionEffects::scroll(Autoscroll::Next),
2520                    window,
2521                    cx,
2522                    |s| s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]),
2523                );
2524            })
2525            .unwrap();
2526        cx.executor().advance_clock(Duration::from_millis(100));
2527        cx.executor().run_until_parked();
2528        editor
2529            .update(cx, |editor, _window, cx| {
2530                let expected_hints = vec![
2531                    "main hint #0".to_string(),
2532                    "main hint #1".to_string(),
2533                    "main hint #2".to_string(),
2534                    "main hint #3".to_string(),
2535                    "main hint #4".to_string(),
2536                    "main hint #5".to_string(),
2537                    "other hint #0".to_string(),
2538                    "other hint #1".to_string(),
2539                    "other hint #2".to_string(),
2540                    "other hint #3".to_string(),
2541                    "other hint #4".to_string(),
2542                    "other hint #5".to_string(),
2543                ];
2544                assert_eq!(
2545                    expected_hints,
2546                    sorted_cached_hint_labels(editor, cx),
2547                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched"
2548                );
2549                assert_eq!(
2550                    expected_hints,
2551                    visible_hint_labels(editor, cx),
2552                    "Editor shows only hints for excerpts that were visible when scrolling"
2553                );
2554            })
2555            .unwrap();
2556
2557        editor
2558            .update(cx, |editor, window, cx| {
2559                editor.change_selections(
2560                    SelectionEffects::scroll(Autoscroll::Next),
2561                    window,
2562                    cx,
2563                    |s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]),
2564                );
2565            })
2566            .unwrap();
2567        cx.executor().run_until_parked();
2568        editor
2569            .update(cx, |editor, _window, cx| {
2570                let expected_hints = vec![
2571                    "main hint #0".to_string(),
2572                    "main hint #1".to_string(),
2573                    "main hint #2".to_string(),
2574                    "main hint #3".to_string(),
2575                    "main hint #4".to_string(),
2576                    "main hint #5".to_string(),
2577                    "other hint #0".to_string(),
2578                    "other hint #1".to_string(),
2579                    "other hint #2".to_string(),
2580                    "other hint #3".to_string(),
2581                    "other hint #4".to_string(),
2582                    "other hint #5".to_string(),
2583                ];
2584                assert_eq!(
2585                    expected_hints,
2586                    sorted_cached_hint_labels(editor, cx),
2587                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints"
2588                );
2589                assert_eq!(
2590                    expected_hints,
2591                    visible_hint_labels(editor, cx),
2592                );
2593            })
2594            .unwrap();
2595
2596        // We prepare to change the scrolling on edit, but do not scroll yet
2597        editor
2598            .update(cx, |editor, window, cx| {
2599                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
2600                    s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
2601                });
2602            })
2603            .unwrap();
2604        cx.executor().run_until_parked();
2605        // Edit triggers the scrolling too
2606        editor_edited.store(true, Ordering::Release);
2607        editor
2608            .update(cx, |editor, window, cx| {
2609                editor.handle_input("++++more text++++", window, cx);
2610            })
2611            .unwrap();
2612        cx.executor().run_until_parked();
2613        // Wait again to trigger the inlay hints fetch on scroll
2614        cx.executor().advance_clock(Duration::from_millis(100));
2615        cx.executor().run_until_parked();
2616        editor
2617            .update(cx, |editor, _window, cx| {
2618                let expected_hints = vec![
2619                    "main hint(edited) #0".to_string(),
2620                    "main hint(edited) #1".to_string(),
2621                    "main hint(edited) #2".to_string(),
2622                    "main hint(edited) #3".to_string(),
2623                    "main hint(edited) #4".to_string(),
2624                    "main hint(edited) #5".to_string(),
2625                    "other hint(edited) #0".to_string(),
2626                    "other hint(edited) #1".to_string(),
2627                    "other hint(edited) #2".to_string(),
2628                    "other hint(edited) #3".to_string(),
2629                ];
2630                assert_eq!(
2631                    expected_hints,
2632                    sorted_cached_hint_labels(editor, cx),
2633                    "After multibuffer edit, editor gets scrolled back to the last selection; \
2634                all hints should be invalidated and required for all of its visible excerpts"
2635                );
2636                assert_eq!(
2637                    expected_hints,
2638                    visible_hint_labels(editor, cx),
2639                    "All excerpts should get their hints"
2640                );
2641            })
2642            .unwrap();
2643    }
2644
2645    #[gpui::test]
2646    async fn test_editing_in_multi_buffer(cx: &mut gpui::TestAppContext) {
2647        init_test(cx, &|settings| {
2648            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
2649                enabled: Some(true),
2650                ..InlayHintSettingsContent::default()
2651            })
2652        });
2653
2654        let fs = FakeFs::new(cx.background_executor.clone());
2655        fs.insert_tree(
2656            path!("/a"),
2657            json!({
2658                "main.rs": format!("fn main() {{\n{}\n}}", (0..200).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2659                "lib.rs": r#"let a = 1;
2660let b = 2;
2661let c = 3;"#
2662            }),
2663        )
2664        .await;
2665
2666        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
2667
2668        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2669        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2670        let language = rust_lang();
2671        language_registry.add(language);
2672
2673        let closure_ranges_fetched = lsp_request_ranges.clone();
2674        let mut fake_servers = language_registry.register_fake_lsp(
2675            "Rust",
2676            FakeLspAdapter {
2677                capabilities: lsp::ServerCapabilities {
2678                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2679                    ..lsp::ServerCapabilities::default()
2680                },
2681                initializer: Some(Box::new(move |fake_server| {
2682                    let closure_ranges_fetched = closure_ranges_fetched.clone();
2683                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
2684                        move |params, _| {
2685                            let closure_ranges_fetched = closure_ranges_fetched.clone();
2686                            async move {
2687                                let prefix = if params.text_document.uri
2688                                    == lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
2689                                {
2690                                    closure_ranges_fetched
2691                                        .lock()
2692                                        .push(("main.rs", params.range));
2693                                    "main.rs"
2694                                } else if params.text_document.uri
2695                                    == lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
2696                                {
2697                                    closure_ranges_fetched.lock().push(("lib.rs", params.range));
2698                                    "lib.rs"
2699                                } else {
2700                                    panic!("Unexpected file path {:?}", params.text_document.uri);
2701                                };
2702                                Ok(Some(
2703                                    (params.range.start.line..params.range.end.line)
2704                                        .map(|row| lsp::InlayHint {
2705                                            position: lsp::Position::new(row, 0),
2706                                            label: lsp::InlayHintLabel::String(format!(
2707                                                "{prefix} Inlay hint #{row}"
2708                                            )),
2709                                            kind: Some(lsp::InlayHintKind::TYPE),
2710                                            text_edits: None,
2711                                            tooltip: None,
2712                                            padding_left: None,
2713                                            padding_right: None,
2714                                            data: None,
2715                                        })
2716                                        .collect(),
2717                                ))
2718                            }
2719                        },
2720                    );
2721                })),
2722                ..FakeLspAdapter::default()
2723            },
2724        );
2725
2726        let (buffer_1, _handle_1) = project
2727            .update(cx, |project, cx| {
2728                project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
2729            })
2730            .await
2731            .unwrap();
2732        let (buffer_2, _handle_2) = project
2733            .update(cx, |project, cx| {
2734                project.open_local_buffer_with_lsp(path!("/a/lib.rs"), cx)
2735            })
2736            .await
2737            .unwrap();
2738        let multi_buffer = cx.new(|cx| {
2739            let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
2740            multibuffer.set_excerpts_for_path(
2741                PathKey::sorted(0),
2742                buffer_1.clone(),
2743                [
2744                    Point::new(49, 0)..Point::new(53, 0),
2745                    Point::new(70, 0)..Point::new(73, 0),
2746                ],
2747                0,
2748                cx,
2749            );
2750            multibuffer.set_excerpts_for_path(
2751                PathKey::sorted(1),
2752                buffer_2.clone(),
2753                [Point::new(0, 0)..Point::new(4, 0)],
2754                0,
2755                cx,
2756            );
2757            multibuffer
2758        });
2759
2760        let editor = cx.add_window(|window, cx| {
2761            let mut editor =
2762                Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx);
2763            editor.change_selections(SelectionEffects::default(), window, cx, |s| {
2764                s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
2765            });
2766            editor
2767        });
2768
2769        let _fake_server = fake_servers.next().await.unwrap();
2770        cx.executor().advance_clock(Duration::from_millis(100));
2771        cx.executor().run_until_parked();
2772
2773        assert_eq!(
2774            vec![
2775                (
2776                    "lib.rs",
2777                    lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(2, 10))
2778                ),
2779                (
2780                    "main.rs",
2781                    lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(50, 0))
2782                ),
2783                (
2784                    "main.rs",
2785                    lsp::Range::new(lsp::Position::new(50, 0), lsp::Position::new(100, 0))
2786                ),
2787            ],
2788            lsp_request_ranges
2789                .lock()
2790                .drain(..)
2791                .sorted_by_key(|(prefix, r)| (prefix.to_owned(), r.start))
2792                .collect::<Vec<_>>(),
2793            "For large buffers, should query chunks that cover both visible excerpt"
2794        );
2795        editor
2796            .update(cx, |editor, _window, cx| {
2797                assert_eq!(
2798                    (0..2)
2799                        .map(|i| format!("lib.rs Inlay hint #{i}"))
2800                        .chain((0..100).map(|i| format!("main.rs Inlay hint #{i}")))
2801                        .collect::<Vec<_>>(),
2802                    sorted_cached_hint_labels(editor, cx),
2803                    "Both chunks should provide their inlay hints"
2804                );
2805                assert_eq!(
2806                    vec![
2807                        "main.rs Inlay hint #49".to_owned(),
2808                        "main.rs Inlay hint #50".to_owned(),
2809                        "main.rs Inlay hint #51".to_owned(),
2810                        "main.rs Inlay hint #52".to_owned(),
2811                        "main.rs Inlay hint #53".to_owned(),
2812                        "main.rs Inlay hint #70".to_owned(),
2813                        "main.rs Inlay hint #71".to_owned(),
2814                        "main.rs Inlay hint #72".to_owned(),
2815                        "main.rs Inlay hint #73".to_owned(),
2816                        "lib.rs Inlay hint #0".to_owned(),
2817                        "lib.rs Inlay hint #1".to_owned(),
2818                    ],
2819                    visible_hint_labels(editor, cx),
2820                    "Only hints from visible excerpt should be added into the editor"
2821                );
2822            })
2823            .unwrap();
2824
2825        editor
2826            .update(cx, |editor, window, cx| {
2827                editor.handle_input("a", window, cx);
2828            })
2829            .unwrap();
2830        cx.executor().advance_clock(Duration::from_millis(1000));
2831        cx.executor().run_until_parked();
2832        assert_eq!(
2833            vec![
2834                (
2835                    "lib.rs",
2836                    lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(2, 10))
2837                ),
2838                (
2839                    "main.rs",
2840                    lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(50, 0))
2841                ),
2842                (
2843                    "main.rs",
2844                    lsp::Range::new(lsp::Position::new(50, 0), lsp::Position::new(100, 0))
2845                ),
2846            ],
2847            lsp_request_ranges
2848                .lock()
2849                .drain(..)
2850                .sorted_by_key(|(prefix, r)| (prefix.to_owned(), r.start))
2851                .collect::<Vec<_>>(),
2852            "Same chunks should be re-queried on edit"
2853        );
2854        editor
2855            .update(cx, |editor, _window, cx| {
2856                assert_eq!(
2857                    (0..2)
2858                        .map(|i| format!("lib.rs Inlay hint #{i}"))
2859                        .chain((0..100).map(|i| format!("main.rs Inlay hint #{i}")))
2860                        .collect::<Vec<_>>(),
2861                    sorted_cached_hint_labels(editor, cx),
2862                    "Same hints should be re-inserted after the edit"
2863                );
2864                assert_eq!(
2865                    vec![
2866                        "main.rs Inlay hint #49".to_owned(),
2867                        "main.rs Inlay hint #50".to_owned(),
2868                        "main.rs Inlay hint #51".to_owned(),
2869                        "main.rs Inlay hint #52".to_owned(),
2870                        "main.rs Inlay hint #53".to_owned(),
2871                        "main.rs Inlay hint #70".to_owned(),
2872                        "main.rs Inlay hint #71".to_owned(),
2873                        "main.rs Inlay hint #72".to_owned(),
2874                        "main.rs Inlay hint #73".to_owned(),
2875                        "lib.rs Inlay hint #0".to_owned(),
2876                        "lib.rs Inlay hint #1".to_owned(),
2877                    ],
2878                    visible_hint_labels(editor, cx),
2879                    "Same hints should be re-inserted into the editor after the edit"
2880                );
2881            })
2882            .unwrap();
2883    }
2884
2885    #[gpui::test]
2886    async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
2887        init_test(cx, &|settings| {
2888            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
2889                show_value_hints: Some(true),
2890                enabled: Some(true),
2891                edit_debounce_ms: Some(0),
2892                scroll_debounce_ms: Some(0),
2893                show_type_hints: Some(false),
2894                show_parameter_hints: Some(false),
2895                show_other_hints: Some(false),
2896                show_background: Some(false),
2897                toggle_on_modifiers_press: None,
2898            })
2899        });
2900
2901        let fs = FakeFs::new(cx.background_executor.clone());
2902        fs.insert_tree(
2903            path!("/a"),
2904            json!({
2905                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2906                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2907            }),
2908        )
2909        .await;
2910
2911        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
2912
2913        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
2914        language_registry.add(rust_lang());
2915        let mut fake_servers = language_registry.register_fake_lsp(
2916            "Rust",
2917            FakeLspAdapter {
2918                capabilities: lsp::ServerCapabilities {
2919                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2920                    ..lsp::ServerCapabilities::default()
2921                },
2922                ..FakeLspAdapter::default()
2923            },
2924        );
2925
2926        let (buffer_1, _handle) = project
2927            .update(cx, |project, cx| {
2928                project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
2929            })
2930            .await
2931            .unwrap();
2932        let (buffer_2, _handle2) = project
2933            .update(cx, |project, cx| {
2934                project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
2935            })
2936            .await
2937            .unwrap();
2938        let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
2939        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2940            multibuffer.set_excerpts_for_path(
2941                PathKey::sorted(0),
2942                buffer_1.clone(),
2943                [Point::new(0, 0)..Point::new(2, 0)],
2944                0,
2945                cx,
2946            );
2947            multibuffer.set_excerpts_for_path(
2948                PathKey::sorted(1),
2949                buffer_2.clone(),
2950                [Point::new(0, 1)..Point::new(2, 1)],
2951                0,
2952                cx,
2953            );
2954            let excerpt_ids = multibuffer.excerpt_ids();
2955            let buffer_1_excerpts = vec![excerpt_ids[0]];
2956            let buffer_2_excerpts = vec![excerpt_ids[1]];
2957            (buffer_1_excerpts, buffer_2_excerpts)
2958        });
2959
2960        assert!(!buffer_1_excerpts.is_empty());
2961        assert!(!buffer_2_excerpts.is_empty());
2962
2963        cx.executor().run_until_parked();
2964        let editor = cx.add_window(|window, cx| {
2965            Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
2966        });
2967        let editor_edited = Arc::new(AtomicBool::new(false));
2968        let fake_server = fake_servers.next().await.unwrap();
2969        let closure_editor_edited = Arc::clone(&editor_edited);
2970        fake_server
2971            .set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2972                let task_editor_edited = Arc::clone(&closure_editor_edited);
2973                async move {
2974                    let hint_text = if params.text_document.uri
2975                        == lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
2976                    {
2977                        "main hint"
2978                    } else if params.text_document.uri
2979                        == lsp::Uri::from_file_path(path!("/a/other.rs")).unwrap()
2980                    {
2981                        "other hint"
2982                    } else {
2983                        panic!("unexpected uri: {:?}", params.text_document.uri);
2984                    };
2985
2986                    let positions = [
2987                        lsp::Position::new(0, 2),
2988                        lsp::Position::new(4, 2),
2989                        lsp::Position::new(22, 2),
2990                        lsp::Position::new(44, 2),
2991                        lsp::Position::new(56, 2),
2992                        lsp::Position::new(67, 2),
2993                    ];
2994                    let out_of_range_hint = lsp::InlayHint {
2995                        position: lsp::Position::new(
2996                            params.range.start.line + 99,
2997                            params.range.start.character + 99,
2998                        ),
2999                        label: lsp::InlayHintLabel::String(
3000                            "out of excerpt range, should be ignored".to_string(),
3001                        ),
3002                        kind: None,
3003                        text_edits: None,
3004                        tooltip: None,
3005                        padding_left: None,
3006                        padding_right: None,
3007                        data: None,
3008                    };
3009
3010                    let edited = task_editor_edited.load(Ordering::Acquire);
3011                    Ok(Some(
3012                        std::iter::once(out_of_range_hint)
3013                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
3014                                lsp::InlayHint {
3015                                    position,
3016                                    label: lsp::InlayHintLabel::String(format!(
3017                                        "{hint_text}{} #{i}",
3018                                        if edited { "(edited)" } else { "" },
3019                                    )),
3020                                    kind: None,
3021                                    text_edits: None,
3022                                    tooltip: None,
3023                                    padding_left: None,
3024                                    padding_right: None,
3025                                    data: None,
3026                                }
3027                            }))
3028                            .collect(),
3029                    ))
3030                }
3031            })
3032            .next()
3033            .await;
3034        cx.executor().advance_clock(Duration::from_millis(100));
3035        cx.executor().run_until_parked();
3036        editor
3037            .update(cx, |editor, _, cx| {
3038                assert_eq!(
3039                    vec![
3040                        "main hint #0".to_string(),
3041                        "main hint #1".to_string(),
3042                        "main hint #2".to_string(),
3043                        "main hint #3".to_string(),
3044                        "other hint #0".to_string(),
3045                        "other hint #1".to_string(),
3046                        "other hint #2".to_string(),
3047                        "other hint #3".to_string(),
3048                    ],
3049                    sorted_cached_hint_labels(editor, cx),
3050                    "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"
3051                );
3052                assert_eq!(
3053                    Vec::<String>::new(),
3054                    visible_hint_labels(editor, cx),
3055                    "All hints are disabled and should not be shown despite being present in the cache"
3056                );
3057            })
3058            .unwrap();
3059
3060        editor
3061            .update(cx, |editor, _, cx| {
3062                editor.buffer().update(cx, |multibuffer, cx| {
3063                    multibuffer.remove_excerpts_for_path(PathKey::sorted(1), cx);
3064                })
3065            })
3066            .unwrap();
3067        cx.executor().run_until_parked();
3068        editor
3069            .update(cx, |editor, _, cx| {
3070                assert_eq!(
3071                    vec![
3072                        "main hint #0".to_string(),
3073                        "main hint #1".to_string(),
3074                        "main hint #2".to_string(),
3075                        "main hint #3".to_string(),
3076                    ],
3077                    cached_hint_labels(editor, cx),
3078                    "For the removed excerpt, should clean corresponding cached hints as its buffer was dropped"
3079                );
3080                assert!(
3081                visible_hint_labels(editor, cx).is_empty(),
3082                "All hints are disabled and should not be shown despite being present in the cache"
3083            );
3084            })
3085            .unwrap();
3086
3087        update_test_language_settings(cx, &|settings| {
3088            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
3089                show_value_hints: Some(true),
3090                enabled: Some(true),
3091                edit_debounce_ms: Some(0),
3092                scroll_debounce_ms: Some(0),
3093                show_type_hints: Some(true),
3094                show_parameter_hints: Some(true),
3095                show_other_hints: Some(true),
3096                show_background: Some(false),
3097                toggle_on_modifiers_press: None,
3098            })
3099        });
3100        cx.executor().run_until_parked();
3101        editor
3102            .update(cx, |editor, _, cx| {
3103                assert_eq!(
3104                    vec![
3105                        "main hint #0".to_string(),
3106                        "main hint #1".to_string(),
3107                        "main hint #2".to_string(),
3108                        "main hint #3".to_string(),
3109                    ],
3110                    cached_hint_labels(editor, cx),
3111                    "Hint display settings change should not change the cache"
3112                );
3113                assert_eq!(
3114                    vec![
3115                        "main hint #0".to_string(),
3116                    ],
3117                    visible_hint_labels(editor, cx),
3118                    "Settings change should make cached hints visible, but only the visible ones, from the remaining excerpt"
3119                );
3120            })
3121            .unwrap();
3122    }
3123
3124    #[gpui::test]
3125    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
3126        init_test(cx, &|settings| {
3127            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
3128                show_value_hints: Some(true),
3129                enabled: Some(true),
3130                edit_debounce_ms: Some(0),
3131                scroll_debounce_ms: Some(0),
3132                show_type_hints: Some(true),
3133                show_parameter_hints: Some(true),
3134                show_other_hints: Some(true),
3135                show_background: Some(false),
3136                toggle_on_modifiers_press: None,
3137            })
3138        });
3139
3140        let fs = FakeFs::new(cx.background_executor.clone());
3141        fs.insert_tree(
3142            path!("/a"),
3143            json!({
3144                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
3145                "other.rs": "// Test file",
3146            }),
3147        )
3148        .await;
3149
3150        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3151
3152        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3153        language_registry.add(rust_lang());
3154        language_registry.register_fake_lsp(
3155            "Rust",
3156            FakeLspAdapter {
3157                capabilities: lsp::ServerCapabilities {
3158                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3159                    ..lsp::ServerCapabilities::default()
3160                },
3161                initializer: Some(Box::new(move |fake_server| {
3162                    let lsp_request_count = Arc::new(AtomicU32::new(0));
3163                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3164                        move |params, _| {
3165                            let i = lsp_request_count.fetch_add(1, Ordering::Release) + 1;
3166                            async move {
3167                                assert_eq!(
3168                                    params.text_document.uri,
3169                                    lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
3170                                );
3171                                let query_start = params.range.start;
3172                                Ok(Some(vec![lsp::InlayHint {
3173                                    position: query_start,
3174                                    label: lsp::InlayHintLabel::String(i.to_string()),
3175                                    kind: None,
3176                                    text_edits: None,
3177                                    tooltip: None,
3178                                    padding_left: None,
3179                                    padding_right: None,
3180                                    data: None,
3181                                }]))
3182                            }
3183                        },
3184                    );
3185                })),
3186                ..FakeLspAdapter::default()
3187            },
3188        );
3189
3190        let buffer = project
3191            .update(cx, |project, cx| {
3192                project.open_local_buffer(path!("/a/main.rs"), cx)
3193            })
3194            .await
3195            .unwrap();
3196        let editor =
3197            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3198
3199        // Allow LSP to initialize
3200        cx.executor().run_until_parked();
3201
3202        // Establish a viewport and explicitly trigger hint refresh.
3203        // This ensures we control exactly when hints are requested.
3204        editor
3205            .update(cx, |editor, window, cx| {
3206                editor.set_visible_line_count(50.0, window, cx);
3207                editor.set_visible_column_count(120.0);
3208                editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
3209            })
3210            .unwrap();
3211
3212        // Allow LSP initialization and hint request/response to complete.
3213        // Use multiple advance_clock + run_until_parked cycles to ensure all async work completes.
3214        for _ in 0..5 {
3215            cx.executor().advance_clock(Duration::from_millis(100));
3216            cx.executor().run_until_parked();
3217        }
3218
3219        // At this point we should have exactly one hint from our explicit refresh.
3220        // The test verifies that hints at character boundaries are handled correctly.
3221        editor
3222            .update(cx, |editor, _, cx| {
3223                assert!(
3224                    !cached_hint_labels(editor, cx).is_empty(),
3225                    "Should have at least one hint after refresh"
3226                );
3227                assert!(
3228                    !visible_hint_labels(editor, cx).is_empty(),
3229                    "Should have at least one visible hint"
3230                );
3231            })
3232            .unwrap();
3233    }
3234
3235    #[gpui::test]
3236    async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) {
3237        init_test(cx, &|settings| {
3238            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
3239                show_value_hints: Some(true),
3240                enabled: Some(false),
3241                edit_debounce_ms: Some(0),
3242                scroll_debounce_ms: Some(0),
3243                show_type_hints: Some(true),
3244                show_parameter_hints: Some(true),
3245                show_other_hints: Some(true),
3246                show_background: Some(false),
3247                toggle_on_modifiers_press: None,
3248            })
3249        });
3250
3251        let (_, editor, _fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
3252            let lsp_request_count = Arc::new(AtomicU32::new(0));
3253            fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3254                move |params, _| {
3255                    let lsp_request_count = lsp_request_count.clone();
3256                    async move {
3257                        assert_eq!(
3258                            params.text_document.uri,
3259                            lsp::Uri::from_file_path(file_with_hints).unwrap(),
3260                        );
3261
3262                        let i = lsp_request_count.fetch_add(1, Ordering::AcqRel) + 1;
3263                        Ok(Some(vec![lsp::InlayHint {
3264                            position: lsp::Position::new(0, i),
3265                            label: lsp::InlayHintLabel::String(i.to_string()),
3266                            kind: None,
3267                            text_edits: None,
3268                            tooltip: None,
3269                            padding_left: None,
3270                            padding_right: None,
3271                            data: None,
3272                        }]))
3273                    }
3274                },
3275            );
3276        })
3277        .await;
3278
3279        editor
3280            .update(cx, |editor, window, cx| {
3281                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3282            })
3283            .unwrap();
3284
3285        cx.executor().run_until_parked();
3286        editor
3287            .update(cx, |editor, _, cx| {
3288                let expected_hints = vec!["1".to_string()];
3289                assert_eq!(
3290                    expected_hints,
3291                    cached_hint_labels(editor, cx),
3292                    "Should display inlays after toggle despite them disabled in settings"
3293                );
3294                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3295            })
3296            .unwrap();
3297
3298        editor
3299            .update(cx, |editor, window, cx| {
3300                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3301            })
3302            .unwrap();
3303        cx.executor().run_until_parked();
3304        editor
3305            .update(cx, |editor, _, cx| {
3306                assert_eq!(
3307                    vec!["1".to_string()],
3308                    cached_hint_labels(editor, cx),
3309                    "Cache does not change because of toggles in the editor"
3310                );
3311                assert_eq!(
3312                    Vec::<String>::new(),
3313                    visible_hint_labels(editor, cx),
3314                    "Should clear hints after 2nd toggle"
3315                );
3316            })
3317            .unwrap();
3318
3319        update_test_language_settings(cx, &|settings| {
3320            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
3321                show_value_hints: Some(true),
3322                enabled: Some(true),
3323                edit_debounce_ms: Some(0),
3324                scroll_debounce_ms: Some(0),
3325                show_type_hints: Some(true),
3326                show_parameter_hints: Some(true),
3327                show_other_hints: Some(true),
3328                show_background: Some(false),
3329                toggle_on_modifiers_press: None,
3330            })
3331        });
3332        cx.executor().run_until_parked();
3333        editor
3334            .update(cx, |editor, _, cx| {
3335                let expected_hints = vec!["1".to_string()];
3336                assert_eq!(
3337                    expected_hints,
3338                    cached_hint_labels(editor, cx),
3339                    "Should not query LSP hints after enabling hints in settings, as file version is the same"
3340                );
3341                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3342            })
3343            .unwrap();
3344
3345        editor
3346            .update(cx, |editor, window, cx| {
3347                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3348            })
3349            .unwrap();
3350        cx.executor().run_until_parked();
3351        editor
3352            .update(cx, |editor, _, cx| {
3353                assert_eq!(
3354                    vec!["1".to_string()],
3355                    cached_hint_labels(editor, cx),
3356                    "Cache does not change because of toggles in the editor"
3357                );
3358                assert_eq!(
3359                    Vec::<String>::new(),
3360                    visible_hint_labels(editor, cx),
3361                    "Should clear hints after enabling in settings and a 3rd toggle"
3362                );
3363            })
3364            .unwrap();
3365
3366        editor
3367            .update(cx, |editor, window, cx| {
3368                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3369            })
3370            .unwrap();
3371        cx.executor().run_until_parked();
3372        editor.update(cx, |editor, _, cx| {
3373            let expected_hints = vec!["1".to_string()];
3374            assert_eq!(
3375                expected_hints,
3376                cached_hint_labels(editor,cx),
3377                "Should not query LSP hints after enabling hints in settings and toggling them back on"
3378            );
3379            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3380        }).unwrap();
3381    }
3382
3383    #[gpui::test]
3384    async fn test_modifiers_change(cx: &mut gpui::TestAppContext) {
3385        init_test(cx, &|settings| {
3386            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
3387                show_value_hints: Some(true),
3388                enabled: Some(true),
3389                edit_debounce_ms: Some(0),
3390                scroll_debounce_ms: Some(0),
3391                show_type_hints: Some(true),
3392                show_parameter_hints: Some(true),
3393                show_other_hints: Some(true),
3394                show_background: Some(false),
3395                toggle_on_modifiers_press: None,
3396            })
3397        });
3398
3399        let (_, editor, _fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
3400            let lsp_request_count = Arc::new(AtomicU32::new(0));
3401            fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3402                move |params, _| {
3403                    let lsp_request_count = lsp_request_count.clone();
3404                    async move {
3405                        assert_eq!(
3406                            params.text_document.uri,
3407                            lsp::Uri::from_file_path(file_with_hints).unwrap(),
3408                        );
3409
3410                        let i = lsp_request_count.fetch_add(1, Ordering::AcqRel) + 1;
3411                        Ok(Some(vec![lsp::InlayHint {
3412                            position: lsp::Position::new(0, i),
3413                            label: lsp::InlayHintLabel::String(i.to_string()),
3414                            kind: None,
3415                            text_edits: None,
3416                            tooltip: None,
3417                            padding_left: None,
3418                            padding_right: None,
3419                            data: None,
3420                        }]))
3421                    }
3422                },
3423            );
3424        })
3425        .await;
3426
3427        cx.executor().run_until_parked();
3428        editor
3429            .update(cx, |editor, _, cx| {
3430                assert_eq!(
3431                    vec!["1".to_string()],
3432                    cached_hint_labels(editor, cx),
3433                    "Should display inlays after toggle despite them disabled in settings"
3434                );
3435                assert_eq!(vec!["1".to_string()], visible_hint_labels(editor, cx));
3436            })
3437            .unwrap();
3438
3439        editor
3440            .update(cx, |editor, _, cx| {
3441                editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(true), cx);
3442            })
3443            .unwrap();
3444        cx.executor().run_until_parked();
3445        editor
3446            .update(cx, |editor, _, cx| {
3447                assert_eq!(
3448                    vec!["1".to_string()],
3449                    cached_hint_labels(editor, cx),
3450                    "Nothing happens with the cache on modifiers change"
3451                );
3452                assert_eq!(
3453                    Vec::<String>::new(),
3454                    visible_hint_labels(editor, cx),
3455                    "On modifiers change and hints toggled on, should hide editor inlays"
3456                );
3457            })
3458            .unwrap();
3459        editor
3460            .update(cx, |editor, _, cx| {
3461                editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(true), cx);
3462            })
3463            .unwrap();
3464        cx.executor().run_until_parked();
3465        editor
3466            .update(cx, |editor, _, cx| {
3467                assert_eq!(vec!["1".to_string()], cached_hint_labels(editor, cx));
3468                assert_eq!(
3469                    Vec::<String>::new(),
3470                    visible_hint_labels(editor, cx),
3471                    "Nothing changes on consequent modifiers change of the same kind"
3472                );
3473            })
3474            .unwrap();
3475
3476        editor
3477            .update(cx, |editor, _, cx| {
3478                editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
3479            })
3480            .unwrap();
3481        cx.executor().run_until_parked();
3482        editor
3483            .update(cx, |editor, _, cx| {
3484                assert_eq!(
3485                    vec!["1".to_string()],
3486                    cached_hint_labels(editor, cx),
3487                    "When modifiers change is off, no extra requests are sent"
3488                );
3489                assert_eq!(
3490                    vec!["1".to_string()],
3491                    visible_hint_labels(editor, cx),
3492                    "When modifiers change is off, hints are back into the editor"
3493                );
3494            })
3495            .unwrap();
3496        editor
3497            .update(cx, |editor, _, cx| {
3498                editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
3499            })
3500            .unwrap();
3501        cx.executor().run_until_parked();
3502        editor
3503            .update(cx, |editor, _, cx| {
3504                assert_eq!(vec!["1".to_string()], cached_hint_labels(editor, cx));
3505                assert_eq!(
3506                    vec!["1".to_string()],
3507                    visible_hint_labels(editor, cx),
3508                    "Nothing changes on consequent modifiers change of the same kind (2)"
3509                );
3510            })
3511            .unwrap();
3512
3513        editor
3514            .update(cx, |editor, window, cx| {
3515                editor.toggle_inlay_hints(&crate::ToggleInlayHints, window, cx)
3516            })
3517            .unwrap();
3518        cx.executor().run_until_parked();
3519        editor
3520            .update(cx, |editor, _, cx| {
3521                assert_eq!(
3522                    vec!["1".to_string()],
3523                    cached_hint_labels(editor, cx),
3524                    "Nothing happens with the cache on modifiers change"
3525                );
3526                assert_eq!(
3527                    Vec::<String>::new(),
3528                    visible_hint_labels(editor, cx),
3529                    "When toggled off, should hide editor inlays"
3530                );
3531            })
3532            .unwrap();
3533
3534        editor
3535            .update(cx, |editor, _, cx| {
3536                editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(true), cx);
3537            })
3538            .unwrap();
3539        cx.executor().run_until_parked();
3540        editor
3541            .update(cx, |editor, _, cx| {
3542                assert_eq!(
3543                    vec!["1".to_string()],
3544                    cached_hint_labels(editor, cx),
3545                    "Nothing happens with the cache on modifiers change"
3546                );
3547                assert_eq!(
3548                    vec!["1".to_string()],
3549                    visible_hint_labels(editor, cx),
3550                    "On modifiers change & hints toggled off, should show editor inlays"
3551                );
3552            })
3553            .unwrap();
3554        editor
3555            .update(cx, |editor, _, cx| {
3556                editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(true), cx);
3557            })
3558            .unwrap();
3559        cx.executor().run_until_parked();
3560        editor
3561            .update(cx, |editor, _, cx| {
3562                assert_eq!(vec!["1".to_string()], cached_hint_labels(editor, cx));
3563                assert_eq!(
3564                    vec!["1".to_string()],
3565                    visible_hint_labels(editor, cx),
3566                    "Nothing changes on consequent modifiers change of the same kind"
3567                );
3568            })
3569            .unwrap();
3570
3571        editor
3572            .update(cx, |editor, _, cx| {
3573                editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
3574            })
3575            .unwrap();
3576        cx.executor().run_until_parked();
3577        editor
3578            .update(cx, |editor, _, cx| {
3579                assert_eq!(
3580                    vec!["1".to_string()],
3581                    cached_hint_labels(editor, cx),
3582                    "When modifiers change is off, no extra requests are sent"
3583                );
3584                assert_eq!(
3585                    Vec::<String>::new(),
3586                    visible_hint_labels(editor, cx),
3587                    "When modifiers change is off, editor hints are back into their toggled off state"
3588                );
3589            })
3590            .unwrap();
3591        editor
3592            .update(cx, |editor, _, cx| {
3593                editor.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
3594            })
3595            .unwrap();
3596        cx.executor().run_until_parked();
3597        editor
3598            .update(cx, |editor, _, cx| {
3599                assert_eq!(vec!["1".to_string()], cached_hint_labels(editor, cx));
3600                assert_eq!(
3601                    Vec::<String>::new(),
3602                    visible_hint_labels(editor, cx),
3603                    "Nothing changes on consequent modifiers change of the same kind (3)"
3604                );
3605            })
3606            .unwrap();
3607    }
3608
3609    #[gpui::test]
3610    async fn test_inlays_at_the_same_place(cx: &mut gpui::TestAppContext) {
3611        init_test(cx, &|settings| {
3612            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
3613                show_value_hints: Some(true),
3614                enabled: Some(true),
3615                edit_debounce_ms: Some(0),
3616                scroll_debounce_ms: Some(0),
3617                show_type_hints: Some(true),
3618                show_parameter_hints: Some(true),
3619                show_other_hints: Some(true),
3620                show_background: Some(false),
3621                toggle_on_modifiers_press: None,
3622            })
3623        });
3624
3625        let fs = FakeFs::new(cx.background_executor.clone());
3626        fs.insert_tree(
3627            path!("/a"),
3628            json!({
3629                "main.rs": "fn main() {
3630                    let x = 42;
3631                    std::thread::scope(|s| {
3632                        s.spawn(|| {
3633                            let _x = x;
3634                        });
3635                    });
3636                }",
3637                "other.rs": "// Test file",
3638            }),
3639        )
3640        .await;
3641
3642        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3643
3644        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3645        language_registry.add(rust_lang());
3646        language_registry.register_fake_lsp(
3647            "Rust",
3648            FakeLspAdapter {
3649                capabilities: lsp::ServerCapabilities {
3650                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3651                    ..Default::default()
3652                },
3653                initializer: Some(Box::new(move |fake_server| {
3654                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3655                        move |params, _| async move {
3656                            assert_eq!(
3657                                params.text_document.uri,
3658                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
3659                            );
3660                            Ok(Some(
3661                                serde_json::from_value(json!([
3662                                    {
3663                                        "position": {
3664                                            "line": 3,
3665                                            "character": 16
3666                                        },
3667                                        "label": "move",
3668                                        "paddingLeft": false,
3669                                        "paddingRight": false
3670                                    },
3671                                    {
3672                                        "position": {
3673                                            "line": 3,
3674                                            "character": 16
3675                                        },
3676                                        "label": "(",
3677                                        "paddingLeft": false,
3678                                        "paddingRight": false
3679                                    },
3680                                    {
3681                                        "position": {
3682                                            "line": 3,
3683                                            "character": 16
3684                                        },
3685                                        "label": [
3686                                            {
3687                                                "value": "&x"
3688                                            }
3689                                        ],
3690                                        "paddingLeft": false,
3691                                        "paddingRight": false,
3692                                        "data": {
3693                                            "file_id": 0
3694                                        }
3695                                    },
3696                                    {
3697                                        "position": {
3698                                            "line": 3,
3699                                            "character": 16
3700                                        },
3701                                        "label": ")",
3702                                        "paddingLeft": false,
3703                                        "paddingRight": true
3704                                    },
3705                                    // not a correct syntax, but checks that same symbols at the same place
3706                                    // are not deduplicated
3707                                    {
3708                                        "position": {
3709                                            "line": 3,
3710                                            "character": 16
3711                                        },
3712                                        "label": ")",
3713                                        "paddingLeft": false,
3714                                        "paddingRight": true
3715                                    },
3716                                ]))
3717                                .unwrap(),
3718                            ))
3719                        },
3720                    );
3721                })),
3722                ..FakeLspAdapter::default()
3723            },
3724        );
3725
3726        let buffer = project
3727            .update(cx, |project, cx| {
3728                project.open_local_buffer(path!("/a/main.rs"), cx)
3729            })
3730            .await
3731            .unwrap();
3732
3733        // Use a VisualTestContext and explicitly establish a viewport on the editor (the production
3734        // trigger for `NewLinesShown` / inlay hint refresh) by setting visible line/column counts.
3735        let (editor_entity, cx) =
3736            cx.add_window_view(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
3737
3738        editor_entity.update_in(cx, |editor, window, cx| {
3739            // Establish a viewport. The exact values are not important for this test; we just need
3740            // the editor to consider itself visible so the refresh pipeline runs.
3741            editor.set_visible_line_count(50.0, window, cx);
3742            editor.set_visible_column_count(120.0);
3743
3744            // Explicitly trigger a refresh now that the viewport exists.
3745            editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
3746        });
3747        cx.executor().run_until_parked();
3748
3749        editor_entity.update_in(cx, |editor, window, cx| {
3750            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3751                s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3752            });
3753        });
3754        cx.executor().run_until_parked();
3755
3756        // Allow any async inlay hint request/response work to complete.
3757        cx.executor().advance_clock(Duration::from_millis(100));
3758        cx.executor().run_until_parked();
3759
3760        editor_entity.update(cx, |editor, cx| {
3761            let expected_hints = vec![
3762                "move".to_string(),
3763                "(".to_string(),
3764                "&x".to_string(),
3765                ") ".to_string(),
3766                ") ".to_string(),
3767            ];
3768            assert_eq!(
3769                expected_hints,
3770                cached_hint_labels(editor, cx),
3771                "Editor inlay hints should repeat server's order when placed at the same spot"
3772            );
3773            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3774        });
3775    }
3776
3777    #[gpui::test]
3778    async fn test_invalidation_and_addition_race(cx: &mut gpui::TestAppContext) {
3779        init_test(cx, &|settings| {
3780            settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
3781                enabled: Some(true),
3782                ..InlayHintSettingsContent::default()
3783            })
3784        });
3785
3786        let fs = FakeFs::new(cx.background_executor.clone());
3787        fs.insert_tree(
3788            path!("/a"),
3789            json!({
3790                "main.rs": r#"fn main() {
3791                    let x = 1;
3792                    ////
3793                    ////
3794                    ////
3795                    ////
3796                    ////
3797                    ////
3798                    ////
3799                    ////
3800                    ////
3801                    ////
3802                    ////
3803                    ////
3804                    ////
3805                    ////
3806                    ////
3807                    ////
3808                    ////
3809                    let x = "2";
3810                }
3811"#,
3812                "lib.rs": r#"fn aaa() {
3813                    let aa = 22;
3814                }
3815                //
3816                //
3817                //
3818                //
3819                //
3820                //
3821                //
3822                //
3823                //
3824                //
3825                //
3826                //
3827                //
3828                //
3829                //
3830                //
3831                //
3832                //
3833                //
3834                //
3835                //
3836                //
3837                //
3838                //
3839
3840                fn bb() {
3841                    let bb = 33;
3842                }
3843"#
3844            }),
3845        )
3846        .await;
3847
3848        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
3849        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
3850        let language = rust_lang();
3851        language_registry.add(language);
3852
3853        let requests_count = Arc::new(AtomicUsize::new(0));
3854        let closure_requests_count = requests_count.clone();
3855        let mut fake_servers = language_registry.register_fake_lsp(
3856            "Rust",
3857            FakeLspAdapter {
3858                name: "rust-analyzer",
3859                capabilities: lsp::ServerCapabilities {
3860                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3861                    ..lsp::ServerCapabilities::default()
3862                },
3863                initializer: Some(Box::new(move |fake_server| {
3864                    let requests_count = closure_requests_count.clone();
3865                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3866                        move |params, _| {
3867                            let requests_count = requests_count.clone();
3868                            async move {
3869                                requests_count.fetch_add(1, Ordering::Release);
3870                                if params.text_document.uri
3871                                    == lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
3872                                {
3873                                    Ok(Some(vec![
3874                                        lsp::InlayHint {
3875                                            position: lsp::Position::new(1, 9),
3876                                            label: lsp::InlayHintLabel::String(": i32".to_owned()),
3877                                            kind: Some(lsp::InlayHintKind::TYPE),
3878                                            text_edits: None,
3879                                            tooltip: None,
3880                                            padding_left: None,
3881                                            padding_right: None,
3882                                            data: None,
3883                                        },
3884                                        lsp::InlayHint {
3885                                            position: lsp::Position::new(19, 9),
3886                                            label: lsp::InlayHintLabel::String(": i33".to_owned()),
3887                                            kind: Some(lsp::InlayHintKind::TYPE),
3888                                            text_edits: None,
3889                                            tooltip: None,
3890                                            padding_left: None,
3891                                            padding_right: None,
3892                                            data: None,
3893                                        },
3894                                    ]))
3895                                } else if params.text_document.uri
3896                                    == lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
3897                                {
3898                                    Ok(Some(vec![
3899                                        lsp::InlayHint {
3900                                            position: lsp::Position::new(1, 10),
3901                                            label: lsp::InlayHintLabel::String(": i34".to_owned()),
3902                                            kind: Some(lsp::InlayHintKind::TYPE),
3903                                            text_edits: None,
3904                                            tooltip: None,
3905                                            padding_left: None,
3906                                            padding_right: None,
3907                                            data: None,
3908                                        },
3909                                        lsp::InlayHint {
3910                                            position: lsp::Position::new(29, 10),
3911                                            label: lsp::InlayHintLabel::String(": i35".to_owned()),
3912                                            kind: Some(lsp::InlayHintKind::TYPE),
3913                                            text_edits: None,
3914                                            tooltip: None,
3915                                            padding_left: None,
3916                                            padding_right: None,
3917                                            data: None,
3918                                        },
3919                                    ]))
3920                                } else {
3921                                    panic!("Unexpected file path {:?}", params.text_document.uri);
3922                                }
3923                            }
3924                        },
3925                    );
3926                })),
3927                ..FakeLspAdapter::default()
3928            },
3929        );
3930
3931        // Add another server that does send the same, duplicate hints back
3932        let mut fake_servers_2 = language_registry.register_fake_lsp(
3933            "Rust",
3934            FakeLspAdapter {
3935                name: "CrabLang-ls",
3936                capabilities: lsp::ServerCapabilities {
3937                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3938                    ..lsp::ServerCapabilities::default()
3939                },
3940                initializer: Some(Box::new(move |fake_server| {
3941                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
3942                        move |params, _| async move {
3943                            if params.text_document.uri
3944                                == lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap()
3945                            {
3946                                Ok(Some(vec![
3947                                    lsp::InlayHint {
3948                                        position: lsp::Position::new(1, 9),
3949                                        label: lsp::InlayHintLabel::String(": i32".to_owned()),
3950                                        kind: Some(lsp::InlayHintKind::TYPE),
3951                                        text_edits: None,
3952                                        tooltip: None,
3953                                        padding_left: None,
3954                                        padding_right: None,
3955                                        data: None,
3956                                    },
3957                                    lsp::InlayHint {
3958                                        position: lsp::Position::new(19, 9),
3959                                        label: lsp::InlayHintLabel::String(": i33".to_owned()),
3960                                        kind: Some(lsp::InlayHintKind::TYPE),
3961                                        text_edits: None,
3962                                        tooltip: None,
3963                                        padding_left: None,
3964                                        padding_right: None,
3965                                        data: None,
3966                                    },
3967                                ]))
3968                            } else if params.text_document.uri
3969                                == lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap()
3970                            {
3971                                Ok(Some(vec![
3972                                    lsp::InlayHint {
3973                                        position: lsp::Position::new(1, 10),
3974                                        label: lsp::InlayHintLabel::String(": i34".to_owned()),
3975                                        kind: Some(lsp::InlayHintKind::TYPE),
3976                                        text_edits: None,
3977                                        tooltip: None,
3978                                        padding_left: None,
3979                                        padding_right: None,
3980                                        data: None,
3981                                    },
3982                                    lsp::InlayHint {
3983                                        position: lsp::Position::new(29, 10),
3984                                        label: lsp::InlayHintLabel::String(": i35".to_owned()),
3985                                        kind: Some(lsp::InlayHintKind::TYPE),
3986                                        text_edits: None,
3987                                        tooltip: None,
3988                                        padding_left: None,
3989                                        padding_right: None,
3990                                        data: None,
3991                                    },
3992                                ]))
3993                            } else {
3994                                panic!("Unexpected file path {:?}", params.text_document.uri);
3995                            }
3996                        },
3997                    );
3998                })),
3999                ..FakeLspAdapter::default()
4000            },
4001        );
4002
4003        let (buffer_1, _handle_1) = project
4004            .update(cx, |project, cx| {
4005                project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
4006            })
4007            .await
4008            .unwrap();
4009        let (buffer_2, _handle_2) = project
4010            .update(cx, |project, cx| {
4011                project.open_local_buffer_with_lsp(path!("/a/lib.rs"), cx)
4012            })
4013            .await
4014            .unwrap();
4015        let multi_buffer = cx.new(|cx| {
4016            let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
4017            multibuffer.set_excerpts_for_path(
4018                PathKey::sorted(0),
4019                buffer_2.clone(),
4020                [
4021                    Point::new(0, 0)..Point::new(10, 0),
4022                    Point::new(23, 0)..Point::new(34, 0),
4023                ],
4024                0,
4025                cx,
4026            );
4027            multibuffer.set_excerpts_for_path(
4028                PathKey::sorted(1),
4029                buffer_1.clone(),
4030                [
4031                    Point::new(0, 0)..Point::new(10, 0),
4032                    Point::new(13, 0)..Point::new(23, 0),
4033                ],
4034                0,
4035                cx,
4036            );
4037            multibuffer
4038        });
4039
4040        let editor = cx.add_window(|window, cx| {
4041            let mut editor =
4042                Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx);
4043            editor.change_selections(SelectionEffects::default(), window, cx, |s| {
4044                s.select_ranges([Point::new(3, 3)..Point::new(3, 3)])
4045            });
4046            editor
4047        });
4048
4049        let fake_server = fake_servers.next().await.unwrap();
4050        let _fake_server_2 = fake_servers_2.next().await.unwrap();
4051        cx.executor().advance_clock(Duration::from_millis(100));
4052        cx.executor().run_until_parked();
4053
4054        editor
4055            .update(cx, |editor, _window, cx| {
4056                assert_eq!(
4057                    vec![
4058                        ": i32".to_string(),
4059                        ": i32".to_string(),
4060                        ": i33".to_string(),
4061                        ": i33".to_string(),
4062                        ": i34".to_string(),
4063                        ": i34".to_string(),
4064                        ": i35".to_string(),
4065                        ": i35".to_string(),
4066                    ],
4067                    sorted_cached_hint_labels(editor, cx),
4068                    "We receive duplicate hints from 2 servers and cache them all"
4069                );
4070                assert_eq!(
4071                    vec![
4072                        ": i34".to_string(),
4073                        ": i35".to_string(),
4074                        ": i32".to_string(),
4075                        ": i33".to_string(),
4076                    ],
4077                    visible_hint_labels(editor, cx),
4078                    "lib.rs is added before main.rs , so its excerpts should be visible first; hints should be deduplicated per label"
4079                );
4080            })
4081            .unwrap();
4082        assert_eq!(
4083            requests_count.load(Ordering::Acquire),
4084            2,
4085            "Should have queried hints once per each file"
4086        );
4087
4088        // Scroll all the way down so the 1st buffer is out of sight.
4089        // The selection is on the 1st buffer still.
4090        editor
4091            .update(cx, |editor, window, cx| {
4092                editor.scroll_screen(&ScrollAmount::Line(88.0), window, cx);
4093            })
4094            .unwrap();
4095        // Emulate a language server refresh request, coming in the background..
4096        editor
4097            .update(cx, |editor, _, cx| {
4098                editor.refresh_inlay_hints(
4099                    InlayHintRefreshReason::RefreshRequested {
4100                        server_id: fake_server.server.server_id(),
4101                        request_id: Some(1),
4102                    },
4103                    cx,
4104                );
4105            })
4106            .unwrap();
4107        // Edit the 1st buffer while scrolled down and not seeing that.
4108        // The edit will auto scroll to the edit (1st buffer).
4109        editor
4110            .update(cx, |editor, window, cx| {
4111                editor.handle_input("a", window, cx);
4112            })
4113            .unwrap();
4114        // Add more racy additive hint tasks.
4115        editor
4116            .update(cx, |editor, window, cx| {
4117                editor.scroll_screen(&ScrollAmount::Line(0.2), window, cx);
4118            })
4119            .unwrap();
4120
4121        cx.executor().advance_clock(Duration::from_millis(1000));
4122        cx.executor().run_until_parked();
4123        editor
4124            .update(cx, |editor, _window, cx| {
4125                assert_eq!(
4126                    vec![
4127                        ": i32".to_string(),
4128                        ": i32".to_string(),
4129                        ": i33".to_string(),
4130                        ": i33".to_string(),
4131                        ": i34".to_string(),
4132                        ": i34".to_string(),
4133                        ": i35".to_string(),
4134                        ": i35".to_string(),
4135                    ],
4136                    sorted_cached_hint_labels(editor, cx),
4137                    "No hint changes/duplicates should occur in the cache",
4138                );
4139                assert_eq!(
4140                    vec![
4141                        ": i34".to_string(),
4142                        ": i35".to_string(),
4143                        ": i32".to_string(),
4144                        ": i33".to_string(),
4145                    ],
4146                    visible_hint_labels(editor, cx),
4147                    "No hint changes/duplicates should occur in the editor excerpts",
4148                );
4149            })
4150            .unwrap();
4151        assert_eq!(
4152            requests_count.load(Ordering::Acquire),
4153            4,
4154            "Should have queried hints once more per each file, after editing the file once"
4155        );
4156    }
4157
4158    pub(crate) fn init_test(cx: &mut TestAppContext, f: &dyn Fn(&mut AllLanguageSettingsContent)) {
4159        cx.update(|cx| {
4160            let settings_store = SettingsStore::test(cx);
4161            cx.set_global(settings_store);
4162            theme::init(theme::LoadThemes::JustBase, cx);
4163            release_channel::init(semver::Version::new(0, 0, 0), cx);
4164            crate::init(cx);
4165        });
4166
4167        update_test_language_settings(cx, f);
4168    }
4169
4170    async fn prepare_test_objects(
4171        cx: &mut TestAppContext,
4172        initialize: impl 'static + Send + Fn(&mut FakeLanguageServer, &'static str) + Send + Sync,
4173    ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
4174        let fs = FakeFs::new(cx.background_executor.clone());
4175        fs.insert_tree(
4176            path!("/a"),
4177            json!({
4178                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
4179                "other.rs": "// Test file",
4180            }),
4181        )
4182        .await;
4183
4184        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
4185        let file_path = path!("/a/main.rs");
4186
4187        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
4188        language_registry.add(rust_lang());
4189        let mut fake_servers = language_registry.register_fake_lsp(
4190            "Rust",
4191            FakeLspAdapter {
4192                capabilities: lsp::ServerCapabilities {
4193                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
4194                    ..lsp::ServerCapabilities::default()
4195                },
4196                initializer: Some(Box::new(move |server| initialize(server, file_path))),
4197                ..FakeLspAdapter::default()
4198            },
4199        );
4200
4201        let buffer = project
4202            .update(cx, |project, cx| {
4203                project.open_local_buffer(path!("/a/main.rs"), cx)
4204            })
4205            .await
4206            .unwrap();
4207        let editor =
4208            cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
4209
4210        editor
4211            .update(cx, |editor, _, cx| {
4212                assert!(cached_hint_labels(editor, cx).is_empty());
4213                assert!(visible_hint_labels(editor, cx).is_empty());
4214            })
4215            .unwrap();
4216
4217        cx.executor().run_until_parked();
4218        let fake_server = fake_servers.next().await.unwrap();
4219
4220        // Establish a viewport so the editor considers itself visible and the hint refresh
4221        // pipeline runs. Then explicitly trigger a refresh.
4222        editor
4223            .update(cx, |editor, window, cx| {
4224                editor.set_visible_line_count(50.0, window, cx);
4225                editor.set_visible_column_count(120.0);
4226                editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
4227            })
4228            .unwrap();
4229        cx.executor().run_until_parked();
4230        (file_path, editor, fake_server)
4231    }
4232
4233    // 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.
4234    // Ensure a stable order for testing.
4235    fn sorted_cached_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
4236        let mut labels = cached_hint_labels(editor, cx);
4237        labels.sort_by(|a, b| natural_sort(a, b));
4238        labels
4239    }
4240
4241    pub fn cached_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
4242        let lsp_store = editor.project().unwrap().read(cx).lsp_store();
4243
4244        let mut all_cached_labels = Vec::new();
4245        let mut all_fetched_hints = Vec::new();
4246        for buffer in editor.buffer.read(cx).all_buffers() {
4247            lsp_store.update(cx, |lsp_store, cx| {
4248                let hints = lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
4249                all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
4250                    let mut label = hint.text().to_string();
4251                    if hint.padding_left {
4252                        label.insert(0, ' ');
4253                    }
4254                    if hint.padding_right {
4255                        label.push_str(" ");
4256                    }
4257                    label
4258                }));
4259                all_fetched_hints.extend(hints.all_fetched_hints());
4260            });
4261        }
4262
4263        all_cached_labels
4264    }
4265
4266    pub fn visible_hint_labels(editor: &Editor, cx: &Context<Editor>) -> Vec<String> {
4267        editor
4268            .visible_inlay_hints(cx)
4269            .into_iter()
4270            .map(|hint| hint.text().to_string())
4271            .collect()
4272    }
4273
4274    fn allowed_hint_kinds_for_editor(editor: &Editor) -> HashSet<Option<InlayHintKind>> {
4275        editor
4276            .inlay_hints
4277            .as_ref()
4278            .unwrap()
4279            .allowed_hint_kinds
4280            .clone()
4281    }
4282}