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