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