inlay_hint_cache.rs

   1/// Stores and updates all data received from LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint">textDocument/inlayHint</a> requests.
   2/// Has nothing to do with other inlays, e.g. copilot suggestions — those are stored elsewhere.
   3/// On every update, cache may query for more inlay hints and update inlays on the screen.
   4///
   5/// Inlays stored on screen are in [`crate::display_map::inlay_map`] and this cache is the only way to update any inlay hint data in the visible hints in the inlay map.
   6/// For determining the update to the `inlay_map`, the cache requires a list of visible inlay hints — all other hints are not relevant and their separate updates are not influencing the cache work.
   7///
   8/// Due to the way the data is stored for both visible inlays and the cache, every inlay (and inlay hint) collection is editor-specific, so a single buffer may have multiple sets of inlays of open on different panes.
   9use std::{
  10    cmp,
  11    ops::{ControlFlow, Range},
  12    sync::Arc,
  13    time::Duration,
  14};
  15
  16use crate::{
  17    display_map::Inlay, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot,
  18};
  19use anyhow::Context;
  20use clock::Global;
  21use futures::future;
  22use gpui::{Model, ModelContext, Task, ViewContext};
  23use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
  24use parking_lot::RwLock;
  25use project::{InlayHint, ResolveState};
  26
  27use collections::{hash_map, HashMap, HashSet};
  28use language::language_settings::InlayHintSettings;
  29use smol::lock::Semaphore;
  30use sum_tree::Bias;
  31use text::{ToOffset, ToPoint};
  32use util::post_inc;
  33
  34pub struct InlayHintCache {
  35    hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
  36    allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
  37    version: usize,
  38    pub(super) enabled: bool,
  39    update_tasks: HashMap<ExcerptId, TasksForRanges>,
  40    lsp_request_limiter: Arc<Semaphore>,
  41}
  42
  43#[derive(Debug)]
  44struct TasksForRanges {
  45    tasks: Vec<Task<()>>,
  46    sorted_ranges: Vec<Range<language::Anchor>>,
  47}
  48
  49#[derive(Debug)]
  50struct CachedExcerptHints {
  51    version: usize,
  52    buffer_version: Global,
  53    buffer_id: u64,
  54    ordered_hints: Vec<InlayId>,
  55    hints_by_id: HashMap<InlayId, InlayHint>,
  56}
  57
  58/// A logic to apply when querying for new inlay hints and deciding what to do with the old entries in the cache in case of conflicts.
  59#[derive(Debug, Clone, Copy)]
  60pub(super) enum InvalidationStrategy {
  61    /// Hints reset is <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlayHint_refresh">requested</a> by the LSP server.
  62    /// Demands to re-query all inlay hints needed and invalidate all cached entries, but does not require instant update with invalidation.
  63    ///
  64    /// Despite nothing forbids language server from sending this request on every edit, it is expected to be sent only when certain internal server state update, invisible for the editor otherwise.
  65    RefreshRequested,
  66    /// Multibuffer excerpt(s) and/or singleton buffer(s) were edited at least on one place.
  67    /// Neither editor nor LSP is able to tell which open file hints' are not affected, so all of them have to be invalidated, re-queried and do that fast enough to avoid being slow, but also debounce to avoid loading hints on every fast keystroke sequence.
  68    BufferEdited,
  69    /// A new file got opened/new excerpt was added to a multibuffer/a [multi]buffer was scrolled to a new position.
  70    /// No invalidation should be done at all, all new hints are added to the cache.
  71    ///
  72    /// A special case is the settings change: in addition to LSP capabilities, Zed allows omitting certain hint kinds (defined by the corresponding LSP part: type/parameter/other).
  73    /// This does not lead to cache invalidation, but would require cache usage for determining which hints are not displayed and issuing an update to inlays on the screen.
  74    None,
  75}
  76
  77/// A splice to send into the `inlay_map` for updating the visible inlays on the screen.
  78/// "Visible" inlays may not be displayed in the buffer right away, but those are ready to be displayed on further buffer scroll, pane item activations, etc. right away without additional LSP queries or settings changes.
  79/// The data in the cache is never used directly for displaying inlays on the screen, to avoid races with updates from LSP queries and sync overhead.
  80/// Splice is picked to help avoid extra hint flickering and "jumps" on the screen.
  81pub(super) struct InlaySplice {
  82    pub to_remove: Vec<InlayId>,
  83    pub to_insert: Vec<Inlay>,
  84}
  85
  86#[derive(Debug)]
  87struct ExcerptHintsUpdate {
  88    excerpt_id: ExcerptId,
  89    remove_from_visible: Vec<InlayId>,
  90    remove_from_cache: HashSet<InlayId>,
  91    add_to_cache: Vec<InlayHint>,
  92}
  93
  94#[derive(Debug, Clone, Copy)]
  95struct ExcerptQuery {
  96    buffer_id: u64,
  97    excerpt_id: ExcerptId,
  98    cache_version: usize,
  99    invalidate: InvalidationStrategy,
 100    reason: &'static str,
 101}
 102
 103impl InvalidationStrategy {
 104    fn should_invalidate(&self) -> bool {
 105        matches!(
 106            self,
 107            InvalidationStrategy::RefreshRequested | InvalidationStrategy::BufferEdited
 108        )
 109    }
 110}
 111
 112impl TasksForRanges {
 113    fn new(query_ranges: QueryRanges, task: Task<()>) -> Self {
 114        let mut sorted_ranges = Vec::new();
 115        sorted_ranges.extend(query_ranges.before_visible);
 116        sorted_ranges.extend(query_ranges.visible);
 117        sorted_ranges.extend(query_ranges.after_visible);
 118        Self {
 119            tasks: vec![task],
 120            sorted_ranges,
 121        }
 122    }
 123
 124    fn update_cached_tasks(
 125        &mut self,
 126        buffer_snapshot: &BufferSnapshot,
 127        query_ranges: QueryRanges,
 128        invalidate: InvalidationStrategy,
 129        spawn_task: impl FnOnce(QueryRanges) -> Task<()>,
 130    ) {
 131        let query_ranges = if invalidate.should_invalidate() {
 132            self.tasks.clear();
 133            self.sorted_ranges.clear();
 134            query_ranges
 135        } else {
 136            let mut non_cached_query_ranges = query_ranges;
 137            non_cached_query_ranges.before_visible = non_cached_query_ranges
 138                .before_visible
 139                .into_iter()
 140                .flat_map(|query_range| {
 141                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
 142                })
 143                .collect();
 144            non_cached_query_ranges.visible = non_cached_query_ranges
 145                .visible
 146                .into_iter()
 147                .flat_map(|query_range| {
 148                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
 149                })
 150                .collect();
 151            non_cached_query_ranges.after_visible = non_cached_query_ranges
 152                .after_visible
 153                .into_iter()
 154                .flat_map(|query_range| {
 155                    self.remove_cached_ranges_from_query(buffer_snapshot, query_range)
 156                })
 157                .collect();
 158            non_cached_query_ranges
 159        };
 160
 161        if !query_ranges.is_empty() {
 162            self.tasks.push(spawn_task(query_ranges));
 163        }
 164    }
 165
 166    fn remove_cached_ranges_from_query(
 167        &mut self,
 168        buffer_snapshot: &BufferSnapshot,
 169        query_range: Range<language::Anchor>,
 170    ) -> Vec<Range<language::Anchor>> {
 171        let mut ranges_to_query = Vec::new();
 172        let mut latest_cached_range = None::<&mut Range<language::Anchor>>;
 173        for cached_range in self
 174            .sorted_ranges
 175            .iter_mut()
 176            .skip_while(|cached_range| {
 177                cached_range
 178                    .end
 179                    .cmp(&query_range.start, buffer_snapshot)
 180                    .is_lt()
 181            })
 182            .take_while(|cached_range| {
 183                cached_range
 184                    .start
 185                    .cmp(&query_range.end, buffer_snapshot)
 186                    .is_le()
 187            })
 188        {
 189            match latest_cached_range {
 190                Some(latest_cached_range) => {
 191                    if latest_cached_range.end.offset.saturating_add(1) < cached_range.start.offset
 192                    {
 193                        ranges_to_query.push(latest_cached_range.end..cached_range.start);
 194                        cached_range.start = latest_cached_range.end;
 195                    }
 196                }
 197                None => {
 198                    if query_range
 199                        .start
 200                        .cmp(&cached_range.start, buffer_snapshot)
 201                        .is_lt()
 202                    {
 203                        ranges_to_query.push(query_range.start..cached_range.start);
 204                        cached_range.start = query_range.start;
 205                    }
 206                }
 207            }
 208            latest_cached_range = Some(cached_range);
 209        }
 210
 211        match latest_cached_range {
 212            Some(latest_cached_range) => {
 213                if latest_cached_range.end.offset.saturating_add(1) < query_range.end.offset {
 214                    ranges_to_query.push(latest_cached_range.end..query_range.end);
 215                    latest_cached_range.end = query_range.end;
 216                }
 217            }
 218            None => {
 219                ranges_to_query.push(query_range.clone());
 220                self.sorted_ranges.push(query_range);
 221                self.sorted_ranges
 222                    .sort_by(|range_a, range_b| range_a.start.cmp(&range_b.start, buffer_snapshot));
 223            }
 224        }
 225
 226        ranges_to_query
 227    }
 228
 229    fn invalidate_range(&mut self, buffer: &BufferSnapshot, range: &Range<language::Anchor>) {
 230        self.sorted_ranges = self
 231            .sorted_ranges
 232            .drain(..)
 233            .filter_map(|mut cached_range| {
 234                if cached_range.start.cmp(&range.end, buffer).is_gt()
 235                    || cached_range.end.cmp(&range.start, buffer).is_lt()
 236                {
 237                    Some(vec![cached_range])
 238                } else if cached_range.start.cmp(&range.start, buffer).is_ge()
 239                    && cached_range.end.cmp(&range.end, buffer).is_le()
 240                {
 241                    None
 242                } else if range.start.cmp(&cached_range.start, buffer).is_ge()
 243                    && range.end.cmp(&cached_range.end, buffer).is_le()
 244                {
 245                    Some(vec![
 246                        cached_range.start..range.start,
 247                        range.end..cached_range.end,
 248                    ])
 249                } else if cached_range.start.cmp(&range.start, buffer).is_ge() {
 250                    cached_range.start = range.end;
 251                    Some(vec![cached_range])
 252                } else {
 253                    cached_range.end = range.start;
 254                    Some(vec![cached_range])
 255                }
 256            })
 257            .flatten()
 258            .collect();
 259    }
 260}
 261
 262impl InlayHintCache {
 263    pub(super) fn new(inlay_hint_settings: InlayHintSettings) -> Self {
 264        Self {
 265            allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
 266            enabled: inlay_hint_settings.enabled,
 267            hints: HashMap::default(),
 268            update_tasks: HashMap::default(),
 269            version: 0,
 270            lsp_request_limiter: Arc::new(Semaphore::new(MAX_CONCURRENT_LSP_REQUESTS)),
 271        }
 272    }
 273
 274    /// Checks inlay hint settings for enabled hint kinds and general enabled state.
 275    /// Generates corresponding inlay_map splice updates on settings changes.
 276    /// Does not update inlay hint cache state on disabling or inlay hint kinds change: only reenabling forces new LSP queries.
 277    pub(super) fn update_settings(
 278        &mut self,
 279        multi_buffer: &Model<MultiBuffer>,
 280        new_hint_settings: InlayHintSettings,
 281        visible_hints: Vec<Inlay>,
 282        cx: &mut ViewContext<Editor>,
 283    ) -> ControlFlow<Option<InlaySplice>> {
 284        let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
 285        match (self.enabled, new_hint_settings.enabled) {
 286            (false, false) => {
 287                self.allowed_hint_kinds = new_allowed_hint_kinds;
 288                ControlFlow::Break(None)
 289            }
 290            (true, true) => {
 291                if new_allowed_hint_kinds == self.allowed_hint_kinds {
 292                    ControlFlow::Break(None)
 293                } else {
 294                    let new_splice = self.new_allowed_hint_kinds_splice(
 295                        multi_buffer,
 296                        &visible_hints,
 297                        &new_allowed_hint_kinds,
 298                        cx,
 299                    );
 300                    if new_splice.is_some() {
 301                        self.version += 1;
 302                        self.allowed_hint_kinds = new_allowed_hint_kinds;
 303                    }
 304                    ControlFlow::Break(new_splice)
 305                }
 306            }
 307            (true, false) => {
 308                self.enabled = new_hint_settings.enabled;
 309                self.allowed_hint_kinds = new_allowed_hint_kinds;
 310                if self.hints.is_empty() {
 311                    ControlFlow::Break(None)
 312                } else {
 313                    self.clear();
 314                    ControlFlow::Break(Some(InlaySplice {
 315                        to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(),
 316                        to_insert: Vec::new(),
 317                    }))
 318                }
 319            }
 320            (false, true) => {
 321                self.enabled = new_hint_settings.enabled;
 322                self.allowed_hint_kinds = new_allowed_hint_kinds;
 323                ControlFlow::Continue(())
 324            }
 325        }
 326    }
 327
 328    /// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
 329    /// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
 330    /// followed by the delayed queries of the same range above and below the visible one.
 331    /// This way, concequent refresh invocations are less likely to trigger LSP queries for the invisible ranges.
 332    pub(super) fn spawn_hint_refresh(
 333        &mut self,
 334        reason: &'static str,
 335        excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
 336        invalidate: InvalidationStrategy,
 337        cx: &mut ViewContext<Editor>,
 338    ) -> Option<InlaySplice> {
 339        if !self.enabled {
 340            return None;
 341        }
 342
 343        let mut invalidated_hints = Vec::new();
 344        if invalidate.should_invalidate() {
 345            self.update_tasks
 346                .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
 347            self.hints.retain(|cached_excerpt, cached_hints| {
 348                let retain = excerpts_to_query.contains_key(cached_excerpt);
 349                if !retain {
 350                    invalidated_hints.extend(cached_hints.read().ordered_hints.iter().copied());
 351                }
 352                retain
 353            });
 354        }
 355        if excerpts_to_query.is_empty() && invalidated_hints.is_empty() {
 356            return None;
 357        }
 358
 359        let cache_version = self.version + 1;
 360        cx.spawn(|editor, mut cx| async move {
 361            editor
 362                .update(&mut cx, |editor, cx| {
 363                    spawn_new_update_tasks(
 364                        editor,
 365                        reason,
 366                        excerpts_to_query,
 367                        invalidate,
 368                        cache_version,
 369                        cx,
 370                    )
 371                })
 372                .ok();
 373        })
 374        .detach();
 375
 376        if invalidated_hints.is_empty() {
 377            None
 378        } else {
 379            Some(InlaySplice {
 380                to_remove: invalidated_hints,
 381                to_insert: Vec::new(),
 382            })
 383        }
 384    }
 385
 386    fn new_allowed_hint_kinds_splice(
 387        &self,
 388        multi_buffer: &Model<MultiBuffer>,
 389        visible_hints: &[Inlay],
 390        new_kinds: &HashSet<Option<InlayHintKind>>,
 391        cx: &mut ViewContext<Editor>,
 392    ) -> Option<InlaySplice> {
 393        let old_kinds = &self.allowed_hint_kinds;
 394        if new_kinds == old_kinds {
 395            return None;
 396        }
 397
 398        let mut to_remove = Vec::new();
 399        let mut to_insert = Vec::new();
 400        let mut shown_hints_to_remove = visible_hints.iter().fold(
 401            HashMap::<ExcerptId, Vec<(Anchor, InlayId)>>::default(),
 402            |mut current_hints, inlay| {
 403                current_hints
 404                    .entry(inlay.position.excerpt_id)
 405                    .or_default()
 406                    .push((inlay.position, inlay.id));
 407                current_hints
 408            },
 409        );
 410
 411        let multi_buffer = multi_buffer.read(cx);
 412        let multi_buffer_snapshot = multi_buffer.snapshot(cx);
 413
 414        for (excerpt_id, excerpt_cached_hints) in &self.hints {
 415            let shown_excerpt_hints_to_remove =
 416                shown_hints_to_remove.entry(*excerpt_id).or_default();
 417            let excerpt_cached_hints = excerpt_cached_hints.read();
 418            let mut excerpt_cache = excerpt_cached_hints.ordered_hints.iter().fuse().peekable();
 419            shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| {
 420                let Some(buffer) = shown_anchor
 421                    .buffer_id
 422                    .and_then(|buffer_id| multi_buffer.buffer(buffer_id))
 423                else {
 424                    return false;
 425                };
 426                let buffer_snapshot = buffer.read(cx).snapshot();
 427                loop {
 428                    match excerpt_cache.peek() {
 429                        Some(&cached_hint_id) => {
 430                            let cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id];
 431                            if cached_hint_id == shown_hint_id {
 432                                excerpt_cache.next();
 433                                return !new_kinds.contains(&cached_hint.kind);
 434                            }
 435
 436                            match cached_hint
 437                                .position
 438                                .cmp(&shown_anchor.text_anchor, &buffer_snapshot)
 439                            {
 440                                cmp::Ordering::Less | cmp::Ordering::Equal => {
 441                                    if !old_kinds.contains(&cached_hint.kind)
 442                                        && new_kinds.contains(&cached_hint.kind)
 443                                    {
 444                                        to_insert.push(Inlay::hint(
 445                                            cached_hint_id.id(),
 446                                            multi_buffer_snapshot.anchor_in_excerpt(
 447                                                *excerpt_id,
 448                                                cached_hint.position,
 449                                            ),
 450                                            &cached_hint,
 451                                        ));
 452                                    }
 453                                    excerpt_cache.next();
 454                                }
 455                                cmp::Ordering::Greater => return true,
 456                            }
 457                        }
 458                        None => return true,
 459                    }
 460                }
 461            });
 462
 463            for cached_hint_id in excerpt_cache {
 464                let maybe_missed_cached_hint = &excerpt_cached_hints.hints_by_id[cached_hint_id];
 465                let cached_hint_kind = maybe_missed_cached_hint.kind;
 466                if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) {
 467                    to_insert.push(Inlay::hint(
 468                        cached_hint_id.id(),
 469                        multi_buffer_snapshot
 470                            .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position),
 471                        &maybe_missed_cached_hint,
 472                    ));
 473                }
 474            }
 475        }
 476
 477        to_remove.extend(
 478            shown_hints_to_remove
 479                .into_values()
 480                .flatten()
 481                .map(|(_, hint_id)| hint_id),
 482        );
 483        if to_remove.is_empty() && to_insert.is_empty() {
 484            None
 485        } else {
 486            Some(InlaySplice {
 487                to_remove,
 488                to_insert,
 489            })
 490        }
 491    }
 492
 493    /// Completely forget of certain excerpts that were removed from the multibuffer.
 494    pub(super) fn remove_excerpts(
 495        &mut self,
 496        excerpts_removed: Vec<ExcerptId>,
 497    ) -> Option<InlaySplice> {
 498        let mut to_remove = Vec::new();
 499        for excerpt_to_remove in excerpts_removed {
 500            self.update_tasks.remove(&excerpt_to_remove);
 501            if let Some(cached_hints) = self.hints.remove(&excerpt_to_remove) {
 502                let cached_hints = cached_hints.read();
 503                to_remove.extend(cached_hints.ordered_hints.iter().copied());
 504            }
 505        }
 506        if to_remove.is_empty() {
 507            None
 508        } else {
 509            self.version += 1;
 510            Some(InlaySplice {
 511                to_remove,
 512                to_insert: Vec::new(),
 513            })
 514        }
 515    }
 516
 517    pub(super) fn clear(&mut self) {
 518        if !self.update_tasks.is_empty() || !self.hints.is_empty() {
 519            self.version += 1;
 520        }
 521        self.update_tasks.clear();
 522        self.hints.clear();
 523    }
 524
 525    pub(super) fn hint_by_id(&self, excerpt_id: ExcerptId, hint_id: InlayId) -> Option<InlayHint> {
 526        self.hints
 527            .get(&excerpt_id)?
 528            .read()
 529            .hints_by_id
 530            .get(&hint_id)
 531            .cloned()
 532    }
 533
 534    pub fn hints(&self) -> Vec<InlayHint> {
 535        let mut hints = Vec::new();
 536        for excerpt_hints in self.hints.values() {
 537            let excerpt_hints = excerpt_hints.read();
 538            hints.extend(
 539                excerpt_hints
 540                    .ordered_hints
 541                    .iter()
 542                    .map(|id| &excerpt_hints.hints_by_id[id])
 543                    .cloned(),
 544            );
 545        }
 546        hints
 547    }
 548
 549    pub fn version(&self) -> usize {
 550        self.version
 551    }
 552
 553    /// Queries a certain hint from the cache for extra data via the LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHint_resolve">resolve</a> request.
 554    pub(super) fn spawn_hint_resolve(
 555        &self,
 556        buffer_id: u64,
 557        excerpt_id: ExcerptId,
 558        id: InlayId,
 559        cx: &mut ViewContext<'_, Editor>,
 560    ) {
 561        if let Some(excerpt_hints) = self.hints.get(&excerpt_id) {
 562            let mut guard = excerpt_hints.write();
 563            if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
 564                if let ResolveState::CanResolve(server_id, _) = &cached_hint.resolve_state {
 565                    let hint_to_resolve = cached_hint.clone();
 566                    let server_id = *server_id;
 567                    cached_hint.resolve_state = ResolveState::Resolving;
 568                    drop(guard);
 569                    cx.spawn(|editor, mut cx| async move {
 570                        let resolved_hint_task = editor.update(&mut cx, |editor, cx| {
 571                            editor
 572                                .buffer()
 573                                .read(cx)
 574                                .buffer(buffer_id)
 575                                .and_then(|buffer| {
 576                                    let project = editor.project.as_ref()?;
 577                                    Some(project.update(cx, |project, cx| {
 578                                        project.resolve_inlay_hint(
 579                                            hint_to_resolve,
 580                                            buffer,
 581                                            server_id,
 582                                            cx,
 583                                        )
 584                                    }))
 585                                })
 586                        })?;
 587                        if let Some(resolved_hint_task) = resolved_hint_task {
 588                            let mut resolved_hint =
 589                                resolved_hint_task.await.context("hint resolve task")?;
 590                            editor.update(&mut cx, |editor, _| {
 591                                if let Some(excerpt_hints) =
 592                                    editor.inlay_hint_cache.hints.get(&excerpt_id)
 593                                {
 594                                    let mut guard = excerpt_hints.write();
 595                                    if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
 596                                        if cached_hint.resolve_state == ResolveState::Resolving {
 597                                            resolved_hint.resolve_state = ResolveState::Resolved;
 598                                            *cached_hint = resolved_hint;
 599                                        }
 600                                    }
 601                                }
 602                            })?;
 603                        }
 604
 605                        anyhow::Ok(())
 606                    })
 607                    .detach_and_log_err(cx);
 608                }
 609            }
 610        }
 611    }
 612}
 613
 614fn spawn_new_update_tasks(
 615    editor: &mut Editor,
 616    reason: &'static str,
 617    excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
 618    invalidate: InvalidationStrategy,
 619    update_cache_version: usize,
 620    cx: &mut ViewContext<'_, Editor>,
 621) {
 622    let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
 623    for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in
 624        excerpts_to_query
 625    {
 626        if excerpt_visible_range.is_empty() {
 627            continue;
 628        }
 629        let buffer = excerpt_buffer.read(cx);
 630        let buffer_id = buffer.remote_id();
 631        let buffer_snapshot = buffer.snapshot();
 632        if buffer_snapshot
 633            .version()
 634            .changed_since(&new_task_buffer_version)
 635        {
 636            continue;
 637        }
 638
 639        let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
 640        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
 641            let cached_excerpt_hints = cached_excerpt_hints.read();
 642            let cached_buffer_version = &cached_excerpt_hints.buffer_version;
 643            if cached_excerpt_hints.version > update_cache_version
 644                || cached_buffer_version.changed_since(&new_task_buffer_version)
 645            {
 646                continue;
 647            }
 648        };
 649
 650        let (multi_buffer_snapshot, Some(query_ranges)) =
 651            editor.buffer.update(cx, |multi_buffer, cx| {
 652                (
 653                    multi_buffer.snapshot(cx),
 654                    determine_query_ranges(
 655                        multi_buffer,
 656                        excerpt_id,
 657                        &excerpt_buffer,
 658                        excerpt_visible_range,
 659                        cx,
 660                    ),
 661                )
 662            })
 663        else {
 664            return;
 665        };
 666        let query = ExcerptQuery {
 667            buffer_id,
 668            excerpt_id,
 669            cache_version: update_cache_version,
 670            invalidate,
 671            reason,
 672        };
 673
 674        let new_update_task = |query_ranges| {
 675            new_update_task(
 676                query,
 677                query_ranges,
 678                multi_buffer_snapshot,
 679                buffer_snapshot.clone(),
 680                Arc::clone(&visible_hints),
 681                cached_excerpt_hints,
 682                Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter),
 683                cx,
 684            )
 685        };
 686
 687        match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
 688            hash_map::Entry::Occupied(mut o) => {
 689                o.get_mut().update_cached_tasks(
 690                    &buffer_snapshot,
 691                    query_ranges,
 692                    invalidate,
 693                    new_update_task,
 694                );
 695            }
 696            hash_map::Entry::Vacant(v) => {
 697                v.insert(TasksForRanges::new(
 698                    query_ranges.clone(),
 699                    new_update_task(query_ranges),
 700                ));
 701            }
 702        }
 703    }
 704}
 705
 706#[derive(Debug, Clone)]
 707struct QueryRanges {
 708    before_visible: Vec<Range<language::Anchor>>,
 709    visible: Vec<Range<language::Anchor>>,
 710    after_visible: Vec<Range<language::Anchor>>,
 711}
 712
 713impl QueryRanges {
 714    fn is_empty(&self) -> bool {
 715        self.before_visible.is_empty() && self.visible.is_empty() && self.after_visible.is_empty()
 716    }
 717}
 718
 719fn determine_query_ranges(
 720    multi_buffer: &mut MultiBuffer,
 721    excerpt_id: ExcerptId,
 722    excerpt_buffer: &Model<Buffer>,
 723    excerpt_visible_range: Range<usize>,
 724    cx: &mut ModelContext<'_, MultiBuffer>,
 725) -> Option<QueryRanges> {
 726    let full_excerpt_range = multi_buffer
 727        .excerpts_for_buffer(excerpt_buffer, cx)
 728        .into_iter()
 729        .find(|(id, _)| id == &excerpt_id)
 730        .map(|(_, range)| range.context)?;
 731    let buffer = excerpt_buffer.read(cx);
 732    let snapshot = buffer.snapshot();
 733    let excerpt_visible_len = excerpt_visible_range.end - excerpt_visible_range.start;
 734
 735    let visible_range = if excerpt_visible_range.start == excerpt_visible_range.end {
 736        return None;
 737    } else {
 738        vec![
 739            buffer.anchor_before(snapshot.clip_offset(excerpt_visible_range.start, Bias::Left))
 740                ..buffer.anchor_after(snapshot.clip_offset(excerpt_visible_range.end, Bias::Right)),
 741        ]
 742    };
 743
 744    let full_excerpt_range_end_offset = full_excerpt_range.end.to_offset(&snapshot);
 745    let after_visible_range_start = excerpt_visible_range
 746        .end
 747        .saturating_add(1)
 748        .min(full_excerpt_range_end_offset)
 749        .min(buffer.len());
 750    let after_visible_range = if after_visible_range_start == full_excerpt_range_end_offset {
 751        Vec::new()
 752    } else {
 753        let after_range_end_offset = after_visible_range_start
 754            .saturating_add(excerpt_visible_len)
 755            .min(full_excerpt_range_end_offset)
 756            .min(buffer.len());
 757        vec![
 758            buffer.anchor_before(snapshot.clip_offset(after_visible_range_start, Bias::Left))
 759                ..buffer.anchor_after(snapshot.clip_offset(after_range_end_offset, Bias::Right)),
 760        ]
 761    };
 762
 763    let full_excerpt_range_start_offset = full_excerpt_range.start.to_offset(&snapshot);
 764    let before_visible_range_end = excerpt_visible_range
 765        .start
 766        .saturating_sub(1)
 767        .max(full_excerpt_range_start_offset);
 768    let before_visible_range = if before_visible_range_end == full_excerpt_range_start_offset {
 769        Vec::new()
 770    } else {
 771        let before_range_start_offset = before_visible_range_end
 772            .saturating_sub(excerpt_visible_len)
 773            .max(full_excerpt_range_start_offset);
 774        vec![
 775            buffer.anchor_before(snapshot.clip_offset(before_range_start_offset, Bias::Left))
 776                ..buffer.anchor_after(snapshot.clip_offset(before_visible_range_end, Bias::Right)),
 777        ]
 778    };
 779
 780    Some(QueryRanges {
 781        before_visible: before_visible_range,
 782        visible: visible_range,
 783        after_visible: after_visible_range,
 784    })
 785}
 786
 787const MAX_CONCURRENT_LSP_REQUESTS: usize = 5;
 788const INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS: u64 = 400;
 789
 790fn new_update_task(
 791    query: ExcerptQuery,
 792    query_ranges: QueryRanges,
 793    multi_buffer_snapshot: MultiBufferSnapshot,
 794    buffer_snapshot: BufferSnapshot,
 795    visible_hints: Arc<Vec<Inlay>>,
 796    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
 797    lsp_request_limiter: Arc<Semaphore>,
 798    cx: &mut ViewContext<'_, Editor>,
 799) -> Task<()> {
 800    cx.spawn(|editor, mut cx| async move {
 801        let closure_cx = cx.clone();
 802        let fetch_and_update_hints = |invalidate, range| {
 803            fetch_and_update_hints(
 804                editor.clone(),
 805                multi_buffer_snapshot.clone(),
 806                buffer_snapshot.clone(),
 807                Arc::clone(&visible_hints),
 808                cached_excerpt_hints.as_ref().map(Arc::clone),
 809                query,
 810                invalidate,
 811                range,
 812                Arc::clone(&lsp_request_limiter),
 813                closure_cx.clone(),
 814            )
 815        };
 816        let visible_range_update_results = future::join_all(query_ranges.visible.into_iter().map(
 817            |visible_range| async move {
 818                (
 819                    visible_range.clone(),
 820                    fetch_and_update_hints(query.invalidate.should_invalidate(), visible_range)
 821                        .await,
 822                )
 823            },
 824        ))
 825        .await;
 826
 827        let hint_delay = cx.background_executor().timer(Duration::from_millis(
 828            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS,
 829        ));
 830
 831        let mut query_range_failed = |range: &Range<language::Anchor>, e: anyhow::Error| {
 832            log::error!("inlay hint update task for range {range:?} failed: {e:#}");
 833            editor
 834                .update(&mut cx, |editor, _| {
 835                    if let Some(task_ranges) = editor
 836                        .inlay_hint_cache
 837                        .update_tasks
 838                        .get_mut(&query.excerpt_id)
 839                    {
 840                        task_ranges.invalidate_range(&buffer_snapshot, &range);
 841                    }
 842                })
 843                .ok()
 844        };
 845
 846        for (range, result) in visible_range_update_results {
 847            if let Err(e) = result {
 848                query_range_failed(&range, e);
 849            }
 850        }
 851
 852        hint_delay.await;
 853        let invisible_range_update_results = future::join_all(
 854            query_ranges
 855                .before_visible
 856                .into_iter()
 857                .chain(query_ranges.after_visible.into_iter())
 858                .map(|invisible_range| async move {
 859                    (
 860                        invisible_range.clone(),
 861                        fetch_and_update_hints(false, invisible_range).await,
 862                    )
 863                }),
 864        )
 865        .await;
 866        for (range, result) in invisible_range_update_results {
 867            if let Err(e) = result {
 868                query_range_failed(&range, e);
 869            }
 870        }
 871    })
 872}
 873
 874async fn fetch_and_update_hints(
 875    editor: gpui::WeakView<Editor>,
 876    multi_buffer_snapshot: MultiBufferSnapshot,
 877    buffer_snapshot: BufferSnapshot,
 878    visible_hints: Arc<Vec<Inlay>>,
 879    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
 880    query: ExcerptQuery,
 881    invalidate: bool,
 882    fetch_range: Range<language::Anchor>,
 883    lsp_request_limiter: Arc<Semaphore>,
 884    mut cx: gpui::AsyncWindowContext,
 885) -> anyhow::Result<()> {
 886    let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() {
 887        (None, false)
 888    } else {
 889        match lsp_request_limiter.try_acquire() {
 890            Some(guard) => (Some(guard), false),
 891            None => (Some(lsp_request_limiter.acquire().await), true),
 892        }
 893    };
 894    let fetch_range_to_log =
 895        fetch_range.start.to_point(&buffer_snapshot)..fetch_range.end.to_point(&buffer_snapshot);
 896    let inlay_hints_fetch_task = editor
 897        .update(&mut cx, |editor, cx| {
 898            if got_throttled {
 899                let query_not_around_visible_range = match editor.excerpts_for_inlay_hints_query(None, cx).remove(&query.excerpt_id) {
 900                    Some((_, _, current_visible_range)) => {
 901                        let visible_offset_length = current_visible_range.len();
 902                        let double_visible_range = current_visible_range
 903                            .start
 904                            .saturating_sub(visible_offset_length)
 905                            ..current_visible_range
 906                                .end
 907                                .saturating_add(visible_offset_length)
 908                                .min(buffer_snapshot.len());
 909                        !double_visible_range
 910                            .contains(&fetch_range.start.to_offset(&buffer_snapshot))
 911                            && !double_visible_range
 912                                .contains(&fetch_range.end.to_offset(&buffer_snapshot))
 913                    },
 914                    None => true,
 915                };
 916                if query_not_around_visible_range {
 917                    log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
 918                    if let Some(task_ranges) = editor
 919                        .inlay_hint_cache
 920                        .update_tasks
 921                        .get_mut(&query.excerpt_id)
 922                    {
 923                        task_ranges.invalidate_range(&buffer_snapshot, &fetch_range);
 924                    }
 925                    return None;
 926                }
 927            }
 928            editor
 929                .buffer()
 930                .read(cx)
 931                .buffer(query.buffer_id)
 932                .and_then(|buffer| {
 933                    let project = editor.project.as_ref()?;
 934                    Some(project.update(cx, |project, cx| {
 935                        project.inlay_hints(buffer, fetch_range.clone(), cx)
 936                    }))
 937                })
 938        })
 939        .ok()
 940        .flatten();
 941    let new_hints = match inlay_hints_fetch_task {
 942        Some(fetch_task) => {
 943            log::debug!(
 944                "Fetching inlay hints for range {fetch_range_to_log:?}, reason: {query_reason}, invalidate: {invalidate}",
 945                query_reason = query.reason,
 946            );
 947            log::trace!(
 948                "Currently visible hints: {visible_hints:?}, cached hints present: {}",
 949                cached_excerpt_hints.is_some(),
 950            );
 951            fetch_task.await.context("inlay hint fetch task")?
 952        }
 953        None => return Ok(()),
 954    };
 955    drop(lsp_request_guard);
 956    log::debug!(
 957        "Fetched {} hints for range {fetch_range_to_log:?}",
 958        new_hints.len()
 959    );
 960    log::trace!("Fetched hints: {new_hints:?}");
 961
 962    let background_task_buffer_snapshot = buffer_snapshot.clone();
 963    let background_fetch_range = fetch_range.clone();
 964    let new_update = cx
 965        .background_executor()
 966        .spawn(async move {
 967            calculate_hint_updates(
 968                query.excerpt_id,
 969                invalidate,
 970                background_fetch_range,
 971                new_hints,
 972                &background_task_buffer_snapshot,
 973                cached_excerpt_hints,
 974                &visible_hints,
 975            )
 976        })
 977        .await;
 978    if let Some(new_update) = new_update {
 979        log::debug!(
 980            "Applying update for range {fetch_range_to_log:?}: remove from editor: {}, remove from cache: {}, add to cache: {}",
 981            new_update.remove_from_visible.len(),
 982            new_update.remove_from_cache.len(),
 983            new_update.add_to_cache.len()
 984        );
 985        log::trace!("New update: {new_update:?}");
 986        editor
 987            .update(&mut cx, |editor, cx| {
 988                apply_hint_update(
 989                    editor,
 990                    new_update,
 991                    query,
 992                    invalidate,
 993                    buffer_snapshot,
 994                    multi_buffer_snapshot,
 995                    cx,
 996                );
 997            })
 998            .ok();
 999    }
1000    Ok(())
1001}
1002
1003fn calculate_hint_updates(
1004    excerpt_id: ExcerptId,
1005    invalidate: bool,
1006    fetch_range: Range<language::Anchor>,
1007    new_excerpt_hints: Vec<InlayHint>,
1008    buffer_snapshot: &BufferSnapshot,
1009    cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
1010    visible_hints: &[Inlay],
1011) -> Option<ExcerptHintsUpdate> {
1012    let mut add_to_cache = Vec::<InlayHint>::new();
1013    let mut excerpt_hints_to_persist = HashMap::default();
1014    for new_hint in new_excerpt_hints {
1015        if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) {
1016            continue;
1017        }
1018        let missing_from_cache = match &cached_excerpt_hints {
1019            Some(cached_excerpt_hints) => {
1020                let cached_excerpt_hints = cached_excerpt_hints.read();
1021                match cached_excerpt_hints
1022                    .ordered_hints
1023                    .binary_search_by(|probe| {
1024                        cached_excerpt_hints.hints_by_id[probe]
1025                            .position
1026                            .cmp(&new_hint.position, buffer_snapshot)
1027                    }) {
1028                    Ok(ix) => {
1029                        let mut missing_from_cache = true;
1030                        for id in &cached_excerpt_hints.ordered_hints[ix..] {
1031                            let cached_hint = &cached_excerpt_hints.hints_by_id[id];
1032                            if new_hint
1033                                .position
1034                                .cmp(&cached_hint.position, buffer_snapshot)
1035                                .is_gt()
1036                            {
1037                                break;
1038                            }
1039                            if cached_hint == &new_hint {
1040                                excerpt_hints_to_persist.insert(*id, cached_hint.kind);
1041                                missing_from_cache = false;
1042                            }
1043                        }
1044                        missing_from_cache
1045                    }
1046                    Err(_) => true,
1047                }
1048            }
1049            None => true,
1050        };
1051        if missing_from_cache {
1052            add_to_cache.push(new_hint);
1053        }
1054    }
1055
1056    let mut remove_from_visible = Vec::new();
1057    let mut remove_from_cache = HashSet::default();
1058    if invalidate {
1059        remove_from_visible.extend(
1060            visible_hints
1061                .iter()
1062                .filter(|hint| hint.position.excerpt_id == excerpt_id)
1063                .map(|inlay_hint| inlay_hint.id)
1064                .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)),
1065        );
1066
1067        if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
1068            let cached_excerpt_hints = cached_excerpt_hints.read();
1069            remove_from_cache.extend(
1070                cached_excerpt_hints
1071                    .ordered_hints
1072                    .iter()
1073                    .filter(|cached_inlay_id| {
1074                        !excerpt_hints_to_persist.contains_key(cached_inlay_id)
1075                    })
1076                    .copied(),
1077            );
1078        }
1079    }
1080
1081    if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
1082        None
1083    } else {
1084        Some(ExcerptHintsUpdate {
1085            excerpt_id,
1086            remove_from_visible,
1087            remove_from_cache,
1088            add_to_cache,
1089        })
1090    }
1091}
1092
1093fn contains_position(
1094    range: &Range<language::Anchor>,
1095    position: language::Anchor,
1096    buffer_snapshot: &BufferSnapshot,
1097) -> bool {
1098    range.start.cmp(&position, buffer_snapshot).is_le()
1099        && range.end.cmp(&position, buffer_snapshot).is_ge()
1100}
1101
1102fn apply_hint_update(
1103    editor: &mut Editor,
1104    new_update: ExcerptHintsUpdate,
1105    query: ExcerptQuery,
1106    invalidate: bool,
1107    buffer_snapshot: BufferSnapshot,
1108    multi_buffer_snapshot: MultiBufferSnapshot,
1109    cx: &mut ViewContext<'_, Editor>,
1110) {
1111    let cached_excerpt_hints = editor
1112        .inlay_hint_cache
1113        .hints
1114        .entry(new_update.excerpt_id)
1115        .or_insert_with(|| {
1116            Arc::new(RwLock::new(CachedExcerptHints {
1117                version: query.cache_version,
1118                buffer_version: buffer_snapshot.version().clone(),
1119                buffer_id: query.buffer_id,
1120                ordered_hints: Vec::new(),
1121                hints_by_id: HashMap::default(),
1122            }))
1123        });
1124    let mut cached_excerpt_hints = cached_excerpt_hints.write();
1125    match query.cache_version.cmp(&cached_excerpt_hints.version) {
1126        cmp::Ordering::Less => return,
1127        cmp::Ordering::Greater | cmp::Ordering::Equal => {
1128            cached_excerpt_hints.version = query.cache_version;
1129        }
1130    }
1131
1132    let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
1133    cached_excerpt_hints
1134        .ordered_hints
1135        .retain(|hint_id| !new_update.remove_from_cache.contains(hint_id));
1136    cached_excerpt_hints
1137        .hints_by_id
1138        .retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id));
1139    let mut splice = InlaySplice {
1140        to_remove: new_update.remove_from_visible,
1141        to_insert: Vec::new(),
1142    };
1143    for new_hint in new_update.add_to_cache {
1144        let insert_position = match cached_excerpt_hints
1145            .ordered_hints
1146            .binary_search_by(|probe| {
1147                cached_excerpt_hints.hints_by_id[probe]
1148                    .position
1149                    .cmp(&new_hint.position, &buffer_snapshot)
1150            }) {
1151            Ok(i) => {
1152                let mut insert_position = Some(i);
1153                for id in &cached_excerpt_hints.ordered_hints[i..] {
1154                    let cached_hint = &cached_excerpt_hints.hints_by_id[id];
1155                    if new_hint
1156                        .position
1157                        .cmp(&cached_hint.position, &buffer_snapshot)
1158                        .is_gt()
1159                    {
1160                        break;
1161                    }
1162                    if cached_hint.text() == new_hint.text() {
1163                        insert_position = None;
1164                        break;
1165                    }
1166                }
1167                insert_position
1168            }
1169            Err(i) => Some(i),
1170        };
1171
1172        if let Some(insert_position) = insert_position {
1173            let new_inlay_id = post_inc(&mut editor.next_inlay_id);
1174            if editor
1175                .inlay_hint_cache
1176                .allowed_hint_kinds
1177                .contains(&new_hint.kind)
1178            {
1179                let new_hint_position =
1180                    multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
1181                splice
1182                    .to_insert
1183                    .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
1184            }
1185            let new_id = InlayId::Hint(new_inlay_id);
1186            cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
1187            cached_excerpt_hints
1188                .ordered_hints
1189                .insert(insert_position, new_id);
1190            cached_inlays_changed = true;
1191        }
1192    }
1193    cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
1194    drop(cached_excerpt_hints);
1195
1196    if invalidate {
1197        let mut outdated_excerpt_caches = HashSet::default();
1198        for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
1199            let excerpt_hints = excerpt_hints.read();
1200            if excerpt_hints.buffer_id == query.buffer_id
1201                && excerpt_id != &query.excerpt_id
1202                && buffer_snapshot
1203                    .version()
1204                    .changed_since(&excerpt_hints.buffer_version)
1205            {
1206                outdated_excerpt_caches.insert(*excerpt_id);
1207                splice
1208                    .to_remove
1209                    .extend(excerpt_hints.ordered_hints.iter().copied());
1210            }
1211        }
1212        cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
1213        editor
1214            .inlay_hint_cache
1215            .hints
1216            .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
1217    }
1218
1219    let InlaySplice {
1220        to_remove,
1221        to_insert,
1222    } = splice;
1223    let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
1224    if cached_inlays_changed || displayed_inlays_changed {
1225        editor.inlay_hint_cache.version += 1;
1226    }
1227    if displayed_inlays_changed {
1228        editor.splice_inlay_hints(to_remove, to_insert, cx)
1229    }
1230}
1231
1232#[cfg(test)]
1233pub mod tests {
1234    use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
1235
1236    use crate::{
1237        scroll::{scroll_amount::ScrollAmount, Autoscroll},
1238        ExcerptRange,
1239    };
1240    use futures::StreamExt;
1241    use gpui::{Context, TestAppContext, WindowHandle};
1242    use itertools::Itertools;
1243    use language::{
1244        language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
1245        LanguageConfig,
1246    };
1247    use lsp::FakeLanguageServer;
1248    use parking_lot::Mutex;
1249    use project::{FakeFs, Project};
1250    use serde_json::json;
1251    use settings::SettingsStore;
1252    use text::{Point, ToPoint};
1253
1254    use crate::editor_tests::update_test_language_settings;
1255
1256    use super::*;
1257
1258    #[gpui::test]
1259    async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
1260        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1261        init_test(cx, |settings| {
1262            settings.defaults.inlay_hints = Some(InlayHintSettings {
1263                enabled: true,
1264                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1265                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1266                show_other_hints: allowed_hint_kinds.contains(&None),
1267            })
1268        });
1269
1270        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1271        let lsp_request_count = Arc::new(AtomicU32::new(0));
1272        fake_server
1273            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1274                let task_lsp_request_count = Arc::clone(&lsp_request_count);
1275                async move {
1276                    assert_eq!(
1277                        params.text_document.uri,
1278                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1279                    );
1280                    let current_call_id =
1281                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1282                    let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
1283                    for _ in 0..2 {
1284                        let mut i = current_call_id;
1285                        loop {
1286                            new_hints.push(lsp::InlayHint {
1287                                position: lsp::Position::new(0, i),
1288                                label: lsp::InlayHintLabel::String(i.to_string()),
1289                                kind: None,
1290                                text_edits: None,
1291                                tooltip: None,
1292                                padding_left: None,
1293                                padding_right: None,
1294                                data: None,
1295                            });
1296                            if i == 0 {
1297                                break;
1298                            }
1299                            i -= 1;
1300                        }
1301                    }
1302
1303                    Ok(Some(new_hints))
1304                }
1305            })
1306            .next()
1307            .await;
1308        cx.executor().run_until_parked();
1309
1310        let mut edits_made = 1;
1311        _ = editor.update(cx, |editor, cx| {
1312            let expected_hints = vec!["0".to_string()];
1313            assert_eq!(
1314                expected_hints,
1315                cached_hint_labels(editor),
1316                "Should get its first hints when opening the editor"
1317            );
1318            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1319            let inlay_cache = editor.inlay_hint_cache();
1320            assert_eq!(
1321                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1322                "Cache should use editor settings to get the allowed hint kinds"
1323            );
1324            assert_eq!(
1325                inlay_cache.version, edits_made,
1326                "The editor update the cache version after every cache/view change"
1327            );
1328        });
1329
1330        _ = editor.update(cx, |editor, cx| {
1331            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1332            editor.handle_input("some change", cx);
1333            edits_made += 1;
1334        });
1335        cx.executor().run_until_parked();
1336        _ = editor.update(cx, |editor, cx| {
1337            let expected_hints = vec!["0".to_string(), "1".to_string()];
1338            assert_eq!(
1339                expected_hints,
1340                cached_hint_labels(editor),
1341                "Should get new hints after an edit"
1342            );
1343            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1344            let inlay_cache = editor.inlay_hint_cache();
1345            assert_eq!(
1346                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1347                "Cache should use editor settings to get the allowed hint kinds"
1348            );
1349            assert_eq!(
1350                inlay_cache.version, edits_made,
1351                "The editor update the cache version after every cache/view change"
1352            );
1353        });
1354
1355        fake_server
1356            .request::<lsp::request::InlayHintRefreshRequest>(())
1357            .await
1358            .expect("inlay refresh request failed");
1359        edits_made += 1;
1360        cx.executor().run_until_parked();
1361        _ = editor.update(cx, |editor, cx| {
1362            let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()];
1363            assert_eq!(
1364                expected_hints,
1365                cached_hint_labels(editor),
1366                "Should get new hints after hint refresh/ request"
1367            );
1368            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1369            let inlay_cache = editor.inlay_hint_cache();
1370            assert_eq!(
1371                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1372                "Cache should use editor settings to get the allowed hint kinds"
1373            );
1374            assert_eq!(
1375                inlay_cache.version, edits_made,
1376                "The editor update the cache version after every cache/view change"
1377            );
1378        });
1379    }
1380
1381    #[gpui::test]
1382    async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
1383        init_test(cx, |settings| {
1384            settings.defaults.inlay_hints = Some(InlayHintSettings {
1385                enabled: true,
1386                show_type_hints: true,
1387                show_parameter_hints: true,
1388                show_other_hints: true,
1389            })
1390        });
1391
1392        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1393        let lsp_request_count = Arc::new(AtomicU32::new(0));
1394        fake_server
1395            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1396                let task_lsp_request_count = Arc::clone(&lsp_request_count);
1397                async move {
1398                    assert_eq!(
1399                        params.text_document.uri,
1400                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1401                    );
1402                    let current_call_id =
1403                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1404                    Ok(Some(vec![lsp::InlayHint {
1405                        position: lsp::Position::new(0, current_call_id),
1406                        label: lsp::InlayHintLabel::String(current_call_id.to_string()),
1407                        kind: None,
1408                        text_edits: None,
1409                        tooltip: None,
1410                        padding_left: None,
1411                        padding_right: None,
1412                        data: None,
1413                    }]))
1414                }
1415            })
1416            .next()
1417            .await;
1418        cx.executor().run_until_parked();
1419
1420        let mut edits_made = 1;
1421        _ = editor.update(cx, |editor, cx| {
1422            let expected_hints = vec!["0".to_string()];
1423            assert_eq!(
1424                expected_hints,
1425                cached_hint_labels(editor),
1426                "Should get its first hints when opening the editor"
1427            );
1428            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1429            assert_eq!(
1430                editor.inlay_hint_cache().version,
1431                edits_made,
1432                "The editor update the cache version after every cache/view change"
1433            );
1434        });
1435
1436        let progress_token = "test_progress_token";
1437        fake_server
1438            .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1439                token: lsp::ProgressToken::String(progress_token.to_string()),
1440            })
1441            .await
1442            .expect("work done progress create request failed");
1443        cx.executor().run_until_parked();
1444        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1445            token: lsp::ProgressToken::String(progress_token.to_string()),
1446            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
1447                lsp::WorkDoneProgressBegin::default(),
1448            )),
1449        });
1450        cx.executor().run_until_parked();
1451
1452        _ = editor.update(cx, |editor, cx| {
1453            let expected_hints = vec!["0".to_string()];
1454            assert_eq!(
1455                expected_hints,
1456                cached_hint_labels(editor),
1457                "Should not update hints while the work task is running"
1458            );
1459            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1460            assert_eq!(
1461                editor.inlay_hint_cache().version,
1462                edits_made,
1463                "Should not update the cache while the work task is running"
1464            );
1465        });
1466
1467        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1468            token: lsp::ProgressToken::String(progress_token.to_string()),
1469            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
1470                lsp::WorkDoneProgressEnd::default(),
1471            )),
1472        });
1473        cx.executor().run_until_parked();
1474
1475        edits_made += 1;
1476        _ = editor.update(cx, |editor, cx| {
1477            let expected_hints = vec!["1".to_string()];
1478            assert_eq!(
1479                expected_hints,
1480                cached_hint_labels(editor),
1481                "New hints should be queried after the work task is done"
1482            );
1483            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1484            assert_eq!(
1485                editor.inlay_hint_cache().version,
1486                edits_made,
1487                "Cache version should update once after the work task is done"
1488            );
1489        });
1490    }
1491
1492    #[gpui::test]
1493    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1494        init_test(cx, |settings| {
1495            settings.defaults.inlay_hints = Some(InlayHintSettings {
1496                enabled: true,
1497                show_type_hints: true,
1498                show_parameter_hints: true,
1499                show_other_hints: true,
1500            })
1501        });
1502
1503        let fs = FakeFs::new(cx.background_executor.clone());
1504        fs.insert_tree(
1505                    "/a",
1506                    json!({
1507                        "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1508                        "other.md": "Test md file with some text",
1509                    }),
1510                )
1511                .await;
1512        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1513
1514        let mut rs_fake_servers = None;
1515        let mut md_fake_servers = None;
1516        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1517            let mut language = Language::new(
1518                LanguageConfig {
1519                    name: name.into(),
1520                    path_suffixes: vec![path_suffix.to_string()],
1521                    ..Default::default()
1522                },
1523                Some(tree_sitter_rust::language()),
1524            );
1525            let fake_servers = language
1526                .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1527                    name,
1528                    capabilities: lsp::ServerCapabilities {
1529                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1530                        ..Default::default()
1531                    },
1532                    ..Default::default()
1533                }))
1534                .await;
1535            match name {
1536                "Rust" => rs_fake_servers = Some(fake_servers),
1537                "Markdown" => md_fake_servers = Some(fake_servers),
1538                _ => unreachable!(),
1539            }
1540            project.update(cx, |project, _| {
1541                project.languages().add(Arc::new(language));
1542            });
1543        }
1544
1545        let rs_buffer = project
1546            .update(cx, |project, cx| {
1547                project.open_local_buffer("/a/main.rs", cx)
1548            })
1549            .await
1550            .unwrap();
1551        cx.executor().run_until_parked();
1552        cx.executor().start_waiting();
1553        let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1554        let rs_editor =
1555            cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
1556        let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1557        rs_fake_server
1558            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1559                let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
1560                async move {
1561                    assert_eq!(
1562                        params.text_document.uri,
1563                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1564                    );
1565                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1566                    Ok(Some(vec![lsp::InlayHint {
1567                        position: lsp::Position::new(0, i),
1568                        label: lsp::InlayHintLabel::String(i.to_string()),
1569                        kind: None,
1570                        text_edits: None,
1571                        tooltip: None,
1572                        padding_left: None,
1573                        padding_right: None,
1574                        data: None,
1575                    }]))
1576                }
1577            })
1578            .next()
1579            .await;
1580        cx.executor().run_until_parked();
1581        _ = rs_editor.update(cx, |editor, cx| {
1582            let expected_hints = vec!["0".to_string()];
1583            assert_eq!(
1584                expected_hints,
1585                cached_hint_labels(editor),
1586                "Should get its first hints when opening the editor"
1587            );
1588            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1589            assert_eq!(
1590                editor.inlay_hint_cache().version,
1591                1,
1592                "Rust editor update the cache version after every cache/view change"
1593            );
1594        });
1595
1596        cx.executor().run_until_parked();
1597        let md_buffer = project
1598            .update(cx, |project, cx| {
1599                project.open_local_buffer("/a/other.md", cx)
1600            })
1601            .await
1602            .unwrap();
1603        cx.executor().run_until_parked();
1604        cx.executor().start_waiting();
1605        let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1606        let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
1607        let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1608        md_fake_server
1609            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1610                let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
1611                async move {
1612                    assert_eq!(
1613                        params.text_document.uri,
1614                        lsp::Url::from_file_path("/a/other.md").unwrap(),
1615                    );
1616                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1617                    Ok(Some(vec![lsp::InlayHint {
1618                        position: lsp::Position::new(0, i),
1619                        label: lsp::InlayHintLabel::String(i.to_string()),
1620                        kind: None,
1621                        text_edits: None,
1622                        tooltip: None,
1623                        padding_left: None,
1624                        padding_right: None,
1625                        data: None,
1626                    }]))
1627                }
1628            })
1629            .next()
1630            .await;
1631        cx.executor().run_until_parked();
1632        _ = md_editor.update(cx, |editor, cx| {
1633            let expected_hints = vec!["0".to_string()];
1634            assert_eq!(
1635                expected_hints,
1636                cached_hint_labels(editor),
1637                "Markdown editor should have a separate version, repeating Rust editor rules"
1638            );
1639            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1640            assert_eq!(editor.inlay_hint_cache().version, 1);
1641        });
1642
1643        _ = rs_editor.update(cx, |editor, cx| {
1644            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1645            editor.handle_input("some rs change", cx);
1646        });
1647        cx.executor().run_until_parked();
1648        _ = rs_editor.update(cx, |editor, cx| {
1649            let expected_hints = vec!["1".to_string()];
1650            assert_eq!(
1651                expected_hints,
1652                cached_hint_labels(editor),
1653                "Rust inlay cache should change after the edit"
1654            );
1655            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1656            assert_eq!(
1657                editor.inlay_hint_cache().version,
1658                2,
1659                "Every time hint cache changes, cache version should be incremented"
1660            );
1661        });
1662        _ = md_editor.update(cx, |editor, cx| {
1663            let expected_hints = vec!["0".to_string()];
1664            assert_eq!(
1665                expected_hints,
1666                cached_hint_labels(editor),
1667                "Markdown editor should not be affected by Rust editor changes"
1668            );
1669            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1670            assert_eq!(editor.inlay_hint_cache().version, 1);
1671        });
1672
1673        _ = md_editor.update(cx, |editor, cx| {
1674            editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1675            editor.handle_input("some md change", cx);
1676        });
1677        cx.executor().run_until_parked();
1678        _ = md_editor.update(cx, |editor, cx| {
1679            let expected_hints = vec!["1".to_string()];
1680            assert_eq!(
1681                expected_hints,
1682                cached_hint_labels(editor),
1683                "Rust editor should not be affected by Markdown editor changes"
1684            );
1685            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1686            assert_eq!(editor.inlay_hint_cache().version, 2);
1687        });
1688        _ = rs_editor.update(cx, |editor, cx| {
1689            let expected_hints = vec!["1".to_string()];
1690            assert_eq!(
1691                expected_hints,
1692                cached_hint_labels(editor),
1693                "Markdown editor should also change independently"
1694            );
1695            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1696            assert_eq!(editor.inlay_hint_cache().version, 2);
1697        });
1698    }
1699
1700    #[gpui::test]
1701    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1702        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1703        init_test(cx, |settings| {
1704            settings.defaults.inlay_hints = Some(InlayHintSettings {
1705                enabled: true,
1706                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1707                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1708                show_other_hints: allowed_hint_kinds.contains(&None),
1709            })
1710        });
1711
1712        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1713        let lsp_request_count = Arc::new(AtomicU32::new(0));
1714        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1715        fake_server
1716            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1717                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1718                async move {
1719                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1720                    assert_eq!(
1721                        params.text_document.uri,
1722                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1723                    );
1724                    Ok(Some(vec![
1725                        lsp::InlayHint {
1726                            position: lsp::Position::new(0, 1),
1727                            label: lsp::InlayHintLabel::String("type hint".to_string()),
1728                            kind: Some(lsp::InlayHintKind::TYPE),
1729                            text_edits: None,
1730                            tooltip: None,
1731                            padding_left: None,
1732                            padding_right: None,
1733                            data: None,
1734                        },
1735                        lsp::InlayHint {
1736                            position: lsp::Position::new(0, 2),
1737                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1738                            kind: Some(lsp::InlayHintKind::PARAMETER),
1739                            text_edits: None,
1740                            tooltip: None,
1741                            padding_left: None,
1742                            padding_right: None,
1743                            data: None,
1744                        },
1745                        lsp::InlayHint {
1746                            position: lsp::Position::new(0, 3),
1747                            label: lsp::InlayHintLabel::String("other hint".to_string()),
1748                            kind: None,
1749                            text_edits: None,
1750                            tooltip: None,
1751                            padding_left: None,
1752                            padding_right: None,
1753                            data: None,
1754                        },
1755                    ]))
1756                }
1757            })
1758            .next()
1759            .await;
1760        cx.executor().run_until_parked();
1761
1762        let mut edits_made = 1;
1763        _ = editor.update(cx, |editor, cx| {
1764            assert_eq!(
1765                lsp_request_count.load(Ordering::Relaxed),
1766                1,
1767                "Should query new hints once"
1768            );
1769            assert_eq!(
1770                vec![
1771                    "other hint".to_string(),
1772                    "parameter hint".to_string(),
1773                    "type hint".to_string(),
1774                ],
1775                cached_hint_labels(editor),
1776                "Should get its first hints when opening the editor"
1777            );
1778            assert_eq!(
1779                vec!["other hint".to_string(), "type hint".to_string()],
1780                visible_hint_labels(editor, cx)
1781            );
1782            let inlay_cache = editor.inlay_hint_cache();
1783            assert_eq!(
1784                inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1785                "Cache should use editor settings to get the allowed hint kinds"
1786            );
1787            assert_eq!(
1788                inlay_cache.version, edits_made,
1789                "The editor update the cache version after every cache/view change"
1790            );
1791        });
1792
1793        fake_server
1794            .request::<lsp::request::InlayHintRefreshRequest>(())
1795            .await
1796            .expect("inlay refresh request failed");
1797        cx.executor().run_until_parked();
1798        _ = editor.update(cx, |editor, cx| {
1799            assert_eq!(
1800                lsp_request_count.load(Ordering::Relaxed),
1801                2,
1802                "Should load new hints twice"
1803            );
1804            assert_eq!(
1805                vec![
1806                    "other hint".to_string(),
1807                    "parameter hint".to_string(),
1808                    "type hint".to_string(),
1809                ],
1810                cached_hint_labels(editor),
1811                "Cached hints should not change due to allowed hint kinds settings update"
1812            );
1813            assert_eq!(
1814                vec!["other hint".to_string(), "type hint".to_string()],
1815                visible_hint_labels(editor, cx)
1816            );
1817            assert_eq!(
1818                editor.inlay_hint_cache().version,
1819                edits_made,
1820                "Should not update cache version due to new loaded hints being the same"
1821            );
1822        });
1823
1824        for (new_allowed_hint_kinds, expected_visible_hints) in [
1825            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1826            (
1827                HashSet::from_iter([Some(InlayHintKind::Type)]),
1828                vec!["type hint".to_string()],
1829            ),
1830            (
1831                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1832                vec!["parameter hint".to_string()],
1833            ),
1834            (
1835                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1836                vec!["other hint".to_string(), "type hint".to_string()],
1837            ),
1838            (
1839                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1840                vec!["other hint".to_string(), "parameter hint".to_string()],
1841            ),
1842            (
1843                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1844                vec!["parameter hint".to_string(), "type hint".to_string()],
1845            ),
1846            (
1847                HashSet::from_iter([
1848                    None,
1849                    Some(InlayHintKind::Type),
1850                    Some(InlayHintKind::Parameter),
1851                ]),
1852                vec![
1853                    "other hint".to_string(),
1854                    "parameter hint".to_string(),
1855                    "type hint".to_string(),
1856                ],
1857            ),
1858        ] {
1859            edits_made += 1;
1860            update_test_language_settings(cx, |settings| {
1861                settings.defaults.inlay_hints = Some(InlayHintSettings {
1862                    enabled: true,
1863                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1864                    show_parameter_hints: new_allowed_hint_kinds
1865                        .contains(&Some(InlayHintKind::Parameter)),
1866                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1867                })
1868            });
1869            cx.executor().run_until_parked();
1870            _ = editor.update(cx, |editor, cx| {
1871                assert_eq!(
1872                    lsp_request_count.load(Ordering::Relaxed),
1873                    2,
1874                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1875                );
1876                assert_eq!(
1877                    vec![
1878                        "other hint".to_string(),
1879                        "parameter hint".to_string(),
1880                        "type hint".to_string(),
1881                    ],
1882                    cached_hint_labels(editor),
1883                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1884                );
1885                assert_eq!(
1886                    expected_visible_hints,
1887                    visible_hint_labels(editor, cx),
1888                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1889                );
1890                let inlay_cache = editor.inlay_hint_cache();
1891                assert_eq!(
1892                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1893                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1894                );
1895                assert_eq!(
1896                    inlay_cache.version, edits_made,
1897                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1898                );
1899            });
1900        }
1901
1902        edits_made += 1;
1903        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1904        update_test_language_settings(cx, |settings| {
1905            settings.defaults.inlay_hints = Some(InlayHintSettings {
1906                enabled: false,
1907                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1908                show_parameter_hints: another_allowed_hint_kinds
1909                    .contains(&Some(InlayHintKind::Parameter)),
1910                show_other_hints: another_allowed_hint_kinds.contains(&None),
1911            })
1912        });
1913        cx.executor().run_until_parked();
1914        _ = editor.update(cx, |editor, cx| {
1915            assert_eq!(
1916                lsp_request_count.load(Ordering::Relaxed),
1917                2,
1918                "Should not load new hints when hints got disabled"
1919            );
1920            assert!(
1921                cached_hint_labels(editor).is_empty(),
1922                "Should clear the cache when hints got disabled"
1923            );
1924            assert!(
1925                visible_hint_labels(editor, cx).is_empty(),
1926                "Should clear visible hints when hints got disabled"
1927            );
1928            let inlay_cache = editor.inlay_hint_cache();
1929            assert_eq!(
1930                inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1931                "Should update its allowed hint kinds even when hints got disabled"
1932            );
1933            assert_eq!(
1934                inlay_cache.version, edits_made,
1935                "The editor should update the cache version after hints got disabled"
1936            );
1937        });
1938
1939        fake_server
1940            .request::<lsp::request::InlayHintRefreshRequest>(())
1941            .await
1942            .expect("inlay refresh request failed");
1943        cx.executor().run_until_parked();
1944        _ = editor.update(cx, |editor, cx| {
1945            assert_eq!(
1946                lsp_request_count.load(Ordering::Relaxed),
1947                2,
1948                "Should not load new hints when they got disabled"
1949            );
1950            assert!(cached_hint_labels(editor).is_empty());
1951            assert!(visible_hint_labels(editor, cx).is_empty());
1952            assert_eq!(
1953                editor.inlay_hint_cache().version, edits_made,
1954                "The editor should not update the cache version after /refresh query without updates"
1955            );
1956        });
1957
1958        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1959        edits_made += 1;
1960        update_test_language_settings(cx, |settings| {
1961            settings.defaults.inlay_hints = Some(InlayHintSettings {
1962                enabled: true,
1963                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1964                show_parameter_hints: final_allowed_hint_kinds
1965                    .contains(&Some(InlayHintKind::Parameter)),
1966                show_other_hints: final_allowed_hint_kinds.contains(&None),
1967            })
1968        });
1969        cx.executor().run_until_parked();
1970        _ = editor.update(cx, |editor, cx| {
1971            assert_eq!(
1972                lsp_request_count.load(Ordering::Relaxed),
1973                3,
1974                "Should query for new hints when they got reenabled"
1975            );
1976            assert_eq!(
1977                vec![
1978                    "other hint".to_string(),
1979                    "parameter hint".to_string(),
1980                    "type hint".to_string(),
1981                ],
1982                cached_hint_labels(editor),
1983                "Should get its cached hints fully repopulated after the hints got reenabled"
1984            );
1985            assert_eq!(
1986                vec!["parameter hint".to_string()],
1987                visible_hint_labels(editor, cx),
1988                "Should get its visible hints repopulated and filtered after the h"
1989            );
1990            let inlay_cache = editor.inlay_hint_cache();
1991            assert_eq!(
1992                inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
1993                "Cache should update editor settings when hints got reenabled"
1994            );
1995            assert_eq!(
1996                inlay_cache.version, edits_made,
1997                "Cache should update its version after hints got reenabled"
1998            );
1999        });
2000
2001        fake_server
2002            .request::<lsp::request::InlayHintRefreshRequest>(())
2003            .await
2004            .expect("inlay refresh request failed");
2005        cx.executor().run_until_parked();
2006        _ = editor.update(cx, |editor, cx| {
2007            assert_eq!(
2008                lsp_request_count.load(Ordering::Relaxed),
2009                4,
2010                "Should query for new hints again"
2011            );
2012            assert_eq!(
2013                vec![
2014                    "other hint".to_string(),
2015                    "parameter hint".to_string(),
2016                    "type hint".to_string(),
2017                ],
2018                cached_hint_labels(editor),
2019            );
2020            assert_eq!(
2021                vec!["parameter hint".to_string()],
2022                visible_hint_labels(editor, cx),
2023            );
2024            assert_eq!(editor.inlay_hint_cache().version, edits_made);
2025        });
2026    }
2027
2028    #[gpui::test]
2029    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
2030        init_test(cx, |settings| {
2031            settings.defaults.inlay_hints = Some(InlayHintSettings {
2032                enabled: true,
2033                show_type_hints: true,
2034                show_parameter_hints: true,
2035                show_other_hints: true,
2036            })
2037        });
2038
2039        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
2040        let fake_server = Arc::new(fake_server);
2041        let lsp_request_count = Arc::new(AtomicU32::new(0));
2042        let another_lsp_request_count = Arc::clone(&lsp_request_count);
2043        fake_server
2044            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2045                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
2046                async move {
2047                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
2048                    assert_eq!(
2049                        params.text_document.uri,
2050                        lsp::Url::from_file_path(file_with_hints).unwrap(),
2051                    );
2052                    Ok(Some(vec![lsp::InlayHint {
2053                        position: lsp::Position::new(0, i),
2054                        label: lsp::InlayHintLabel::String(i.to_string()),
2055                        kind: None,
2056                        text_edits: None,
2057                        tooltip: None,
2058                        padding_left: None,
2059                        padding_right: None,
2060                        data: None,
2061                    }]))
2062                }
2063            })
2064            .next()
2065            .await;
2066
2067        let mut expected_changes = Vec::new();
2068        for change_after_opening in [
2069            "initial change #1",
2070            "initial change #2",
2071            "initial change #3",
2072        ] {
2073            _ = editor.update(cx, |editor, cx| {
2074                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
2075                editor.handle_input(change_after_opening, cx);
2076            });
2077            expected_changes.push(change_after_opening);
2078        }
2079
2080        cx.executor().run_until_parked();
2081
2082        _ = editor.update(cx, |editor, cx| {
2083            let current_text = editor.text(cx);
2084            for change in &expected_changes {
2085                assert!(
2086                    current_text.contains(change),
2087                    "Should apply all changes made"
2088                );
2089            }
2090            assert_eq!(
2091                lsp_request_count.load(Ordering::Relaxed),
2092                2,
2093                "Should query new hints twice: for editor init and for the last edit that interrupted all others"
2094            );
2095            let expected_hints = vec!["2".to_string()];
2096            assert_eq!(
2097                expected_hints,
2098                cached_hint_labels(editor),
2099                "Should get hints from the last edit landed only"
2100            );
2101            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2102            assert_eq!(
2103                editor.inlay_hint_cache().version, 1,
2104                "Only one update should be registered in the cache after all cancellations"
2105            );
2106        });
2107
2108        let mut edits = Vec::new();
2109        for async_later_change in [
2110            "another change #1",
2111            "another change #2",
2112            "another change #3",
2113        ] {
2114            expected_changes.push(async_later_change);
2115            let task_editor = editor.clone();
2116            edits.push(cx.spawn(|mut cx| async move {
2117                _ = task_editor.update(&mut cx, |editor, cx| {
2118                    editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
2119                    editor.handle_input(async_later_change, cx);
2120                });
2121            }));
2122        }
2123        let _ = future::join_all(edits).await;
2124        cx.executor().run_until_parked();
2125
2126        _ = editor.update(cx, |editor, cx| {
2127            let current_text = editor.text(cx);
2128            for change in &expected_changes {
2129                assert!(
2130                    current_text.contains(change),
2131                    "Should apply all changes made"
2132                );
2133            }
2134            assert_eq!(
2135                lsp_request_count.load(Ordering::SeqCst),
2136                3,
2137                "Should query new hints one more time, for the last edit only"
2138            );
2139            let expected_hints = vec!["3".to_string()];
2140            assert_eq!(
2141                expected_hints,
2142                cached_hint_labels(editor),
2143                "Should get hints from the last edit landed only"
2144            );
2145            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2146            assert_eq!(
2147                editor.inlay_hint_cache().version,
2148                2,
2149                "Should update the cache version once more, for the new change"
2150            );
2151        });
2152    }
2153
2154    #[gpui::test(iterations = 10)]
2155    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
2156        init_test(cx, |settings| {
2157            settings.defaults.inlay_hints = Some(InlayHintSettings {
2158                enabled: true,
2159                show_type_hints: true,
2160                show_parameter_hints: true,
2161                show_other_hints: true,
2162            })
2163        });
2164
2165        let mut language = Language::new(
2166            LanguageConfig {
2167                name: "Rust".into(),
2168                path_suffixes: vec!["rs".to_string()],
2169                ..Default::default()
2170            },
2171            Some(tree_sitter_rust::language()),
2172        );
2173        let mut fake_servers = language
2174            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2175                capabilities: lsp::ServerCapabilities {
2176                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2177                    ..Default::default()
2178                },
2179                ..Default::default()
2180            }))
2181            .await;
2182        let fs = FakeFs::new(cx.background_executor.clone());
2183        fs.insert_tree(
2184            "/a",
2185            json!({
2186                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
2187                "other.rs": "// Test file",
2188            }),
2189        )
2190        .await;
2191        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2192        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2193        let buffer = project
2194            .update(cx, |project, cx| {
2195                project.open_local_buffer("/a/main.rs", cx)
2196            })
2197            .await
2198            .unwrap();
2199        cx.executor().run_until_parked();
2200        cx.executor().start_waiting();
2201        let fake_server = fake_servers.next().await.unwrap();
2202        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
2203        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
2204        let lsp_request_count = Arc::new(AtomicUsize::new(0));
2205        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
2206        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2207        fake_server
2208            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2209                let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
2210                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2211                async move {
2212                    assert_eq!(
2213                        params.text_document.uri,
2214                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
2215                    );
2216
2217                    task_lsp_request_ranges.lock().push(params.range);
2218                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
2219                    Ok(Some(vec![lsp::InlayHint {
2220                        position: params.range.end,
2221                        label: lsp::InlayHintLabel::String(i.to_string()),
2222                        kind: None,
2223                        text_edits: None,
2224                        tooltip: None,
2225                        padding_left: None,
2226                        padding_right: None,
2227                        data: None,
2228                    }]))
2229                }
2230            })
2231            .next()
2232            .await;
2233
2234        fn editor_visible_range(
2235            editor: &WindowHandle<Editor>,
2236            cx: &mut gpui::TestAppContext,
2237        ) -> Range<Point> {
2238            let ranges = editor
2239                .update(cx, |editor, cx| {
2240                    editor.excerpts_for_inlay_hints_query(None, cx)
2241                })
2242                .unwrap();
2243            assert_eq!(
2244                ranges.len(),
2245                1,
2246                "Single buffer should produce a single excerpt with visible range"
2247            );
2248            let (_, (excerpt_buffer, _, excerpt_visible_range)) =
2249                ranges.into_iter().next().unwrap();
2250            excerpt_buffer.update(cx, |buffer, _| {
2251                let snapshot = buffer.snapshot();
2252                let start = buffer
2253                    .anchor_before(excerpt_visible_range.start)
2254                    .to_point(&snapshot);
2255                let end = buffer
2256                    .anchor_after(excerpt_visible_range.end)
2257                    .to_point(&snapshot);
2258                start..end
2259            })
2260        }
2261
2262        // in large buffers, requests are made for more than visible range of a buffer.
2263        // invisible parts are queried later, to avoid excessive requests on quick typing.
2264        // wait the timeout needed to get all requests.
2265        cx.executor().advance_clock(Duration::from_millis(
2266            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2267        ));
2268        cx.executor().run_until_parked();
2269        let initial_visible_range = editor_visible_range(&editor, cx);
2270        let lsp_initial_visible_range = lsp::Range::new(
2271            lsp::Position::new(
2272                initial_visible_range.start.row,
2273                initial_visible_range.start.column,
2274            ),
2275            lsp::Position::new(
2276                initial_visible_range.end.row,
2277                initial_visible_range.end.column,
2278            ),
2279        );
2280        let expected_initial_query_range_end =
2281            lsp::Position::new(initial_visible_range.end.row * 2, 2);
2282        let mut expected_invisible_query_start = lsp_initial_visible_range.end;
2283        expected_invisible_query_start.character += 1;
2284        _ = editor.update(cx, |editor, cx| {
2285            let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2286            assert_eq!(ranges.len(), 2,
2287                "When scroll is at the edge of a big document, its visible part and the same range further should be queried in order, but got: {ranges:?}");
2288            let visible_query_range = &ranges[0];
2289            assert_eq!(visible_query_range.start, lsp_initial_visible_range.start);
2290            assert_eq!(visible_query_range.end, lsp_initial_visible_range.end);
2291            let invisible_query_range = &ranges[1];
2292
2293            assert_eq!(invisible_query_range.start, expected_invisible_query_start, "Should initially query visible edge of the document");
2294            assert_eq!(invisible_query_range.end, expected_initial_query_range_end, "Should initially query visible edge of the document");
2295
2296            let requests_count = lsp_request_count.load(Ordering::Acquire);
2297            assert_eq!(requests_count, 2, "Visible + invisible request");
2298            let expected_hints = vec!["1".to_string(), "2".to_string()];
2299            assert_eq!(
2300                expected_hints,
2301                cached_hint_labels(editor),
2302                "Should have hints from both LSP requests made for a big file"
2303            );
2304            assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
2305            assert_eq!(
2306                editor.inlay_hint_cache().version, requests_count,
2307                "LSP queries should've bumped the cache version"
2308            );
2309        });
2310
2311        _ = editor.update(cx, |editor, cx| {
2312            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
2313            editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
2314        });
2315        cx.executor().advance_clock(Duration::from_millis(
2316            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2317        ));
2318        cx.executor().run_until_parked();
2319        let visible_range_after_scrolls = editor_visible_range(&editor, cx);
2320        let visible_line_count = editor
2321            .update(cx, |editor, _| editor.visible_line_count().unwrap())
2322            .unwrap();
2323        let selection_in_cached_range = editor
2324            .update(cx, |editor, cx| {
2325                let ranges = lsp_request_ranges
2326                    .lock()
2327                    .drain(..)
2328                    .sorted_by_key(|r| r.start)
2329                    .collect::<Vec<_>>();
2330                assert_eq!(
2331                    ranges.len(),
2332                    2,
2333                    "Should query 2 ranges after both scrolls, but got: {ranges:?}"
2334                );
2335                let first_scroll = &ranges[0];
2336                let second_scroll = &ranges[1];
2337                assert_eq!(
2338                    first_scroll.end, second_scroll.start,
2339                    "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
2340                );
2341                assert_eq!(
2342                first_scroll.start, expected_initial_query_range_end,
2343                "First scroll should start the query right after the end of the original scroll",
2344            );
2345                assert_eq!(
2346                second_scroll.end,
2347                lsp::Position::new(
2348                    visible_range_after_scrolls.end.row
2349                        + visible_line_count.ceil() as u32,
2350                    1,
2351                ),
2352                "Second scroll should query one more screen down after the end of the visible range"
2353            );
2354
2355                let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2356                assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
2357                let expected_hints = vec![
2358                    "1".to_string(),
2359                    "2".to_string(),
2360                    "3".to_string(),
2361                    "4".to_string(),
2362                ];
2363                assert_eq!(
2364                    expected_hints,
2365                    cached_hint_labels(editor),
2366                    "Should have hints from the new LSP response after the edit"
2367                );
2368                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2369                assert_eq!(
2370                    editor.inlay_hint_cache().version,
2371                    lsp_requests,
2372                    "Should update the cache for every LSP response with hints added"
2373                );
2374
2375                let mut selection_in_cached_range = visible_range_after_scrolls.end;
2376                selection_in_cached_range.row -= visible_line_count.ceil() as u32;
2377                selection_in_cached_range
2378            })
2379            .unwrap();
2380
2381        _ = editor.update(cx, |editor, cx| {
2382            editor.change_selections(Some(Autoscroll::center()), cx, |s| {
2383                s.select_ranges([selection_in_cached_range..selection_in_cached_range])
2384            });
2385        });
2386        cx.executor().advance_clock(Duration::from_millis(
2387            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2388        ));
2389        cx.executor().run_until_parked();
2390        _ = editor.update(cx, |_, _| {
2391            let ranges = lsp_request_ranges
2392                .lock()
2393                .drain(..)
2394                .sorted_by_key(|r| r.start)
2395                .collect::<Vec<_>>();
2396            assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
2397            assert_eq!(lsp_request_count.load(Ordering::Acquire), 4);
2398        });
2399
2400        _ = editor.update(cx, |editor, cx| {
2401            editor.handle_input("++++more text++++", cx);
2402        });
2403        cx.executor().advance_clock(Duration::from_millis(
2404            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2405        ));
2406        cx.executor().run_until_parked();
2407        _ = editor.update(cx, |editor, cx| {
2408            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2409            ranges.sort_by_key(|r| r.start);
2410
2411            assert_eq!(ranges.len(), 3,
2412                "On edit, should scroll to selection and query a range around it: visible + same range above and below. Instead, got query ranges {ranges:?}");
2413            let above_query_range = &ranges[0];
2414            let visible_query_range = &ranges[1];
2415            let below_query_range = &ranges[2];
2416            assert!(above_query_range.end.character < visible_query_range.start.character || above_query_range.end.line + 1 == visible_query_range.start.line,
2417                "Above range {above_query_range:?} should be before visible range {visible_query_range:?}");
2418            assert!(visible_query_range.end.character < below_query_range.start.character || visible_query_range.end.line  + 1 == below_query_range.start.line,
2419                "Visible range {visible_query_range:?} should be before below range {below_query_range:?}");
2420            assert!(above_query_range.start.line < selection_in_cached_range.row,
2421                "Hints should be queried with the selected range after the query range start");
2422            assert!(below_query_range.end.line > selection_in_cached_range.row,
2423                "Hints should be queried with the selected range before the query range end");
2424            assert!(above_query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
2425                "Hints query range should contain one more screen before");
2426            assert!(below_query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
2427                "Hints query range should contain one more screen after");
2428
2429            let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2430            assert_eq!(lsp_requests, 7, "There should be a visible range and two ranges above and below it queried");
2431            let expected_hints = vec!["5".to_string(), "6".to_string(), "7".to_string()];
2432            assert_eq!(expected_hints, cached_hint_labels(editor),
2433                "Should have hints from the new LSP response after the edit");
2434            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2435            assert_eq!(editor.inlay_hint_cache().version, lsp_requests, "Should update the cache for every LSP response with hints added");
2436        });
2437    }
2438
2439    #[gpui::test(iterations = 10)]
2440    async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
2441        init_test(cx, |settings| {
2442            settings.defaults.inlay_hints = Some(InlayHintSettings {
2443                enabled: true,
2444                show_type_hints: true,
2445                show_parameter_hints: true,
2446                show_other_hints: true,
2447            })
2448        });
2449
2450        let mut language = Language::new(
2451            LanguageConfig {
2452                name: "Rust".into(),
2453                path_suffixes: vec!["rs".to_string()],
2454                ..Default::default()
2455            },
2456            Some(tree_sitter_rust::language()),
2457        );
2458        let mut fake_servers = language
2459            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2460                capabilities: lsp::ServerCapabilities {
2461                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2462                    ..Default::default()
2463                },
2464                ..Default::default()
2465            }))
2466            .await;
2467        let language = Arc::new(language);
2468        let fs = FakeFs::new(cx.background_executor.clone());
2469        fs.insert_tree(
2470                "/a",
2471                json!({
2472                    "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2473                    "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2474                }),
2475            )
2476            .await;
2477        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2478        project.update(cx, |project, _| {
2479            project.languages().add(Arc::clone(&language))
2480        });
2481        let worktree_id = project.update(cx, |project, cx| {
2482            project.worktrees().next().unwrap().read(cx).id()
2483        });
2484
2485        let buffer_1 = project
2486            .update(cx, |project, cx| {
2487                project.open_buffer((worktree_id, "main.rs"), cx)
2488            })
2489            .await
2490            .unwrap();
2491        let buffer_2 = project
2492            .update(cx, |project, cx| {
2493                project.open_buffer((worktree_id, "other.rs"), cx)
2494            })
2495            .await
2496            .unwrap();
2497        let multibuffer = cx.new_model(|cx| {
2498            let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
2499            multibuffer.push_excerpts(
2500                buffer_1.clone(),
2501                [
2502                    ExcerptRange {
2503                        context: Point::new(0, 0)..Point::new(2, 0),
2504                        primary: None,
2505                    },
2506                    ExcerptRange {
2507                        context: Point::new(4, 0)..Point::new(11, 0),
2508                        primary: None,
2509                    },
2510                    ExcerptRange {
2511                        context: Point::new(22, 0)..Point::new(33, 0),
2512                        primary: None,
2513                    },
2514                    ExcerptRange {
2515                        context: Point::new(44, 0)..Point::new(55, 0),
2516                        primary: None,
2517                    },
2518                    ExcerptRange {
2519                        context: Point::new(56, 0)..Point::new(66, 0),
2520                        primary: None,
2521                    },
2522                    ExcerptRange {
2523                        context: Point::new(67, 0)..Point::new(77, 0),
2524                        primary: None,
2525                    },
2526                ],
2527                cx,
2528            );
2529            multibuffer.push_excerpts(
2530                buffer_2.clone(),
2531                [
2532                    ExcerptRange {
2533                        context: Point::new(0, 1)..Point::new(2, 1),
2534                        primary: None,
2535                    },
2536                    ExcerptRange {
2537                        context: Point::new(4, 1)..Point::new(11, 1),
2538                        primary: None,
2539                    },
2540                    ExcerptRange {
2541                        context: Point::new(22, 1)..Point::new(33, 1),
2542                        primary: None,
2543                    },
2544                    ExcerptRange {
2545                        context: Point::new(44, 1)..Point::new(55, 1),
2546                        primary: None,
2547                    },
2548                    ExcerptRange {
2549                        context: Point::new(56, 1)..Point::new(66, 1),
2550                        primary: None,
2551                    },
2552                    ExcerptRange {
2553                        context: Point::new(67, 1)..Point::new(77, 1),
2554                        primary: None,
2555                    },
2556                ],
2557                cx,
2558            );
2559            multibuffer
2560        });
2561
2562        cx.executor().run_until_parked();
2563        let editor =
2564            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
2565        let editor_edited = Arc::new(AtomicBool::new(false));
2566        let fake_server = fake_servers.next().await.unwrap();
2567        let closure_editor_edited = Arc::clone(&editor_edited);
2568        fake_server
2569            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2570                let task_editor_edited = Arc::clone(&closure_editor_edited);
2571                async move {
2572                    let hint_text = if params.text_document.uri
2573                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2574                    {
2575                        "main hint"
2576                    } else if params.text_document.uri
2577                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2578                    {
2579                        "other hint"
2580                    } else {
2581                        panic!("unexpected uri: {:?}", params.text_document.uri);
2582                    };
2583
2584                    // one hint per excerpt
2585                    let positions = [
2586                        lsp::Position::new(0, 2),
2587                        lsp::Position::new(4, 2),
2588                        lsp::Position::new(22, 2),
2589                        lsp::Position::new(44, 2),
2590                        lsp::Position::new(56, 2),
2591                        lsp::Position::new(67, 2),
2592                    ];
2593                    let out_of_range_hint = lsp::InlayHint {
2594                        position: lsp::Position::new(
2595                            params.range.start.line + 99,
2596                            params.range.start.character + 99,
2597                        ),
2598                        label: lsp::InlayHintLabel::String(
2599                            "out of excerpt range, should be ignored".to_string(),
2600                        ),
2601                        kind: None,
2602                        text_edits: None,
2603                        tooltip: None,
2604                        padding_left: None,
2605                        padding_right: None,
2606                        data: None,
2607                    };
2608
2609                    let edited = task_editor_edited.load(Ordering::Acquire);
2610                    Ok(Some(
2611                        std::iter::once(out_of_range_hint)
2612                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2613                                lsp::InlayHint {
2614                                    position,
2615                                    label: lsp::InlayHintLabel::String(format!(
2616                                        "{hint_text}{} #{i}",
2617                                        if edited { "(edited)" } else { "" },
2618                                    )),
2619                                    kind: None,
2620                                    text_edits: None,
2621                                    tooltip: None,
2622                                    padding_left: None,
2623                                    padding_right: None,
2624                                    data: None,
2625                                }
2626                            }))
2627                            .collect(),
2628                    ))
2629                }
2630            })
2631            .next()
2632            .await;
2633        cx.executor().run_until_parked();
2634
2635        _ = editor.update(cx, |editor, cx| {
2636                let expected_hints = vec![
2637                    "main hint #0".to_string(),
2638                    "main hint #1".to_string(),
2639                    "main hint #2".to_string(),
2640                    "main hint #3".to_string(),
2641                    "main hint #4".to_string(),
2642                    "main hint #5".to_string(),
2643                ];
2644                assert_eq!(
2645                    expected_hints,
2646                    cached_hint_labels(editor),
2647                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2648                );
2649                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2650                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the version");
2651            });
2652
2653        _ = editor.update(cx, |editor, cx| {
2654            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2655                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2656            });
2657            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2658                s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2659            });
2660            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2661                s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2662            });
2663        });
2664        cx.executor().run_until_parked();
2665        _ = editor.update(cx, |editor, cx| {
2666                let expected_hints = vec![
2667                    "main hint #0".to_string(),
2668                    "main hint #1".to_string(),
2669                    "main hint #2".to_string(),
2670                    "main hint #3".to_string(),
2671                    "main hint #4".to_string(),
2672                    "main hint #5".to_string(),
2673                    "other hint #0".to_string(),
2674                    "other hint #1".to_string(),
2675                    "other hint #2".to_string(),
2676                ];
2677                assert_eq!(expected_hints, cached_hint_labels(editor),
2678                    "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2679                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2680                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
2681                    "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
2682            });
2683
2684        _ = editor.update(cx, |editor, cx| {
2685            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2686                s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2687            });
2688        });
2689        cx.executor().advance_clock(Duration::from_millis(
2690            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2691        ));
2692        cx.executor().run_until_parked();
2693        let last_scroll_update_version = editor.update(cx, |editor, cx| {
2694                let expected_hints = vec![
2695                    "main hint #0".to_string(),
2696                    "main hint #1".to_string(),
2697                    "main hint #2".to_string(),
2698                    "main hint #3".to_string(),
2699                    "main hint #4".to_string(),
2700                    "main hint #5".to_string(),
2701                    "other hint #0".to_string(),
2702                    "other hint #1".to_string(),
2703                    "other hint #2".to_string(),
2704                    "other hint #3".to_string(),
2705                    "other hint #4".to_string(),
2706                    "other hint #5".to_string(),
2707                ];
2708                assert_eq!(expected_hints, cached_hint_labels(editor),
2709                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2710                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2711                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
2712                expected_hints.len()
2713            }).unwrap();
2714
2715        _ = editor.update(cx, |editor, cx| {
2716            editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2717                s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2718            });
2719        });
2720        cx.executor().run_until_parked();
2721        _ = editor.update(cx, |editor, cx| {
2722                let expected_hints = vec![
2723                    "main hint #0".to_string(),
2724                    "main hint #1".to_string(),
2725                    "main hint #2".to_string(),
2726                    "main hint #3".to_string(),
2727                    "main hint #4".to_string(),
2728                    "main hint #5".to_string(),
2729                    "other hint #0".to_string(),
2730                    "other hint #1".to_string(),
2731                    "other hint #2".to_string(),
2732                    "other hint #3".to_string(),
2733                    "other hint #4".to_string(),
2734                    "other hint #5".to_string(),
2735                ];
2736                assert_eq!(expected_hints, cached_hint_labels(editor),
2737                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2738                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2739                assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
2740            });
2741
2742        editor_edited.store(true, Ordering::Release);
2743        _ = editor.update(cx, |editor, cx| {
2744            editor.change_selections(None, cx, |s| {
2745                // TODO if this gets set to hint boundary (e.g. 56) we sometimes get an extra cache version bump, why?
2746                s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
2747            });
2748            editor.handle_input("++++more text++++", cx);
2749        });
2750        cx.executor().run_until_parked();
2751        _ = editor.update(cx, |editor, cx| {
2752            let expected_hints = vec![
2753                "main hint(edited) #0".to_string(),
2754                "main hint(edited) #1".to_string(),
2755                "main hint(edited) #2".to_string(),
2756                "main hint(edited) #3".to_string(),
2757                "main hint(edited) #4".to_string(),
2758                "main hint(edited) #5".to_string(),
2759                "other hint(edited) #0".to_string(),
2760                "other hint(edited) #1".to_string(),
2761            ];
2762            assert_eq!(
2763                expected_hints,
2764                cached_hint_labels(editor),
2765                "After multibuffer edit, editor gets scolled back to the last selection; \
2766    all hints should be invalidated and required for all of its visible excerpts"
2767            );
2768            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2769
2770            let current_cache_version = editor.inlay_hint_cache().version;
2771            assert_eq!(
2772                current_cache_version,
2773                last_scroll_update_version + expected_hints.len(),
2774                "We should have updated cache N times == N of new hints arrived (separately from each excerpt)"
2775            );
2776        });
2777    }
2778
2779    #[gpui::test]
2780    async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
2781        init_test(cx, |settings| {
2782            settings.defaults.inlay_hints = Some(InlayHintSettings {
2783                enabled: true,
2784                show_type_hints: false,
2785                show_parameter_hints: false,
2786                show_other_hints: false,
2787            })
2788        });
2789
2790        let mut language = Language::new(
2791            LanguageConfig {
2792                name: "Rust".into(),
2793                path_suffixes: vec!["rs".to_string()],
2794                ..Default::default()
2795            },
2796            Some(tree_sitter_rust::language()),
2797        );
2798        let mut fake_servers = language
2799            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2800                capabilities: lsp::ServerCapabilities {
2801                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2802                    ..Default::default()
2803                },
2804                ..Default::default()
2805            }))
2806            .await;
2807        let language = Arc::new(language);
2808        let fs = FakeFs::new(cx.background_executor.clone());
2809        fs.insert_tree(
2810            "/a",
2811            json!({
2812                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2813                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2814            }),
2815        )
2816        .await;
2817        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2818        project.update(cx, |project, _| {
2819            project.languages().add(Arc::clone(&language))
2820        });
2821        let worktree_id = project.update(cx, |project, cx| {
2822            project.worktrees().next().unwrap().read(cx).id()
2823        });
2824
2825        let buffer_1 = project
2826            .update(cx, |project, cx| {
2827                project.open_buffer((worktree_id, "main.rs"), cx)
2828            })
2829            .await
2830            .unwrap();
2831        let buffer_2 = project
2832            .update(cx, |project, cx| {
2833                project.open_buffer((worktree_id, "other.rs"), cx)
2834            })
2835            .await
2836            .unwrap();
2837        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
2838        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2839            let buffer_1_excerpts = multibuffer.push_excerpts(
2840                buffer_1.clone(),
2841                [ExcerptRange {
2842                    context: Point::new(0, 0)..Point::new(2, 0),
2843                    primary: None,
2844                }],
2845                cx,
2846            );
2847            let buffer_2_excerpts = multibuffer.push_excerpts(
2848                buffer_2.clone(),
2849                [ExcerptRange {
2850                    context: Point::new(0, 1)..Point::new(2, 1),
2851                    primary: None,
2852                }],
2853                cx,
2854            );
2855            (buffer_1_excerpts, buffer_2_excerpts)
2856        });
2857
2858        assert!(!buffer_1_excerpts.is_empty());
2859        assert!(!buffer_2_excerpts.is_empty());
2860
2861        cx.executor().run_until_parked();
2862        let editor =
2863            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
2864        let editor_edited = Arc::new(AtomicBool::new(false));
2865        let fake_server = fake_servers.next().await.unwrap();
2866        let closure_editor_edited = Arc::clone(&editor_edited);
2867        fake_server
2868            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2869                let task_editor_edited = Arc::clone(&closure_editor_edited);
2870                async move {
2871                    let hint_text = if params.text_document.uri
2872                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2873                    {
2874                        "main hint"
2875                    } else if params.text_document.uri
2876                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2877                    {
2878                        "other hint"
2879                    } else {
2880                        panic!("unexpected uri: {:?}", params.text_document.uri);
2881                    };
2882
2883                    let positions = [
2884                        lsp::Position::new(0, 2),
2885                        lsp::Position::new(4, 2),
2886                        lsp::Position::new(22, 2),
2887                        lsp::Position::new(44, 2),
2888                        lsp::Position::new(56, 2),
2889                        lsp::Position::new(67, 2),
2890                    ];
2891                    let out_of_range_hint = lsp::InlayHint {
2892                        position: lsp::Position::new(
2893                            params.range.start.line + 99,
2894                            params.range.start.character + 99,
2895                        ),
2896                        label: lsp::InlayHintLabel::String(
2897                            "out of excerpt range, should be ignored".to_string(),
2898                        ),
2899                        kind: None,
2900                        text_edits: None,
2901                        tooltip: None,
2902                        padding_left: None,
2903                        padding_right: None,
2904                        data: None,
2905                    };
2906
2907                    let edited = task_editor_edited.load(Ordering::Acquire);
2908                    Ok(Some(
2909                        std::iter::once(out_of_range_hint)
2910                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2911                                lsp::InlayHint {
2912                                    position,
2913                                    label: lsp::InlayHintLabel::String(format!(
2914                                        "{hint_text}{} #{i}",
2915                                        if edited { "(edited)" } else { "" },
2916                                    )),
2917                                    kind: None,
2918                                    text_edits: None,
2919                                    tooltip: None,
2920                                    padding_left: None,
2921                                    padding_right: None,
2922                                    data: None,
2923                                }
2924                            }))
2925                            .collect(),
2926                    ))
2927                }
2928            })
2929            .next()
2930            .await;
2931        cx.executor().run_until_parked();
2932
2933        _ = editor.update(cx, |editor, cx| {
2934            assert_eq!(
2935                vec!["main hint #0".to_string(), "other hint #0".to_string()],
2936                cached_hint_labels(editor),
2937                "Cache should update for both excerpts despite hints display was disabled"
2938            );
2939            assert!(
2940                visible_hint_labels(editor, cx).is_empty(),
2941                "All hints are disabled and should not be shown despite being present in the cache"
2942            );
2943            assert_eq!(
2944                editor.inlay_hint_cache().version,
2945                2,
2946                "Cache should update once per excerpt query"
2947            );
2948        });
2949
2950        _ = editor.update(cx, |editor, cx| {
2951            editor.buffer().update(cx, |multibuffer, cx| {
2952                multibuffer.remove_excerpts(buffer_2_excerpts, cx)
2953            })
2954        });
2955        cx.executor().run_until_parked();
2956        _ = editor.update(cx, |editor, cx| {
2957            assert_eq!(
2958                vec!["main hint #0".to_string()],
2959                cached_hint_labels(editor),
2960                "For the removed excerpt, should clean corresponding cached hints"
2961            );
2962            assert!(
2963                visible_hint_labels(editor, cx).is_empty(),
2964                "All hints are disabled and should not be shown despite being present in the cache"
2965            );
2966            assert_eq!(
2967                editor.inlay_hint_cache().version,
2968                3,
2969                "Excerpt removal should trigger a cache update"
2970            );
2971        });
2972
2973        update_test_language_settings(cx, |settings| {
2974            settings.defaults.inlay_hints = Some(InlayHintSettings {
2975                enabled: true,
2976                show_type_hints: true,
2977                show_parameter_hints: true,
2978                show_other_hints: true,
2979            })
2980        });
2981        cx.executor().run_until_parked();
2982        _ = editor.update(cx, |editor, cx| {
2983            let expected_hints = vec!["main hint #0".to_string()];
2984            assert_eq!(
2985                expected_hints,
2986                cached_hint_labels(editor),
2987                "Hint display settings change should not change the cache"
2988            );
2989            assert_eq!(
2990                expected_hints,
2991                visible_hint_labels(editor, cx),
2992                "Settings change should make cached hints visible"
2993            );
2994            assert_eq!(
2995                editor.inlay_hint_cache().version,
2996                4,
2997                "Settings change should trigger a cache update"
2998            );
2999        });
3000    }
3001
3002    #[gpui::test]
3003    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
3004        init_test(cx, |settings| {
3005            settings.defaults.inlay_hints = Some(InlayHintSettings {
3006                enabled: true,
3007                show_type_hints: true,
3008                show_parameter_hints: true,
3009                show_other_hints: true,
3010            })
3011        });
3012
3013        let mut language = Language::new(
3014            LanguageConfig {
3015                name: "Rust".into(),
3016                path_suffixes: vec!["rs".to_string()],
3017                ..Default::default()
3018            },
3019            Some(tree_sitter_rust::language()),
3020        );
3021        let mut fake_servers = language
3022            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3023                capabilities: lsp::ServerCapabilities {
3024                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3025                    ..Default::default()
3026                },
3027                ..Default::default()
3028            }))
3029            .await;
3030        let fs = FakeFs::new(cx.background_executor.clone());
3031        fs.insert_tree(
3032            "/a",
3033            json!({
3034                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
3035                "other.rs": "// Test file",
3036            }),
3037        )
3038        .await;
3039        let project = Project::test(fs, ["/a".as_ref()], cx).await;
3040        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3041        let buffer = project
3042            .update(cx, |project, cx| {
3043                project.open_local_buffer("/a/main.rs", cx)
3044            })
3045            .await
3046            .unwrap();
3047        cx.executor().run_until_parked();
3048        cx.executor().start_waiting();
3049        let fake_server = fake_servers.next().await.unwrap();
3050        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
3051        let lsp_request_count = Arc::new(AtomicU32::new(0));
3052        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
3053        fake_server
3054            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
3055                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
3056                async move {
3057                    assert_eq!(
3058                        params.text_document.uri,
3059                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
3060                    );
3061                    let query_start = params.range.start;
3062                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
3063                    Ok(Some(vec![lsp::InlayHint {
3064                        position: query_start,
3065                        label: lsp::InlayHintLabel::String(i.to_string()),
3066                        kind: None,
3067                        text_edits: None,
3068                        tooltip: None,
3069                        padding_left: None,
3070                        padding_right: None,
3071                        data: None,
3072                    }]))
3073                }
3074            })
3075            .next()
3076            .await;
3077
3078        cx.executor().run_until_parked();
3079        _ = editor.update(cx, |editor, cx| {
3080            editor.change_selections(None, cx, |s| {
3081                s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3082            })
3083        });
3084        cx.executor().run_until_parked();
3085        _ = editor.update(cx, |editor, cx| {
3086            let expected_hints = vec!["1".to_string()];
3087            assert_eq!(expected_hints, cached_hint_labels(editor));
3088            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3089            assert_eq!(editor.inlay_hint_cache().version, 1);
3090        });
3091    }
3092
3093    #[gpui::test]
3094    async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) {
3095        init_test(cx, |settings| {
3096            settings.defaults.inlay_hints = Some(InlayHintSettings {
3097                enabled: false,
3098                show_type_hints: true,
3099                show_parameter_hints: true,
3100                show_other_hints: true,
3101            })
3102        });
3103
3104        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
3105
3106        _ = editor.update(cx, |editor, cx| {
3107            editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3108        });
3109        cx.executor().start_waiting();
3110        let lsp_request_count = Arc::new(AtomicU32::new(0));
3111        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
3112        fake_server
3113            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
3114                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
3115                async move {
3116                    assert_eq!(
3117                        params.text_document.uri,
3118                        lsp::Url::from_file_path(file_with_hints).unwrap(),
3119                    );
3120
3121                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
3122                    Ok(Some(vec![lsp::InlayHint {
3123                        position: lsp::Position::new(0, i),
3124                        label: lsp::InlayHintLabel::String(i.to_string()),
3125                        kind: None,
3126                        text_edits: None,
3127                        tooltip: None,
3128                        padding_left: None,
3129                        padding_right: None,
3130                        data: None,
3131                    }]))
3132                }
3133            })
3134            .next()
3135            .await;
3136        cx.executor().run_until_parked();
3137        _ = editor.update(cx, |editor, cx| {
3138            let expected_hints = vec!["1".to_string()];
3139            assert_eq!(
3140                expected_hints,
3141                cached_hint_labels(editor),
3142                "Should display inlays after toggle despite them disabled in settings"
3143            );
3144            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3145            assert_eq!(
3146                editor.inlay_hint_cache().version,
3147                1,
3148                "First toggle should be cache's first update"
3149            );
3150        });
3151
3152        _ = editor.update(cx, |editor, cx| {
3153            editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3154        });
3155        cx.executor().run_until_parked();
3156        _ = editor.update(cx, |editor, cx| {
3157            assert!(
3158                cached_hint_labels(editor).is_empty(),
3159                "Should clear hints after 2nd toggle"
3160            );
3161            assert!(visible_hint_labels(editor, cx).is_empty());
3162            assert_eq!(editor.inlay_hint_cache().version, 2);
3163        });
3164
3165        update_test_language_settings(cx, |settings| {
3166            settings.defaults.inlay_hints = Some(InlayHintSettings {
3167                enabled: true,
3168                show_type_hints: true,
3169                show_parameter_hints: true,
3170                show_other_hints: true,
3171            })
3172        });
3173        cx.executor().run_until_parked();
3174        _ = editor.update(cx, |editor, cx| {
3175            let expected_hints = vec!["2".to_string()];
3176            assert_eq!(
3177                expected_hints,
3178                cached_hint_labels(editor),
3179                "Should query LSP hints for the 2nd time after enabling hints in settings"
3180            );
3181            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3182            assert_eq!(editor.inlay_hint_cache().version, 3);
3183        });
3184
3185        _ = editor.update(cx, |editor, cx| {
3186            editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3187        });
3188        cx.executor().run_until_parked();
3189        _ = editor.update(cx, |editor, cx| {
3190            assert!(
3191                cached_hint_labels(editor).is_empty(),
3192                "Should clear hints after enabling in settings and a 3rd toggle"
3193            );
3194            assert!(visible_hint_labels(editor, cx).is_empty());
3195            assert_eq!(editor.inlay_hint_cache().version, 4);
3196        });
3197
3198        _ = editor.update(cx, |editor, cx| {
3199            editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3200        });
3201        cx.executor().run_until_parked();
3202        _ = editor.update(cx, |editor, cx| {
3203            let expected_hints = vec!["3".to_string()];
3204            assert_eq!(
3205                expected_hints,
3206                cached_hint_labels(editor),
3207                "Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
3208            );
3209            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3210            assert_eq!(editor.inlay_hint_cache().version, 5);
3211        });
3212    }
3213
3214    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
3215        cx.update(|cx| {
3216            let settings_store = SettingsStore::test(cx);
3217            cx.set_global(settings_store);
3218            theme::init(theme::LoadThemes::JustBase, cx);
3219            client::init_settings(cx);
3220            language::init(cx);
3221            Project::init_settings(cx);
3222            workspace::init_settings(cx);
3223            crate::init(cx);
3224        });
3225
3226        update_test_language_settings(cx, f);
3227    }
3228
3229    async fn prepare_test_objects(
3230        cx: &mut TestAppContext,
3231    ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
3232        let mut language = Language::new(
3233            LanguageConfig {
3234                name: "Rust".into(),
3235                path_suffixes: vec!["rs".to_string()],
3236                ..Default::default()
3237            },
3238            Some(tree_sitter_rust::language()),
3239        );
3240        let mut fake_servers = language
3241            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3242                capabilities: lsp::ServerCapabilities {
3243                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3244                    ..Default::default()
3245                },
3246                ..Default::default()
3247            }))
3248            .await;
3249
3250        let fs = FakeFs::new(cx.background_executor.clone());
3251        fs.insert_tree(
3252            "/a",
3253            json!({
3254                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
3255                "other.rs": "// Test file",
3256            }),
3257        )
3258        .await;
3259
3260        let project = Project::test(fs, ["/a".as_ref()], cx).await;
3261        _ = project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3262        let buffer = project
3263            .update(cx, |project, cx| {
3264                project.open_local_buffer("/a/main.rs", cx)
3265            })
3266            .await
3267            .unwrap();
3268        cx.executor().run_until_parked();
3269        cx.executor().start_waiting();
3270        let fake_server = fake_servers.next().await.unwrap();
3271        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
3272
3273        _ = editor.update(cx, |editor, cx| {
3274            assert!(cached_hint_labels(editor).is_empty());
3275            assert!(visible_hint_labels(editor, cx).is_empty());
3276            assert_eq!(editor.inlay_hint_cache().version, 0);
3277        });
3278
3279        ("/a/main.rs", editor, fake_server)
3280    }
3281
3282    pub fn cached_hint_labels(editor: &Editor) -> Vec<String> {
3283        let mut labels = Vec::new();
3284        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
3285            let excerpt_hints = excerpt_hints.read();
3286            for id in &excerpt_hints.ordered_hints {
3287                labels.push(excerpt_hints.hints_by_id[id].text());
3288            }
3289        }
3290
3291        labels.sort();
3292        labels
3293    }
3294
3295    pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, Editor>) -> Vec<String> {
3296        let mut hints = editor
3297            .visible_inlay_hints(cx)
3298            .into_iter()
3299            .map(|hint| hint.text.to_string())
3300            .collect::<Vec<_>>();
3301        hints.sort();
3302        hints
3303    }
3304}