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