inlay_hints.rs

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