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