inlay_hints.rs

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