inlay_hints.rs

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