inlay_hint_cache.rs

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