inlay_hints.rs

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