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