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