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, Default)]
  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: HashSet<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 = HashSet::default();
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            remove_from_visible.extend(remove_from_cache.iter().cloned());
1078        }
1079    }
1080
1081    if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() {
1082        None
1083    } else {
1084        Some(ExcerptHintsUpdate {
1085            excerpt_id,
1086            remove_from_visible,
1087            remove_from_cache,
1088            add_to_cache,
1089        })
1090    }
1091}
1092
1093fn contains_position(
1094    range: &Range<language::Anchor>,
1095    position: language::Anchor,
1096    buffer_snapshot: &BufferSnapshot,
1097) -> bool {
1098    range.start.cmp(&position, buffer_snapshot).is_le()
1099        && range.end.cmp(&position, buffer_snapshot).is_ge()
1100}
1101
1102fn apply_hint_update(
1103    editor: &mut Editor,
1104    new_update: ExcerptHintsUpdate,
1105    query: ExcerptQuery,
1106    invalidate: bool,
1107    buffer_snapshot: BufferSnapshot,
1108    multi_buffer_snapshot: MultiBufferSnapshot,
1109    cx: &mut ViewContext<'_, Editor>,
1110) {
1111    let cached_excerpt_hints = editor
1112        .inlay_hint_cache
1113        .hints
1114        .entry(new_update.excerpt_id)
1115        .or_insert_with(|| {
1116            Arc::new(RwLock::new(CachedExcerptHints {
1117                version: query.cache_version,
1118                buffer_version: buffer_snapshot.version().clone(),
1119                buffer_id: query.buffer_id,
1120                ordered_hints: Vec::new(),
1121                hints_by_id: HashMap::default(),
1122            }))
1123        });
1124    let mut cached_excerpt_hints = cached_excerpt_hints.write();
1125    match query.cache_version.cmp(&cached_excerpt_hints.version) {
1126        cmp::Ordering::Less => return,
1127        cmp::Ordering::Greater | cmp::Ordering::Equal => {
1128            cached_excerpt_hints.version = query.cache_version;
1129        }
1130    }
1131
1132    let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
1133    cached_excerpt_hints
1134        .ordered_hints
1135        .retain(|hint_id| !new_update.remove_from_cache.contains(hint_id));
1136    cached_excerpt_hints
1137        .hints_by_id
1138        .retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id));
1139    let mut splice = InlaySplice::default();
1140    splice.to_remove.extend(new_update.remove_from_visible);
1141    for new_hint in new_update.add_to_cache {
1142        let insert_position = match cached_excerpt_hints
1143            .ordered_hints
1144            .binary_search_by(|probe| {
1145                cached_excerpt_hints.hints_by_id[probe]
1146                    .position
1147                    .cmp(&new_hint.position, &buffer_snapshot)
1148            }) {
1149            Ok(i) => {
1150                let mut insert_position = Some(i);
1151                for id in &cached_excerpt_hints.ordered_hints[i..] {
1152                    let cached_hint = &cached_excerpt_hints.hints_by_id[id];
1153                    if new_hint
1154                        .position
1155                        .cmp(&cached_hint.position, &buffer_snapshot)
1156                        .is_gt()
1157                    {
1158                        break;
1159                    }
1160                    if cached_hint.text() == new_hint.text() {
1161                        insert_position = None;
1162                        break;
1163                    }
1164                }
1165                insert_position
1166            }
1167            Err(i) => Some(i),
1168        };
1169
1170        if let Some(insert_position) = insert_position {
1171            let new_inlay_id = post_inc(&mut editor.next_inlay_id);
1172            if editor
1173                .inlay_hint_cache
1174                .allowed_hint_kinds
1175                .contains(&new_hint.kind)
1176            {
1177                let new_hint_position =
1178                    multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
1179                splice
1180                    .to_insert
1181                    .push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
1182            }
1183            let new_id = InlayId::Hint(new_inlay_id);
1184            cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
1185            cached_excerpt_hints
1186                .ordered_hints
1187                .insert(insert_position, new_id);
1188            cached_inlays_changed = true;
1189        }
1190    }
1191    cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
1192    drop(cached_excerpt_hints);
1193
1194    if invalidate {
1195        let mut outdated_excerpt_caches = HashSet::default();
1196        for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
1197            let excerpt_hints = excerpt_hints.read();
1198            if excerpt_hints.buffer_id == query.buffer_id
1199                && excerpt_id != &query.excerpt_id
1200                && buffer_snapshot
1201                    .version()
1202                    .changed_since(&excerpt_hints.buffer_version)
1203            {
1204                outdated_excerpt_caches.insert(*excerpt_id);
1205                splice
1206                    .to_remove
1207                    .extend(excerpt_hints.ordered_hints.iter().copied());
1208            }
1209        }
1210        cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
1211        editor
1212            .inlay_hint_cache
1213            .hints
1214            .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
1215    }
1216
1217    let InlaySplice {
1218        to_remove,
1219        to_insert,
1220    } = splice;
1221    let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
1222    if cached_inlays_changed || displayed_inlays_changed {
1223        editor.inlay_hint_cache.version += 1;
1224    }
1225    if displayed_inlays_changed {
1226        editor.splice_inlay_hints(to_remove, to_insert, cx)
1227    }
1228}
1229
1230#[cfg(test)]
1231pub mod tests {
1232    use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
1233
1234    use crate::{
1235        scroll::{scroll_amount::ScrollAmount, Autoscroll},
1236        ExcerptRange,
1237    };
1238    use futures::StreamExt;
1239    use gpui::{Context, TestAppContext, WindowHandle};
1240    use itertools::Itertools;
1241    use language::{
1242        language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
1243        LanguageConfig, LanguageMatcher,
1244    };
1245    use lsp::FakeLanguageServer;
1246    use parking_lot::Mutex;
1247    use project::{FakeFs, Project};
1248    use serde_json::json;
1249    use settings::SettingsStore;
1250    use text::{Point, ToPoint};
1251
1252    use crate::editor_tests::update_test_language_settings;
1253
1254    use super::*;
1255
1256    #[gpui::test]
1257    async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) {
1258        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1259        init_test(cx, |settings| {
1260            settings.defaults.inlay_hints = Some(InlayHintSettings {
1261                enabled: true,
1262                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1263                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1264                show_other_hints: allowed_hint_kinds.contains(&None),
1265            })
1266        });
1267
1268        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1269        let lsp_request_count = Arc::new(AtomicU32::new(0));
1270        fake_server
1271            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1272                let task_lsp_request_count = Arc::clone(&lsp_request_count);
1273                async move {
1274                    assert_eq!(
1275                        params.text_document.uri,
1276                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1277                    );
1278                    let current_call_id =
1279                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1280                    let mut new_hints = Vec::with_capacity(2 * current_call_id as usize);
1281                    for _ in 0..2 {
1282                        let mut i = current_call_id;
1283                        loop {
1284                            new_hints.push(lsp::InlayHint {
1285                                position: lsp::Position::new(0, i),
1286                                label: lsp::InlayHintLabel::String(i.to_string()),
1287                                kind: None,
1288                                text_edits: None,
1289                                tooltip: None,
1290                                padding_left: None,
1291                                padding_right: None,
1292                                data: None,
1293                            });
1294                            if i == 0 {
1295                                break;
1296                            }
1297                            i -= 1;
1298                        }
1299                    }
1300
1301                    Ok(Some(new_hints))
1302                }
1303            })
1304            .next()
1305            .await;
1306        cx.executor().run_until_parked();
1307
1308        let mut edits_made = 1;
1309        editor
1310            .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            .unwrap();
1329
1330        editor
1331            .update(cx, |editor, cx| {
1332                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1333                editor.handle_input("some change", cx);
1334                edits_made += 1;
1335            })
1336            .unwrap();
1337        cx.executor().run_until_parked();
1338        editor
1339            .update(cx, |editor, cx| {
1340                let expected_hints = vec!["0".to_string(), "1".to_string()];
1341                assert_eq!(
1342                    expected_hints,
1343                    cached_hint_labels(editor),
1344                    "Should get new hints after an edit"
1345                );
1346                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1347                let inlay_cache = editor.inlay_hint_cache();
1348                assert_eq!(
1349                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1350                    "Cache should use editor settings to get the allowed hint kinds"
1351                );
1352                assert_eq!(
1353                    inlay_cache.version, edits_made,
1354                    "The editor update the cache version after every cache/view change"
1355                );
1356            })
1357            .unwrap();
1358
1359        fake_server
1360            .request::<lsp::request::InlayHintRefreshRequest>(())
1361            .await
1362            .expect("inlay refresh request failed");
1363        edits_made += 1;
1364        cx.executor().run_until_parked();
1365        editor
1366            .update(cx, |editor, cx| {
1367                let expected_hints = vec!["0".to_string(), "1".to_string(), "2".to_string()];
1368                assert_eq!(
1369                    expected_hints,
1370                    cached_hint_labels(editor),
1371                    "Should get new hints after hint refresh/ request"
1372                );
1373                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1374                let inlay_cache = editor.inlay_hint_cache();
1375                assert_eq!(
1376                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1377                    "Cache should use editor settings to get the allowed hint kinds"
1378                );
1379                assert_eq!(
1380                    inlay_cache.version, edits_made,
1381                    "The editor update the cache version after every cache/view change"
1382                );
1383            })
1384            .unwrap();
1385    }
1386
1387    #[gpui::test]
1388    async fn test_cache_update_on_lsp_completion_tasks(cx: &mut gpui::TestAppContext) {
1389        init_test(cx, |settings| {
1390            settings.defaults.inlay_hints = Some(InlayHintSettings {
1391                enabled: true,
1392                show_type_hints: true,
1393                show_parameter_hints: true,
1394                show_other_hints: true,
1395            })
1396        });
1397
1398        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1399        let lsp_request_count = Arc::new(AtomicU32::new(0));
1400        fake_server
1401            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1402                let task_lsp_request_count = Arc::clone(&lsp_request_count);
1403                async move {
1404                    assert_eq!(
1405                        params.text_document.uri,
1406                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1407                    );
1408                    let current_call_id =
1409                        Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1410                    Ok(Some(vec![lsp::InlayHint {
1411                        position: lsp::Position::new(0, current_call_id),
1412                        label: lsp::InlayHintLabel::String(current_call_id.to_string()),
1413                        kind: None,
1414                        text_edits: None,
1415                        tooltip: None,
1416                        padding_left: None,
1417                        padding_right: None,
1418                        data: None,
1419                    }]))
1420                }
1421            })
1422            .next()
1423            .await;
1424        cx.executor().run_until_parked();
1425
1426        let mut edits_made = 1;
1427        editor
1428            .update(cx, |editor, cx| {
1429                let expected_hints = vec!["0".to_string()];
1430                assert_eq!(
1431                    expected_hints,
1432                    cached_hint_labels(editor),
1433                    "Should get its first hints when opening the editor"
1434                );
1435                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1436                assert_eq!(
1437                    editor.inlay_hint_cache().version,
1438                    edits_made,
1439                    "The editor update the cache version after every cache/view change"
1440                );
1441            })
1442            .unwrap();
1443
1444        let progress_token = "test_progress_token";
1445        fake_server
1446            .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
1447                token: lsp::ProgressToken::String(progress_token.to_string()),
1448            })
1449            .await
1450            .expect("work done progress create request failed");
1451        cx.executor().run_until_parked();
1452        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1453            token: lsp::ProgressToken::String(progress_token.to_string()),
1454            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
1455                lsp::WorkDoneProgressBegin::default(),
1456            )),
1457        });
1458        cx.executor().run_until_parked();
1459
1460        editor
1461            .update(cx, |editor, cx| {
1462                let expected_hints = vec!["0".to_string()];
1463                assert_eq!(
1464                    expected_hints,
1465                    cached_hint_labels(editor),
1466                    "Should not update hints while the work task is running"
1467                );
1468                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1469                assert_eq!(
1470                    editor.inlay_hint_cache().version,
1471                    edits_made,
1472                    "Should not update the cache while the work task is running"
1473                );
1474            })
1475            .unwrap();
1476
1477        fake_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
1478            token: lsp::ProgressToken::String(progress_token.to_string()),
1479            value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
1480                lsp::WorkDoneProgressEnd::default(),
1481            )),
1482        });
1483        cx.executor().run_until_parked();
1484
1485        edits_made += 1;
1486        editor
1487            .update(cx, |editor, cx| {
1488                let expected_hints = vec!["1".to_string()];
1489                assert_eq!(
1490                    expected_hints,
1491                    cached_hint_labels(editor),
1492                    "New hints should be queried after the work task is done"
1493                );
1494                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1495                assert_eq!(
1496                    editor.inlay_hint_cache().version,
1497                    edits_made,
1498                    "Cache version should update once after the work task is done"
1499                );
1500            })
1501            .unwrap();
1502    }
1503
1504    #[gpui::test]
1505    async fn test_no_hint_updates_for_unrelated_language_files(cx: &mut gpui::TestAppContext) {
1506        init_test(cx, |settings| {
1507            settings.defaults.inlay_hints = Some(InlayHintSettings {
1508                enabled: true,
1509                show_type_hints: true,
1510                show_parameter_hints: true,
1511                show_other_hints: true,
1512            })
1513        });
1514
1515        let fs = FakeFs::new(cx.background_executor.clone());
1516        fs.insert_tree(
1517                    "/a",
1518                    json!({
1519                        "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
1520                        "other.md": "Test md file with some text",
1521                    }),
1522                )
1523                .await;
1524        let project = Project::test(fs, ["/a".as_ref()], cx).await;
1525
1526        let mut rs_fake_servers = None;
1527        let mut md_fake_servers = None;
1528        for (name, path_suffix) in [("Rust", "rs"), ("Markdown", "md")] {
1529            let mut language = Language::new(
1530                LanguageConfig {
1531                    name: name.into(),
1532                    matcher: LanguageMatcher {
1533                        path_suffixes: vec![path_suffix.to_string()],
1534                        ..Default::default()
1535                    },
1536                    ..Default::default()
1537                },
1538                Some(tree_sitter_rust::language()),
1539            );
1540            let fake_servers = language
1541                .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1542                    name,
1543                    capabilities: lsp::ServerCapabilities {
1544                        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1545                        ..Default::default()
1546                    },
1547                    ..Default::default()
1548                }))
1549                .await;
1550            match name {
1551                "Rust" => rs_fake_servers = Some(fake_servers),
1552                "Markdown" => md_fake_servers = Some(fake_servers),
1553                _ => unreachable!(),
1554            }
1555            project.update(cx, |project, _| {
1556                project.languages().add(Arc::new(language));
1557            });
1558        }
1559
1560        let rs_buffer = project
1561            .update(cx, |project, cx| {
1562                project.open_local_buffer("/a/main.rs", cx)
1563            })
1564            .await
1565            .unwrap();
1566        cx.executor().run_until_parked();
1567        cx.executor().start_waiting();
1568        let rs_fake_server = rs_fake_servers.unwrap().next().await.unwrap();
1569        let rs_editor =
1570            cx.add_window(|cx| Editor::for_buffer(rs_buffer, Some(project.clone()), cx));
1571        let rs_lsp_request_count = Arc::new(AtomicU32::new(0));
1572        rs_fake_server
1573            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1574                let task_lsp_request_count = Arc::clone(&rs_lsp_request_count);
1575                async move {
1576                    assert_eq!(
1577                        params.text_document.uri,
1578                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
1579                    );
1580                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1581                    Ok(Some(vec![lsp::InlayHint {
1582                        position: lsp::Position::new(0, i),
1583                        label: lsp::InlayHintLabel::String(i.to_string()),
1584                        kind: None,
1585                        text_edits: None,
1586                        tooltip: None,
1587                        padding_left: None,
1588                        padding_right: None,
1589                        data: None,
1590                    }]))
1591                }
1592            })
1593            .next()
1594            .await;
1595        cx.executor().run_until_parked();
1596        rs_editor
1597            .update(cx, |editor, cx| {
1598                let expected_hints = vec!["0".to_string()];
1599                assert_eq!(
1600                    expected_hints,
1601                    cached_hint_labels(editor),
1602                    "Should get its first hints when opening the editor"
1603                );
1604                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1605                assert_eq!(
1606                    editor.inlay_hint_cache().version,
1607                    1,
1608                    "Rust editor update the cache version after every cache/view change"
1609                );
1610            })
1611            .unwrap();
1612
1613        cx.executor().run_until_parked();
1614        let md_buffer = project
1615            .update(cx, |project, cx| {
1616                project.open_local_buffer("/a/other.md", cx)
1617            })
1618            .await
1619            .unwrap();
1620        cx.executor().run_until_parked();
1621        cx.executor().start_waiting();
1622        let md_fake_server = md_fake_servers.unwrap().next().await.unwrap();
1623        let md_editor = cx.add_window(|cx| Editor::for_buffer(md_buffer, Some(project), cx));
1624        let md_lsp_request_count = Arc::new(AtomicU32::new(0));
1625        md_fake_server
1626            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1627                let task_lsp_request_count = Arc::clone(&md_lsp_request_count);
1628                async move {
1629                    assert_eq!(
1630                        params.text_document.uri,
1631                        lsp::Url::from_file_path("/a/other.md").unwrap(),
1632                    );
1633                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1634                    Ok(Some(vec![lsp::InlayHint {
1635                        position: lsp::Position::new(0, i),
1636                        label: lsp::InlayHintLabel::String(i.to_string()),
1637                        kind: None,
1638                        text_edits: None,
1639                        tooltip: None,
1640                        padding_left: None,
1641                        padding_right: None,
1642                        data: None,
1643                    }]))
1644                }
1645            })
1646            .next()
1647            .await;
1648        cx.executor().run_until_parked();
1649        md_editor
1650            .update(cx, |editor, cx| {
1651                let expected_hints = vec!["0".to_string()];
1652                assert_eq!(
1653                    expected_hints,
1654                    cached_hint_labels(editor),
1655                    "Markdown editor should have a separate version, repeating Rust editor rules"
1656                );
1657                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1658                assert_eq!(editor.inlay_hint_cache().version, 1);
1659            })
1660            .unwrap();
1661
1662        rs_editor
1663            .update(cx, |editor, cx| {
1664                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1665                editor.handle_input("some rs change", cx);
1666            })
1667            .unwrap();
1668        cx.executor().run_until_parked();
1669        rs_editor
1670            .update(cx, |editor, cx| {
1671                let expected_hints = vec!["1".to_string()];
1672                assert_eq!(
1673                    expected_hints,
1674                    cached_hint_labels(editor),
1675                    "Rust inlay cache should change after the edit"
1676                );
1677                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1678                assert_eq!(
1679                    editor.inlay_hint_cache().version,
1680                    2,
1681                    "Every time hint cache changes, cache version should be incremented"
1682                );
1683            })
1684            .unwrap();
1685        md_editor
1686            .update(cx, |editor, cx| {
1687                let expected_hints = vec!["0".to_string()];
1688                assert_eq!(
1689                    expected_hints,
1690                    cached_hint_labels(editor),
1691                    "Markdown editor should not be affected by Rust editor changes"
1692                );
1693                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1694                assert_eq!(editor.inlay_hint_cache().version, 1);
1695            })
1696            .unwrap();
1697
1698        md_editor
1699            .update(cx, |editor, cx| {
1700                editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1701                editor.handle_input("some md change", cx);
1702            })
1703            .unwrap();
1704        cx.executor().run_until_parked();
1705        md_editor
1706            .update(cx, |editor, cx| {
1707                let expected_hints = vec!["1".to_string()];
1708                assert_eq!(
1709                    expected_hints,
1710                    cached_hint_labels(editor),
1711                    "Rust editor should not be affected by Markdown editor changes"
1712                );
1713                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1714                assert_eq!(editor.inlay_hint_cache().version, 2);
1715            })
1716            .unwrap();
1717        rs_editor
1718            .update(cx, |editor, cx| {
1719                let expected_hints = vec!["1".to_string()];
1720                assert_eq!(
1721                    expected_hints,
1722                    cached_hint_labels(editor),
1723                    "Markdown editor should also change independently"
1724                );
1725                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
1726                assert_eq!(editor.inlay_hint_cache().version, 2);
1727            })
1728            .unwrap();
1729    }
1730
1731    #[gpui::test]
1732    async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) {
1733        let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]);
1734        init_test(cx, |settings| {
1735            settings.defaults.inlay_hints = Some(InlayHintSettings {
1736                enabled: true,
1737                show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1738                show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
1739                show_other_hints: allowed_hint_kinds.contains(&None),
1740            })
1741        });
1742
1743        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
1744        let lsp_request_count = Arc::new(AtomicU32::new(0));
1745        let another_lsp_request_count = Arc::clone(&lsp_request_count);
1746        fake_server
1747            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1748                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
1749                async move {
1750                    Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst);
1751                    assert_eq!(
1752                        params.text_document.uri,
1753                        lsp::Url::from_file_path(file_with_hints).unwrap(),
1754                    );
1755                    Ok(Some(vec![
1756                        lsp::InlayHint {
1757                            position: lsp::Position::new(0, 1),
1758                            label: lsp::InlayHintLabel::String("type hint".to_string()),
1759                            kind: Some(lsp::InlayHintKind::TYPE),
1760                            text_edits: None,
1761                            tooltip: None,
1762                            padding_left: None,
1763                            padding_right: None,
1764                            data: None,
1765                        },
1766                        lsp::InlayHint {
1767                            position: lsp::Position::new(0, 2),
1768                            label: lsp::InlayHintLabel::String("parameter hint".to_string()),
1769                            kind: Some(lsp::InlayHintKind::PARAMETER),
1770                            text_edits: None,
1771                            tooltip: None,
1772                            padding_left: None,
1773                            padding_right: None,
1774                            data: None,
1775                        },
1776                        lsp::InlayHint {
1777                            position: lsp::Position::new(0, 3),
1778                            label: lsp::InlayHintLabel::String("other hint".to_string()),
1779                            kind: None,
1780                            text_edits: None,
1781                            tooltip: None,
1782                            padding_left: None,
1783                            padding_right: None,
1784                            data: None,
1785                        },
1786                    ]))
1787                }
1788            })
1789            .next()
1790            .await;
1791        cx.executor().run_until_parked();
1792
1793        let mut edits_made = 1;
1794        editor
1795            .update(cx, |editor, cx| {
1796                assert_eq!(
1797                    lsp_request_count.load(Ordering::Relaxed),
1798                    1,
1799                    "Should query new hints once"
1800                );
1801                assert_eq!(
1802                    vec![
1803                        "other hint".to_string(),
1804                        "parameter hint".to_string(),
1805                        "type hint".to_string(),
1806                    ],
1807                    cached_hint_labels(editor),
1808                    "Should get its first hints when opening the editor"
1809                );
1810                assert_eq!(
1811                    vec!["other hint".to_string(), "type hint".to_string()],
1812                    visible_hint_labels(editor, cx)
1813                );
1814                let inlay_cache = editor.inlay_hint_cache();
1815                assert_eq!(
1816                    inlay_cache.allowed_hint_kinds, allowed_hint_kinds,
1817                    "Cache should use editor settings to get the allowed hint kinds"
1818                );
1819                assert_eq!(
1820                    inlay_cache.version, edits_made,
1821                    "The editor update the cache version after every cache/view change"
1822                );
1823            })
1824            .unwrap();
1825
1826        fake_server
1827            .request::<lsp::request::InlayHintRefreshRequest>(())
1828            .await
1829            .expect("inlay refresh request failed");
1830        cx.executor().run_until_parked();
1831        editor
1832            .update(cx, |editor, cx| {
1833                assert_eq!(
1834                    lsp_request_count.load(Ordering::Relaxed),
1835                    2,
1836                    "Should load new hints twice"
1837                );
1838                assert_eq!(
1839                    vec![
1840                        "other hint".to_string(),
1841                        "parameter hint".to_string(),
1842                        "type hint".to_string(),
1843                    ],
1844                    cached_hint_labels(editor),
1845                    "Cached hints should not change due to allowed hint kinds settings update"
1846                );
1847                assert_eq!(
1848                    vec!["other hint".to_string(), "type hint".to_string()],
1849                    visible_hint_labels(editor, cx)
1850                );
1851                assert_eq!(
1852                    editor.inlay_hint_cache().version,
1853                    edits_made,
1854                    "Should not update cache version due to new loaded hints being the same"
1855                );
1856            })
1857            .unwrap();
1858
1859        for (new_allowed_hint_kinds, expected_visible_hints) in [
1860            (HashSet::from_iter([None]), vec!["other hint".to_string()]),
1861            (
1862                HashSet::from_iter([Some(InlayHintKind::Type)]),
1863                vec!["type hint".to_string()],
1864            ),
1865            (
1866                HashSet::from_iter([Some(InlayHintKind::Parameter)]),
1867                vec!["parameter hint".to_string()],
1868            ),
1869            (
1870                HashSet::from_iter([None, Some(InlayHintKind::Type)]),
1871                vec!["other hint".to_string(), "type hint".to_string()],
1872            ),
1873            (
1874                HashSet::from_iter([None, Some(InlayHintKind::Parameter)]),
1875                vec!["other hint".to_string(), "parameter hint".to_string()],
1876            ),
1877            (
1878                HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]),
1879                vec!["parameter hint".to_string(), "type hint".to_string()],
1880            ),
1881            (
1882                HashSet::from_iter([
1883                    None,
1884                    Some(InlayHintKind::Type),
1885                    Some(InlayHintKind::Parameter),
1886                ]),
1887                vec![
1888                    "other hint".to_string(),
1889                    "parameter hint".to_string(),
1890                    "type hint".to_string(),
1891                ],
1892            ),
1893        ] {
1894            edits_made += 1;
1895            update_test_language_settings(cx, |settings| {
1896                settings.defaults.inlay_hints = Some(InlayHintSettings {
1897                    enabled: true,
1898                    show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1899                    show_parameter_hints: new_allowed_hint_kinds
1900                        .contains(&Some(InlayHintKind::Parameter)),
1901                    show_other_hints: new_allowed_hint_kinds.contains(&None),
1902                })
1903            });
1904            cx.executor().run_until_parked();
1905            editor.update(cx, |editor, cx| {
1906                assert_eq!(
1907                    lsp_request_count.load(Ordering::Relaxed),
1908                    2,
1909                    "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}"
1910                );
1911                assert_eq!(
1912                    vec![
1913                        "other hint".to_string(),
1914                        "parameter hint".to_string(),
1915                        "type hint".to_string(),
1916                    ],
1917                    cached_hint_labels(editor),
1918                    "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1919                );
1920                assert_eq!(
1921                    expected_visible_hints,
1922                    visible_hint_labels(editor, cx),
1923                    "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}"
1924                );
1925                let inlay_cache = editor.inlay_hint_cache();
1926                assert_eq!(
1927                    inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds,
1928                    "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}"
1929                );
1930                assert_eq!(
1931                    inlay_cache.version, edits_made,
1932                    "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change"
1933                );
1934            }).unwrap();
1935        }
1936
1937        edits_made += 1;
1938        let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]);
1939        update_test_language_settings(cx, |settings| {
1940            settings.defaults.inlay_hints = Some(InlayHintSettings {
1941                enabled: false,
1942                show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
1943                show_parameter_hints: another_allowed_hint_kinds
1944                    .contains(&Some(InlayHintKind::Parameter)),
1945                show_other_hints: another_allowed_hint_kinds.contains(&None),
1946            })
1947        });
1948        cx.executor().run_until_parked();
1949        editor
1950            .update(cx, |editor, cx| {
1951                assert_eq!(
1952                    lsp_request_count.load(Ordering::Relaxed),
1953                    2,
1954                    "Should not load new hints when hints got disabled"
1955                );
1956                assert!(
1957                    cached_hint_labels(editor).is_empty(),
1958                    "Should clear the cache when hints got disabled"
1959                );
1960                assert!(
1961                    visible_hint_labels(editor, cx).is_empty(),
1962                    "Should clear visible hints when hints got disabled"
1963                );
1964                let inlay_cache = editor.inlay_hint_cache();
1965                assert_eq!(
1966                    inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds,
1967                    "Should update its allowed hint kinds even when hints got disabled"
1968                );
1969                assert_eq!(
1970                    inlay_cache.version, edits_made,
1971                    "The editor should update the cache version after hints got disabled"
1972                );
1973            })
1974            .unwrap();
1975
1976        fake_server
1977            .request::<lsp::request::InlayHintRefreshRequest>(())
1978            .await
1979            .expect("inlay refresh request failed");
1980        cx.executor().run_until_parked();
1981        editor.update(cx, |editor, cx| {
1982            assert_eq!(
1983                lsp_request_count.load(Ordering::Relaxed),
1984                2,
1985                "Should not load new hints when they got disabled"
1986            );
1987            assert!(cached_hint_labels(editor).is_empty());
1988            assert!(visible_hint_labels(editor, cx).is_empty());
1989            assert_eq!(
1990                editor.inlay_hint_cache().version, edits_made,
1991                "The editor should not update the cache version after /refresh query without updates"
1992            );
1993        }).unwrap();
1994
1995        let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]);
1996        edits_made += 1;
1997        update_test_language_settings(cx, |settings| {
1998            settings.defaults.inlay_hints = Some(InlayHintSettings {
1999                enabled: true,
2000                show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
2001                show_parameter_hints: final_allowed_hint_kinds
2002                    .contains(&Some(InlayHintKind::Parameter)),
2003                show_other_hints: final_allowed_hint_kinds.contains(&None),
2004            })
2005        });
2006        cx.executor().run_until_parked();
2007        editor
2008            .update(cx, |editor, cx| {
2009                assert_eq!(
2010                    lsp_request_count.load(Ordering::Relaxed),
2011                    3,
2012                    "Should query for new hints when they got re-enabled"
2013                );
2014                assert_eq!(
2015                    vec![
2016                        "other hint".to_string(),
2017                        "parameter hint".to_string(),
2018                        "type hint".to_string(),
2019                    ],
2020                    cached_hint_labels(editor),
2021                    "Should get its cached hints fully repopulated after the hints got re-enabled"
2022                );
2023                assert_eq!(
2024                    vec!["parameter hint".to_string()],
2025                    visible_hint_labels(editor, cx),
2026                    "Should get its visible hints repopulated and filtered after the h"
2027                );
2028                let inlay_cache = editor.inlay_hint_cache();
2029                assert_eq!(
2030                    inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds,
2031                    "Cache should update editor settings when hints got re-enabled"
2032                );
2033                assert_eq!(
2034                    inlay_cache.version, edits_made,
2035                    "Cache should update its version after hints got re-enabled"
2036                );
2037            })
2038            .unwrap();
2039
2040        fake_server
2041            .request::<lsp::request::InlayHintRefreshRequest>(())
2042            .await
2043            .expect("inlay refresh request failed");
2044        cx.executor().run_until_parked();
2045        editor
2046            .update(cx, |editor, cx| {
2047                assert_eq!(
2048                    lsp_request_count.load(Ordering::Relaxed),
2049                    4,
2050                    "Should query for new hints again"
2051                );
2052                assert_eq!(
2053                    vec![
2054                        "other hint".to_string(),
2055                        "parameter hint".to_string(),
2056                        "type hint".to_string(),
2057                    ],
2058                    cached_hint_labels(editor),
2059                );
2060                assert_eq!(
2061                    vec!["parameter hint".to_string()],
2062                    visible_hint_labels(editor, cx),
2063                );
2064                assert_eq!(editor.inlay_hint_cache().version, edits_made);
2065            })
2066            .unwrap();
2067    }
2068
2069    #[gpui::test]
2070    async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) {
2071        init_test(cx, |settings| {
2072            settings.defaults.inlay_hints = Some(InlayHintSettings {
2073                enabled: true,
2074                show_type_hints: true,
2075                show_parameter_hints: true,
2076                show_other_hints: true,
2077            })
2078        });
2079
2080        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
2081        let fake_server = Arc::new(fake_server);
2082        let lsp_request_count = Arc::new(AtomicU32::new(0));
2083        let another_lsp_request_count = Arc::clone(&lsp_request_count);
2084        fake_server
2085            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2086                let task_lsp_request_count = Arc::clone(&another_lsp_request_count);
2087                async move {
2088                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
2089                    assert_eq!(
2090                        params.text_document.uri,
2091                        lsp::Url::from_file_path(file_with_hints).unwrap(),
2092                    );
2093                    Ok(Some(vec![lsp::InlayHint {
2094                        position: lsp::Position::new(0, i),
2095                        label: lsp::InlayHintLabel::String(i.to_string()),
2096                        kind: None,
2097                        text_edits: None,
2098                        tooltip: None,
2099                        padding_left: None,
2100                        padding_right: None,
2101                        data: None,
2102                    }]))
2103                }
2104            })
2105            .next()
2106            .await;
2107
2108        let mut expected_changes = Vec::new();
2109        for change_after_opening in [
2110            "initial change #1",
2111            "initial change #2",
2112            "initial change #3",
2113        ] {
2114            editor
2115                .update(cx, |editor, cx| {
2116                    editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
2117                    editor.handle_input(change_after_opening, cx);
2118                })
2119                .unwrap();
2120            expected_changes.push(change_after_opening);
2121        }
2122
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::Relaxed),
2135                2,
2136                "Should query new hints twice: for editor init and for the last edit that interrupted all others"
2137            );
2138            let expected_hints = vec!["2".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, 1,
2147                "Only one update should be registered in the cache after all cancellations"
2148            );
2149        }).unwrap();
2150
2151        let mut edits = Vec::new();
2152        for async_later_change in [
2153            "another change #1",
2154            "another change #2",
2155            "another change #3",
2156        ] {
2157            expected_changes.push(async_later_change);
2158            let task_editor = editor.clone();
2159            edits.push(cx.spawn(|mut cx| async move {
2160                task_editor
2161                    .update(&mut cx, |editor, cx| {
2162                        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
2163                        editor.handle_input(async_later_change, cx);
2164                    })
2165                    .unwrap();
2166            }));
2167        }
2168        let _ = future::join_all(edits).await;
2169        cx.executor().run_until_parked();
2170
2171        editor
2172            .update(cx, |editor, cx| {
2173                let current_text = editor.text(cx);
2174                for change in &expected_changes {
2175                    assert!(
2176                        current_text.contains(change),
2177                        "Should apply all changes made"
2178                    );
2179                }
2180                assert_eq!(
2181                    lsp_request_count.load(Ordering::SeqCst),
2182                    3,
2183                    "Should query new hints one more time, for the last edit only"
2184                );
2185                let expected_hints = vec!["3".to_string()];
2186                assert_eq!(
2187                    expected_hints,
2188                    cached_hint_labels(editor),
2189                    "Should get hints from the last edit landed only"
2190                );
2191                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2192                assert_eq!(
2193                    editor.inlay_hint_cache().version,
2194                    2,
2195                    "Should update the cache version once more, for the new change"
2196                );
2197            })
2198            .unwrap();
2199    }
2200
2201    #[gpui::test(iterations = 10)]
2202    async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) {
2203        init_test(cx, |settings| {
2204            settings.defaults.inlay_hints = Some(InlayHintSettings {
2205                enabled: true,
2206                show_type_hints: true,
2207                show_parameter_hints: true,
2208                show_other_hints: true,
2209            })
2210        });
2211
2212        let mut language = Language::new(
2213            LanguageConfig {
2214                name: "Rust".into(),
2215                matcher: LanguageMatcher {
2216                    path_suffixes: vec!["rs".to_string()],
2217                    ..Default::default()
2218                },
2219                ..Default::default()
2220            },
2221            Some(tree_sitter_rust::language()),
2222        );
2223        let mut fake_servers = language
2224            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2225                capabilities: lsp::ServerCapabilities {
2226                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2227                    ..Default::default()
2228                },
2229                ..Default::default()
2230            }))
2231            .await;
2232        let fs = FakeFs::new(cx.background_executor.clone());
2233        fs.insert_tree(
2234            "/a",
2235            json!({
2236                "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)),
2237                "other.rs": "// Test file",
2238            }),
2239        )
2240        .await;
2241        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2242        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
2243        let buffer = project
2244            .update(cx, |project, cx| {
2245                project.open_local_buffer("/a/main.rs", cx)
2246            })
2247            .await
2248            .unwrap();
2249        cx.executor().run_until_parked();
2250        cx.executor().start_waiting();
2251        let fake_server = fake_servers.next().await.unwrap();
2252        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
2253        let lsp_request_ranges = Arc::new(Mutex::new(Vec::new()));
2254        let lsp_request_count = Arc::new(AtomicUsize::new(0));
2255        let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges);
2256        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
2257        fake_server
2258            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2259                let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges);
2260                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
2261                async move {
2262                    assert_eq!(
2263                        params.text_document.uri,
2264                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
2265                    );
2266
2267                    task_lsp_request_ranges.lock().push(params.range);
2268                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
2269                    Ok(Some(vec![lsp::InlayHint {
2270                        position: params.range.end,
2271                        label: lsp::InlayHintLabel::String(i.to_string()),
2272                        kind: None,
2273                        text_edits: None,
2274                        tooltip: None,
2275                        padding_left: None,
2276                        padding_right: None,
2277                        data: None,
2278                    }]))
2279                }
2280            })
2281            .next()
2282            .await;
2283
2284        fn editor_visible_range(
2285            editor: &WindowHandle<Editor>,
2286            cx: &mut gpui::TestAppContext,
2287        ) -> Range<Point> {
2288            let ranges = editor
2289                .update(cx, |editor, cx| {
2290                    editor.excerpts_for_inlay_hints_query(None, cx)
2291                })
2292                .unwrap();
2293            assert_eq!(
2294                ranges.len(),
2295                1,
2296                "Single buffer should produce a single excerpt with visible range"
2297            );
2298            let (_, (excerpt_buffer, _, excerpt_visible_range)) =
2299                ranges.into_iter().next().unwrap();
2300            excerpt_buffer.update(cx, |buffer, _| {
2301                let snapshot = buffer.snapshot();
2302                let start = buffer
2303                    .anchor_before(excerpt_visible_range.start)
2304                    .to_point(&snapshot);
2305                let end = buffer
2306                    .anchor_after(excerpt_visible_range.end)
2307                    .to_point(&snapshot);
2308                start..end
2309            })
2310        }
2311
2312        // in large buffers, requests are made for more than visible range of a buffer.
2313        // invisible parts are queried later, to avoid excessive requests on quick typing.
2314        // wait the timeout needed to get all requests.
2315        cx.executor().advance_clock(Duration::from_millis(
2316            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2317        ));
2318        cx.executor().run_until_parked();
2319        let initial_visible_range = editor_visible_range(&editor, cx);
2320        let lsp_initial_visible_range = lsp::Range::new(
2321            lsp::Position::new(
2322                initial_visible_range.start.row,
2323                initial_visible_range.start.column,
2324            ),
2325            lsp::Position::new(
2326                initial_visible_range.end.row,
2327                initial_visible_range.end.column,
2328            ),
2329        );
2330        let expected_initial_query_range_end =
2331            lsp::Position::new(initial_visible_range.end.row * 2, 2);
2332        let mut expected_invisible_query_start = lsp_initial_visible_range.end;
2333        expected_invisible_query_start.character += 1;
2334        editor.update(cx, |editor, cx| {
2335            let ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2336            assert_eq!(ranges.len(), 2,
2337                "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:?}");
2338            let visible_query_range = &ranges[0];
2339            assert_eq!(visible_query_range.start, lsp_initial_visible_range.start);
2340            assert_eq!(visible_query_range.end, lsp_initial_visible_range.end);
2341            let invisible_query_range = &ranges[1];
2342
2343            assert_eq!(invisible_query_range.start, expected_invisible_query_start, "Should initially query visible edge of the document");
2344            assert_eq!(invisible_query_range.end, expected_initial_query_range_end, "Should initially query visible edge of the document");
2345
2346            let requests_count = lsp_request_count.load(Ordering::Acquire);
2347            assert_eq!(requests_count, 2, "Visible + invisible request");
2348            let expected_hints = vec!["1".to_string(), "2".to_string()];
2349            assert_eq!(
2350                expected_hints,
2351                cached_hint_labels(editor),
2352                "Should have hints from both LSP requests made for a big file"
2353            );
2354            assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
2355            assert_eq!(
2356                editor.inlay_hint_cache().version, requests_count,
2357                "LSP queries should've bumped the cache version"
2358            );
2359        }).unwrap();
2360
2361        editor
2362            .update(cx, |editor, cx| {
2363                editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
2364                editor.scroll_screen(&ScrollAmount::Page(1.0), cx);
2365            })
2366            .unwrap();
2367        cx.executor().advance_clock(Duration::from_millis(
2368            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2369        ));
2370        cx.executor().run_until_parked();
2371        let visible_range_after_scrolls = editor_visible_range(&editor, cx);
2372        let visible_line_count = editor
2373            .update(cx, |editor, _| editor.visible_line_count().unwrap())
2374            .unwrap();
2375        let selection_in_cached_range = editor
2376            .update(cx, |editor, cx| {
2377                let ranges = lsp_request_ranges
2378                    .lock()
2379                    .drain(..)
2380                    .sorted_by_key(|r| r.start)
2381                    .collect::<Vec<_>>();
2382                assert_eq!(
2383                    ranges.len(),
2384                    2,
2385                    "Should query 2 ranges after both scrolls, but got: {ranges:?}"
2386                );
2387                let first_scroll = &ranges[0];
2388                let second_scroll = &ranges[1];
2389                assert_eq!(
2390                    first_scroll.end, second_scroll.start,
2391                    "Should query 2 adjacent ranges after the scrolls, but got: {ranges:?}"
2392                );
2393                assert_eq!(
2394                first_scroll.start, expected_initial_query_range_end,
2395                "First scroll should start the query right after the end of the original scroll",
2396            );
2397                assert_eq!(
2398                second_scroll.end,
2399                lsp::Position::new(
2400                    visible_range_after_scrolls.end.row
2401                        + visible_line_count.ceil() as u32,
2402                    1,
2403                ),
2404                "Second scroll should query one more screen down after the end of the visible range"
2405            );
2406
2407                let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2408                assert_eq!(lsp_requests, 4, "Should query for hints after every scroll");
2409                let expected_hints = vec![
2410                    "1".to_string(),
2411                    "2".to_string(),
2412                    "3".to_string(),
2413                    "4".to_string(),
2414                ];
2415                assert_eq!(
2416                    expected_hints,
2417                    cached_hint_labels(editor),
2418                    "Should have hints from the new LSP response after the edit"
2419                );
2420                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2421                assert_eq!(
2422                    editor.inlay_hint_cache().version,
2423                    lsp_requests,
2424                    "Should update the cache for every LSP response with hints added"
2425                );
2426
2427                let mut selection_in_cached_range = visible_range_after_scrolls.end;
2428                selection_in_cached_range.row -= visible_line_count.ceil() as u32;
2429                selection_in_cached_range
2430            })
2431            .unwrap();
2432
2433        editor
2434            .update(cx, |editor, cx| {
2435                editor.change_selections(Some(Autoscroll::center()), cx, |s| {
2436                    s.select_ranges([selection_in_cached_range..selection_in_cached_range])
2437                });
2438            })
2439            .unwrap();
2440        cx.executor().advance_clock(Duration::from_millis(
2441            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2442        ));
2443        cx.executor().run_until_parked();
2444        editor.update(cx, |_, _| {
2445            let ranges = lsp_request_ranges
2446                .lock()
2447                .drain(..)
2448                .sorted_by_key(|r| r.start)
2449                .collect::<Vec<_>>();
2450            assert!(ranges.is_empty(), "No new ranges or LSP queries should be made after returning to the selection with cached hints");
2451            assert_eq!(lsp_request_count.load(Ordering::Acquire), 4);
2452        }).unwrap();
2453
2454        editor
2455            .update(cx, |editor, cx| {
2456                editor.handle_input("++++more text++++", cx);
2457            })
2458            .unwrap();
2459        cx.executor().advance_clock(Duration::from_millis(
2460            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2461        ));
2462        cx.executor().run_until_parked();
2463        editor.update(cx, |editor, cx| {
2464            let mut ranges = lsp_request_ranges.lock().drain(..).collect::<Vec<_>>();
2465            ranges.sort_by_key(|r| r.start);
2466
2467            assert_eq!(ranges.len(), 3,
2468                "On edit, should scroll to selection and query a range around it: visible + same range above and below. Instead, got query ranges {ranges:?}");
2469            let above_query_range = &ranges[0];
2470            let visible_query_range = &ranges[1];
2471            let below_query_range = &ranges[2];
2472            assert!(above_query_range.end.character < visible_query_range.start.character || above_query_range.end.line + 1 == visible_query_range.start.line,
2473                "Above range {above_query_range:?} should be before visible range {visible_query_range:?}");
2474            assert!(visible_query_range.end.character < below_query_range.start.character || visible_query_range.end.line  + 1 == below_query_range.start.line,
2475                "Visible range {visible_query_range:?} should be before below range {below_query_range:?}");
2476            assert!(above_query_range.start.line < selection_in_cached_range.row,
2477                "Hints should be queried with the selected range after the query range start");
2478            assert!(below_query_range.end.line > selection_in_cached_range.row,
2479                "Hints should be queried with the selected range before the query range end");
2480            assert!(above_query_range.start.line <= selection_in_cached_range.row - (visible_line_count * 3.0 / 2.0) as u32,
2481                "Hints query range should contain one more screen before");
2482            assert!(below_query_range.end.line >= selection_in_cached_range.row + (visible_line_count * 3.0 / 2.0) as u32,
2483                "Hints query range should contain one more screen after");
2484
2485            let lsp_requests = lsp_request_count.load(Ordering::Acquire);
2486            assert_eq!(lsp_requests, 7, "There should be a visible range and two ranges above and below it queried");
2487            let expected_hints = vec!["5".to_string(), "6".to_string(), "7".to_string()];
2488            assert_eq!(expected_hints, cached_hint_labels(editor),
2489                "Should have hints from the new LSP response after the edit");
2490            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2491            assert_eq!(editor.inlay_hint_cache().version, lsp_requests, "Should update the cache for every LSP response with hints added");
2492        }).unwrap();
2493    }
2494
2495    #[gpui::test(iterations = 30)]
2496    async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
2497        init_test(cx, |settings| {
2498            settings.defaults.inlay_hints = Some(InlayHintSettings {
2499                enabled: true,
2500                show_type_hints: true,
2501                show_parameter_hints: true,
2502                show_other_hints: true,
2503            })
2504        });
2505
2506        let mut language = Language::new(
2507            LanguageConfig {
2508                name: "Rust".into(),
2509                matcher: LanguageMatcher {
2510                    path_suffixes: vec!["rs".to_string()],
2511                    ..Default::default()
2512                },
2513                ..Default::default()
2514            },
2515            Some(tree_sitter_rust::language()),
2516        );
2517        let mut fake_servers = language
2518            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2519                capabilities: lsp::ServerCapabilities {
2520                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2521                    ..Default::default()
2522                },
2523                ..Default::default()
2524            }))
2525            .await;
2526        let language = Arc::new(language);
2527        let fs = FakeFs::new(cx.background_executor.clone());
2528        fs.insert_tree(
2529                "/a",
2530                json!({
2531                    "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2532                    "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2533                }),
2534            )
2535            .await;
2536        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2537        project.update(cx, |project, _| {
2538            project.languages().add(Arc::clone(&language))
2539        });
2540        let worktree_id = project.update(cx, |project, cx| {
2541            project.worktrees().next().unwrap().read(cx).id()
2542        });
2543
2544        let buffer_1 = project
2545            .update(cx, |project, cx| {
2546                project.open_buffer((worktree_id, "main.rs"), cx)
2547            })
2548            .await
2549            .unwrap();
2550        let buffer_2 = project
2551            .update(cx, |project, cx| {
2552                project.open_buffer((worktree_id, "other.rs"), cx)
2553            })
2554            .await
2555            .unwrap();
2556        let multibuffer = cx.new_model(|cx| {
2557            let mut multibuffer = MultiBuffer::new(0, Capability::ReadWrite);
2558            multibuffer.push_excerpts(
2559                buffer_1.clone(),
2560                [
2561                    ExcerptRange {
2562                        context: Point::new(0, 0)..Point::new(2, 0),
2563                        primary: None,
2564                    },
2565                    ExcerptRange {
2566                        context: Point::new(4, 0)..Point::new(11, 0),
2567                        primary: None,
2568                    },
2569                    ExcerptRange {
2570                        context: Point::new(22, 0)..Point::new(33, 0),
2571                        primary: None,
2572                    },
2573                    ExcerptRange {
2574                        context: Point::new(44, 0)..Point::new(55, 0),
2575                        primary: None,
2576                    },
2577                    ExcerptRange {
2578                        context: Point::new(56, 0)..Point::new(66, 0),
2579                        primary: None,
2580                    },
2581                    ExcerptRange {
2582                        context: Point::new(67, 0)..Point::new(77, 0),
2583                        primary: None,
2584                    },
2585                ],
2586                cx,
2587            );
2588            multibuffer.push_excerpts(
2589                buffer_2.clone(),
2590                [
2591                    ExcerptRange {
2592                        context: Point::new(0, 1)..Point::new(2, 1),
2593                        primary: None,
2594                    },
2595                    ExcerptRange {
2596                        context: Point::new(4, 1)..Point::new(11, 1),
2597                        primary: None,
2598                    },
2599                    ExcerptRange {
2600                        context: Point::new(22, 1)..Point::new(33, 1),
2601                        primary: None,
2602                    },
2603                    ExcerptRange {
2604                        context: Point::new(44, 1)..Point::new(55, 1),
2605                        primary: None,
2606                    },
2607                    ExcerptRange {
2608                        context: Point::new(56, 1)..Point::new(66, 1),
2609                        primary: None,
2610                    },
2611                    ExcerptRange {
2612                        context: Point::new(67, 1)..Point::new(77, 1),
2613                        primary: None,
2614                    },
2615                ],
2616                cx,
2617            );
2618            multibuffer
2619        });
2620
2621        cx.executor().run_until_parked();
2622        let editor =
2623            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
2624        let editor_edited = Arc::new(AtomicBool::new(false));
2625        let fake_server = fake_servers.next().await.unwrap();
2626        let closure_editor_edited = Arc::clone(&editor_edited);
2627        fake_server
2628            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2629                let task_editor_edited = Arc::clone(&closure_editor_edited);
2630                async move {
2631                    let hint_text = if params.text_document.uri
2632                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2633                    {
2634                        "main hint"
2635                    } else if params.text_document.uri
2636                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2637                    {
2638                        "other hint"
2639                    } else {
2640                        panic!("unexpected uri: {:?}", params.text_document.uri);
2641                    };
2642
2643                    // one hint per excerpt
2644                    let positions = [
2645                        lsp::Position::new(0, 2),
2646                        lsp::Position::new(4, 2),
2647                        lsp::Position::new(22, 2),
2648                        lsp::Position::new(44, 2),
2649                        lsp::Position::new(56, 2),
2650                        lsp::Position::new(67, 2),
2651                    ];
2652                    let out_of_range_hint = lsp::InlayHint {
2653                        position: lsp::Position::new(
2654                            params.range.start.line + 99,
2655                            params.range.start.character + 99,
2656                        ),
2657                        label: lsp::InlayHintLabel::String(
2658                            "out of excerpt range, should be ignored".to_string(),
2659                        ),
2660                        kind: None,
2661                        text_edits: None,
2662                        tooltip: None,
2663                        padding_left: None,
2664                        padding_right: None,
2665                        data: None,
2666                    };
2667
2668                    let edited = task_editor_edited.load(Ordering::Acquire);
2669                    Ok(Some(
2670                        std::iter::once(out_of_range_hint)
2671                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2672                                lsp::InlayHint {
2673                                    position,
2674                                    label: lsp::InlayHintLabel::String(format!(
2675                                        "{hint_text}{} #{i}",
2676                                        if edited { "(edited)" } else { "" },
2677                                    )),
2678                                    kind: None,
2679                                    text_edits: None,
2680                                    tooltip: None,
2681                                    padding_left: None,
2682                                    padding_right: None,
2683                                    data: None,
2684                                }
2685                            }))
2686                            .collect(),
2687                    ))
2688                }
2689            })
2690            .next()
2691            .await;
2692        cx.executor().run_until_parked();
2693
2694        editor.update(cx, |editor, cx| {
2695                let expected_hints = vec![
2696                    "main hint #0".to_string(),
2697                    "main hint #1".to_string(),
2698                    "main hint #2".to_string(),
2699                    "main hint #3".to_string(),
2700                    "main hint #4".to_string(),
2701                    "main hint #5".to_string(),
2702                ];
2703                assert_eq!(
2704                    expected_hints,
2705                    cached_hint_labels(editor),
2706                    "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
2707                );
2708                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2709                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the version");
2710            }).unwrap();
2711
2712        editor
2713            .update(cx, |editor, cx| {
2714                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2715                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2716                });
2717                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2718                    s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
2719                });
2720                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2721                    s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
2722                });
2723            })
2724            .unwrap();
2725        cx.executor().run_until_parked();
2726        editor.update(cx, |editor, cx| {
2727                let expected_hints = vec![
2728                    "main hint #0".to_string(),
2729                    "main hint #1".to_string(),
2730                    "main hint #2".to_string(),
2731                    "main hint #3".to_string(),
2732                    "main hint #4".to_string(),
2733                    "main hint #5".to_string(),
2734                    "other hint #0".to_string(),
2735                    "other hint #1".to_string(),
2736                    "other hint #2".to_string(),
2737                ];
2738                assert_eq!(expected_hints, cached_hint_labels(editor),
2739                    "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
2740                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2741                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
2742                    "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
2743            }).unwrap();
2744
2745        editor
2746            .update(cx, |editor, cx| {
2747                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2748                    s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
2749                });
2750            })
2751            .unwrap();
2752        cx.executor().advance_clock(Duration::from_millis(
2753            INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
2754        ));
2755        cx.executor().run_until_parked();
2756        let last_scroll_update_version = editor.update(cx, |editor, cx| {
2757                let expected_hints = vec![
2758                    "main hint #0".to_string(),
2759                    "main hint #1".to_string(),
2760                    "main hint #2".to_string(),
2761                    "main hint #3".to_string(),
2762                    "main hint #4".to_string(),
2763                    "main hint #5".to_string(),
2764                    "other hint #0".to_string(),
2765                    "other hint #1".to_string(),
2766                    "other hint #2".to_string(),
2767                    "other hint #3".to_string(),
2768                    "other hint #4".to_string(),
2769                    "other hint #5".to_string(),
2770                ];
2771                assert_eq!(expected_hints, cached_hint_labels(editor),
2772                    "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
2773                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2774                assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
2775                expected_hints.len()
2776            }).unwrap();
2777
2778        editor
2779            .update(cx, |editor, cx| {
2780                editor.change_selections(Some(Autoscroll::Next), cx, |s| {
2781                    s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
2782                });
2783            })
2784            .unwrap();
2785        cx.executor().run_until_parked();
2786        editor.update(cx, |editor, cx| {
2787                let expected_hints = vec![
2788                    "main hint #0".to_string(),
2789                    "main hint #1".to_string(),
2790                    "main hint #2".to_string(),
2791                    "main hint #3".to_string(),
2792                    "main hint #4".to_string(),
2793                    "main hint #5".to_string(),
2794                    "other hint #0".to_string(),
2795                    "other hint #1".to_string(),
2796                    "other hint #2".to_string(),
2797                    "other hint #3".to_string(),
2798                    "other hint #4".to_string(),
2799                    "other hint #5".to_string(),
2800                ];
2801                assert_eq!(expected_hints, cached_hint_labels(editor),
2802                    "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
2803                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2804                assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scrolled buffer");
2805            }).unwrap();
2806
2807        editor_edited.store(true, Ordering::Release);
2808        editor
2809            .update(cx, |editor, cx| {
2810                editor.change_selections(None, cx, |s| {
2811                    s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
2812                });
2813                editor.handle_input("++++more text++++", cx);
2814            })
2815            .unwrap();
2816        cx.executor().run_until_parked();
2817        editor.update(cx, |editor, cx| {
2818            let expected_hints = vec![
2819                "main hint(edited) #0".to_string(),
2820                "main hint(edited) #1".to_string(),
2821                "main hint(edited) #2".to_string(),
2822                "main hint(edited) #3".to_string(),
2823                "main hint(edited) #4".to_string(),
2824                "main hint(edited) #5".to_string(),
2825                "other hint(edited) #0".to_string(),
2826                "other hint(edited) #1".to_string(),
2827            ];
2828            assert_eq!(
2829                expected_hints,
2830                cached_hint_labels(editor),
2831                "After multibuffer edit, editor gets scrolled back to the last selection; \
2832    all hints should be invalidated and required for all of its visible excerpts"
2833            );
2834            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
2835
2836            let current_cache_version = editor.inlay_hint_cache().version;
2837            let expected_version = last_scroll_update_version + expected_hints.len();
2838            assert!(
2839                current_cache_version == expected_version || current_cache_version == expected_version + 1 ,
2840                // TODO we sometimes get an extra cache version bump, why?
2841                "We should have updated cache N times == N of new hints arrived (separately from each excerpt), or hit a bug and do that one extra time"
2842            );
2843        }).unwrap();
2844    }
2845
2846    #[gpui::test]
2847    async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
2848        init_test(cx, |settings| {
2849            settings.defaults.inlay_hints = Some(InlayHintSettings {
2850                enabled: true,
2851                show_type_hints: false,
2852                show_parameter_hints: false,
2853                show_other_hints: false,
2854            })
2855        });
2856
2857        let mut language = Language::new(
2858            LanguageConfig {
2859                name: "Rust".into(),
2860                matcher: LanguageMatcher {
2861                    path_suffixes: vec!["rs".to_string()],
2862                    ..Default::default()
2863                },
2864                ..Default::default()
2865            },
2866            Some(tree_sitter_rust::language()),
2867        );
2868        let mut fake_servers = language
2869            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2870                capabilities: lsp::ServerCapabilities {
2871                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
2872                    ..Default::default()
2873                },
2874                ..Default::default()
2875            }))
2876            .await;
2877        let language = Arc::new(language);
2878        let fs = FakeFs::new(cx.background_executor.clone());
2879        fs.insert_tree(
2880            "/a",
2881            json!({
2882                "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
2883                "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
2884            }),
2885        )
2886        .await;
2887        let project = Project::test(fs, ["/a".as_ref()], cx).await;
2888        project.update(cx, |project, _| {
2889            project.languages().add(Arc::clone(&language))
2890        });
2891        let worktree_id = project.update(cx, |project, cx| {
2892            project.worktrees().next().unwrap().read(cx).id()
2893        });
2894
2895        let buffer_1 = project
2896            .update(cx, |project, cx| {
2897                project.open_buffer((worktree_id, "main.rs"), cx)
2898            })
2899            .await
2900            .unwrap();
2901        let buffer_2 = project
2902            .update(cx, |project, cx| {
2903                project.open_buffer((worktree_id, "other.rs"), cx)
2904            })
2905            .await
2906            .unwrap();
2907        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, Capability::ReadWrite));
2908        let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
2909            let buffer_1_excerpts = multibuffer.push_excerpts(
2910                buffer_1.clone(),
2911                [ExcerptRange {
2912                    context: Point::new(0, 0)..Point::new(2, 0),
2913                    primary: None,
2914                }],
2915                cx,
2916            );
2917            let buffer_2_excerpts = multibuffer.push_excerpts(
2918                buffer_2.clone(),
2919                [ExcerptRange {
2920                    context: Point::new(0, 1)..Point::new(2, 1),
2921                    primary: None,
2922                }],
2923                cx,
2924            );
2925            (buffer_1_excerpts, buffer_2_excerpts)
2926        });
2927
2928        assert!(!buffer_1_excerpts.is_empty());
2929        assert!(!buffer_2_excerpts.is_empty());
2930
2931        cx.executor().run_until_parked();
2932        let editor =
2933            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
2934        let editor_edited = Arc::new(AtomicBool::new(false));
2935        let fake_server = fake_servers.next().await.unwrap();
2936        let closure_editor_edited = Arc::clone(&editor_edited);
2937        fake_server
2938            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
2939                let task_editor_edited = Arc::clone(&closure_editor_edited);
2940                async move {
2941                    let hint_text = if params.text_document.uri
2942                        == lsp::Url::from_file_path("/a/main.rs").unwrap()
2943                    {
2944                        "main hint"
2945                    } else if params.text_document.uri
2946                        == lsp::Url::from_file_path("/a/other.rs").unwrap()
2947                    {
2948                        "other hint"
2949                    } else {
2950                        panic!("unexpected uri: {:?}", params.text_document.uri);
2951                    };
2952
2953                    let positions = [
2954                        lsp::Position::new(0, 2),
2955                        lsp::Position::new(4, 2),
2956                        lsp::Position::new(22, 2),
2957                        lsp::Position::new(44, 2),
2958                        lsp::Position::new(56, 2),
2959                        lsp::Position::new(67, 2),
2960                    ];
2961                    let out_of_range_hint = lsp::InlayHint {
2962                        position: lsp::Position::new(
2963                            params.range.start.line + 99,
2964                            params.range.start.character + 99,
2965                        ),
2966                        label: lsp::InlayHintLabel::String(
2967                            "out of excerpt range, should be ignored".to_string(),
2968                        ),
2969                        kind: None,
2970                        text_edits: None,
2971                        tooltip: None,
2972                        padding_left: None,
2973                        padding_right: None,
2974                        data: None,
2975                    };
2976
2977                    let edited = task_editor_edited.load(Ordering::Acquire);
2978                    Ok(Some(
2979                        std::iter::once(out_of_range_hint)
2980                            .chain(positions.into_iter().enumerate().map(|(i, position)| {
2981                                lsp::InlayHint {
2982                                    position,
2983                                    label: lsp::InlayHintLabel::String(format!(
2984                                        "{hint_text}{} #{i}",
2985                                        if edited { "(edited)" } else { "" },
2986                                    )),
2987                                    kind: None,
2988                                    text_edits: None,
2989                                    tooltip: None,
2990                                    padding_left: None,
2991                                    padding_right: None,
2992                                    data: None,
2993                                }
2994                            }))
2995                            .collect(),
2996                    ))
2997                }
2998            })
2999            .next()
3000            .await;
3001        cx.executor().run_until_parked();
3002
3003        editor
3004            .update(cx, |editor, cx| {
3005                assert_eq!(
3006                    vec!["main hint #0".to_string(), "other hint #0".to_string()],
3007                    cached_hint_labels(editor),
3008                    "Cache should update for both excerpts despite hints display was disabled"
3009                );
3010                assert!(
3011                visible_hint_labels(editor, cx).is_empty(),
3012                "All hints are disabled and should not be shown despite being present in the cache"
3013            );
3014                assert_eq!(
3015                    editor.inlay_hint_cache().version,
3016                    2,
3017                    "Cache should update once per excerpt query"
3018                );
3019            })
3020            .unwrap();
3021
3022        editor
3023            .update(cx, |editor, cx| {
3024                editor.buffer().update(cx, |multibuffer, cx| {
3025                    multibuffer.remove_excerpts(buffer_2_excerpts, cx)
3026                })
3027            })
3028            .unwrap();
3029        cx.executor().run_until_parked();
3030        editor
3031            .update(cx, |editor, cx| {
3032                assert_eq!(
3033                    vec!["main hint #0".to_string()],
3034                    cached_hint_labels(editor),
3035                    "For the removed excerpt, should clean corresponding cached hints"
3036                );
3037                assert!(
3038                visible_hint_labels(editor, cx).is_empty(),
3039                "All hints are disabled and should not be shown despite being present in the cache"
3040            );
3041                assert_eq!(
3042                    editor.inlay_hint_cache().version,
3043                    3,
3044                    "Excerpt removal should trigger a cache update"
3045                );
3046            })
3047            .unwrap();
3048
3049        update_test_language_settings(cx, |settings| {
3050            settings.defaults.inlay_hints = Some(InlayHintSettings {
3051                enabled: true,
3052                show_type_hints: true,
3053                show_parameter_hints: true,
3054                show_other_hints: true,
3055            })
3056        });
3057        cx.executor().run_until_parked();
3058        editor
3059            .update(cx, |editor, cx| {
3060                let expected_hints = vec!["main hint #0".to_string()];
3061                assert_eq!(
3062                    expected_hints,
3063                    cached_hint_labels(editor),
3064                    "Hint display settings change should not change the cache"
3065                );
3066                assert_eq!(
3067                    expected_hints,
3068                    visible_hint_labels(editor, cx),
3069                    "Settings change should make cached hints visible"
3070                );
3071                assert_eq!(
3072                    editor.inlay_hint_cache().version,
3073                    4,
3074                    "Settings change should trigger a cache update"
3075                );
3076            })
3077            .unwrap();
3078    }
3079
3080    #[gpui::test]
3081    async fn test_inside_char_boundary_range_hints(cx: &mut gpui::TestAppContext) {
3082        init_test(cx, |settings| {
3083            settings.defaults.inlay_hints = Some(InlayHintSettings {
3084                enabled: true,
3085                show_type_hints: true,
3086                show_parameter_hints: true,
3087                show_other_hints: true,
3088            })
3089        });
3090
3091        let mut language = Language::new(
3092            LanguageConfig {
3093                name: "Rust".into(),
3094                matcher: LanguageMatcher {
3095                    path_suffixes: vec!["rs".to_string()],
3096                    ..Default::default()
3097                },
3098                ..Default::default()
3099            },
3100            Some(tree_sitter_rust::language()),
3101        );
3102        let mut fake_servers = language
3103            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3104                capabilities: lsp::ServerCapabilities {
3105                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3106                    ..Default::default()
3107                },
3108                ..Default::default()
3109            }))
3110            .await;
3111        let fs = FakeFs::new(cx.background_executor.clone());
3112        fs.insert_tree(
3113            "/a",
3114            json!({
3115                "main.rs": format!(r#"fn main() {{\n{}\n}}"#, format!("let i = {};\n", "".repeat(10)).repeat(500)),
3116                "other.rs": "// Test file",
3117            }),
3118        )
3119        .await;
3120        let project = Project::test(fs, ["/a".as_ref()], cx).await;
3121        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3122        let buffer = project
3123            .update(cx, |project, cx| {
3124                project.open_local_buffer("/a/main.rs", cx)
3125            })
3126            .await
3127            .unwrap();
3128        cx.executor().run_until_parked();
3129        cx.executor().start_waiting();
3130        let fake_server = fake_servers.next().await.unwrap();
3131        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
3132        let lsp_request_count = Arc::new(AtomicU32::new(0));
3133        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
3134        fake_server
3135            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
3136                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
3137                async move {
3138                    assert_eq!(
3139                        params.text_document.uri,
3140                        lsp::Url::from_file_path("/a/main.rs").unwrap(),
3141                    );
3142                    let query_start = params.range.start;
3143                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::Release) + 1;
3144                    Ok(Some(vec![lsp::InlayHint {
3145                        position: query_start,
3146                        label: lsp::InlayHintLabel::String(i.to_string()),
3147                        kind: None,
3148                        text_edits: None,
3149                        tooltip: None,
3150                        padding_left: None,
3151                        padding_right: None,
3152                        data: None,
3153                    }]))
3154                }
3155            })
3156            .next()
3157            .await;
3158
3159        cx.executor().run_until_parked();
3160        editor
3161            .update(cx, |editor, cx| {
3162                editor.change_selections(None, cx, |s| {
3163                    s.select_ranges([Point::new(10, 0)..Point::new(10, 0)])
3164                })
3165            })
3166            .unwrap();
3167        cx.executor().run_until_parked();
3168        editor
3169            .update(cx, |editor, cx| {
3170                let expected_hints = vec!["1".to_string()];
3171                assert_eq!(expected_hints, cached_hint_labels(editor));
3172                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3173                assert_eq!(editor.inlay_hint_cache().version, 1);
3174            })
3175            .unwrap();
3176    }
3177
3178    #[gpui::test]
3179    async fn test_toggle_inlay_hints(cx: &mut gpui::TestAppContext) {
3180        init_test(cx, |settings| {
3181            settings.defaults.inlay_hints = Some(InlayHintSettings {
3182                enabled: false,
3183                show_type_hints: true,
3184                show_parameter_hints: true,
3185                show_other_hints: true,
3186            })
3187        });
3188
3189        let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await;
3190
3191        editor
3192            .update(cx, |editor, cx| {
3193                editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3194            })
3195            .unwrap();
3196        cx.executor().start_waiting();
3197        let lsp_request_count = Arc::new(AtomicU32::new(0));
3198        let closure_lsp_request_count = Arc::clone(&lsp_request_count);
3199        fake_server
3200            .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
3201                let task_lsp_request_count = Arc::clone(&closure_lsp_request_count);
3202                async move {
3203                    assert_eq!(
3204                        params.text_document.uri,
3205                        lsp::Url::from_file_path(file_with_hints).unwrap(),
3206                    );
3207
3208                    let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1;
3209                    Ok(Some(vec![lsp::InlayHint {
3210                        position: lsp::Position::new(0, i),
3211                        label: lsp::InlayHintLabel::String(i.to_string()),
3212                        kind: None,
3213                        text_edits: None,
3214                        tooltip: None,
3215                        padding_left: None,
3216                        padding_right: None,
3217                        data: None,
3218                    }]))
3219                }
3220            })
3221            .next()
3222            .await;
3223        cx.executor().run_until_parked();
3224        editor
3225            .update(cx, |editor, cx| {
3226                let expected_hints = vec!["1".to_string()];
3227                assert_eq!(
3228                    expected_hints,
3229                    cached_hint_labels(editor),
3230                    "Should display inlays after toggle despite them disabled in settings"
3231                );
3232                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3233                assert_eq!(
3234                    editor.inlay_hint_cache().version,
3235                    1,
3236                    "First toggle should be cache's first update"
3237                );
3238            })
3239            .unwrap();
3240
3241        editor
3242            .update(cx, |editor, cx| {
3243                editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3244            })
3245            .unwrap();
3246        cx.executor().run_until_parked();
3247        editor
3248            .update(cx, |editor, cx| {
3249                assert!(
3250                    cached_hint_labels(editor).is_empty(),
3251                    "Should clear hints after 2nd toggle"
3252                );
3253                assert!(visible_hint_labels(editor, cx).is_empty());
3254                assert_eq!(editor.inlay_hint_cache().version, 2);
3255            })
3256            .unwrap();
3257
3258        update_test_language_settings(cx, |settings| {
3259            settings.defaults.inlay_hints = Some(InlayHintSettings {
3260                enabled: true,
3261                show_type_hints: true,
3262                show_parameter_hints: true,
3263                show_other_hints: true,
3264            })
3265        });
3266        cx.executor().run_until_parked();
3267        editor
3268            .update(cx, |editor, cx| {
3269                let expected_hints = vec!["2".to_string()];
3270                assert_eq!(
3271                    expected_hints,
3272                    cached_hint_labels(editor),
3273                    "Should query LSP hints for the 2nd time after enabling hints in settings"
3274                );
3275                assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3276                assert_eq!(editor.inlay_hint_cache().version, 3);
3277            })
3278            .unwrap();
3279
3280        editor
3281            .update(cx, |editor, cx| {
3282                editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3283            })
3284            .unwrap();
3285        cx.executor().run_until_parked();
3286        editor
3287            .update(cx, |editor, cx| {
3288                assert!(
3289                    cached_hint_labels(editor).is_empty(),
3290                    "Should clear hints after enabling in settings and a 3rd toggle"
3291                );
3292                assert!(visible_hint_labels(editor, cx).is_empty());
3293                assert_eq!(editor.inlay_hint_cache().version, 4);
3294            })
3295            .unwrap();
3296
3297        editor
3298            .update(cx, |editor, cx| {
3299                editor.toggle_inlay_hints(&crate::ToggleInlayHints, cx)
3300            })
3301            .unwrap();
3302        cx.executor().run_until_parked();
3303        editor.update(cx, |editor, cx| {
3304            let expected_hints = vec!["3".to_string()];
3305            assert_eq!(
3306                expected_hints,
3307                cached_hint_labels(editor),
3308                "Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
3309            );
3310            assert_eq!(expected_hints, visible_hint_labels(editor, cx));
3311            assert_eq!(editor.inlay_hint_cache().version, 5);
3312        }).unwrap();
3313    }
3314
3315    pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
3316        cx.update(|cx| {
3317            let settings_store = SettingsStore::test(cx);
3318            cx.set_global(settings_store);
3319            theme::init(theme::LoadThemes::JustBase, cx);
3320            release_channel::init("0.0.0", cx);
3321            client::init_settings(cx);
3322            language::init(cx);
3323            Project::init_settings(cx);
3324            workspace::init_settings(cx);
3325            crate::init(cx);
3326        });
3327
3328        update_test_language_settings(cx, f);
3329    }
3330
3331    async fn prepare_test_objects(
3332        cx: &mut TestAppContext,
3333    ) -> (&'static str, WindowHandle<Editor>, FakeLanguageServer) {
3334        let mut language = Language::new(
3335            LanguageConfig {
3336                name: "Rust".into(),
3337                matcher: LanguageMatcher {
3338                    path_suffixes: vec!["rs".to_string()],
3339                    ..Default::default()
3340                },
3341                ..Default::default()
3342            },
3343            Some(tree_sitter_rust::language()),
3344        );
3345        let mut fake_servers = language
3346            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3347                capabilities: lsp::ServerCapabilities {
3348                    inlay_hint_provider: Some(lsp::OneOf::Left(true)),
3349                    ..Default::default()
3350                },
3351                ..Default::default()
3352            }))
3353            .await;
3354
3355        let fs = FakeFs::new(cx.background_executor.clone());
3356        fs.insert_tree(
3357            "/a",
3358            json!({
3359                "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out",
3360                "other.rs": "// Test file",
3361            }),
3362        )
3363        .await;
3364
3365        let project = Project::test(fs, ["/a".as_ref()], cx).await;
3366        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
3367        let buffer = project
3368            .update(cx, |project, cx| {
3369                project.open_local_buffer("/a/main.rs", cx)
3370            })
3371            .await
3372            .unwrap();
3373        cx.executor().run_until_parked();
3374        cx.executor().start_waiting();
3375        let fake_server = fake_servers.next().await.unwrap();
3376        let editor = cx.add_window(|cx| Editor::for_buffer(buffer, Some(project), cx));
3377
3378        editor
3379            .update(cx, |editor, cx| {
3380                assert!(cached_hint_labels(editor).is_empty());
3381                assert!(visible_hint_labels(editor, cx).is_empty());
3382                assert_eq!(editor.inlay_hint_cache().version, 0);
3383            })
3384            .unwrap();
3385
3386        ("/a/main.rs", editor, fake_server)
3387    }
3388
3389    pub fn cached_hint_labels(editor: &Editor) -> Vec<String> {
3390        let mut labels = Vec::new();
3391        for (_, excerpt_hints) in &editor.inlay_hint_cache().hints {
3392            let excerpt_hints = excerpt_hints.read();
3393            for id in &excerpt_hints.ordered_hints {
3394                labels.push(excerpt_hints.hints_by_id[id].text());
3395            }
3396        }
3397
3398        labels.sort();
3399        labels
3400    }
3401
3402    pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, Editor>) -> Vec<String> {
3403        let mut hints = editor
3404            .visible_inlay_hints(cx)
3405            .into_iter()
3406            .map(|hint| hint.text.to_string())
3407            .collect::<Vec<_>>();
3408        hints.sort();
3409        hints
3410    }
3411}