inlay_hint_cache.rs

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