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