inlay_hint_cache.rs

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