inlay_map.rs

   1//! The inlay map. See the [`display_map`][super] docs for an overview of how the inlay map fits
   2//! into the rest of the [`DisplayMap`][super::DisplayMap]. Much of the documentation for this
   3//! module generalizes to other layers.
   4//!
   5//! The core of this module is the [`InlayMap`] struct, which maintains a vec of [`Inlay`]s, and
   6//! [`InlaySnapshot`], which holds a sum tree of [`Transform`]s.
   7
   8use crate::{
   9    ChunkRenderer, HighlightStyles,
  10    inlays::{Inlay, InlayContent},
  11};
  12use collections::BTreeSet;
  13use language::{Chunk, Edit, Point, TextSummary};
  14use multi_buffer::{
  15    MBTextSummary, MultiBufferOffset, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot,
  16    RowInfo, ToOffset,
  17};
  18use project::InlayId;
  19use std::{
  20    cmp,
  21    ops::{Add, AddAssign, Range, Sub, SubAssign},
  22    sync::Arc,
  23};
  24use sum_tree::{Bias, Cursor, Dimensions, SumTree};
  25use text::{ChunkBitmaps, Patch};
  26use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
  27
  28use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId};
  29
  30/// Decides where the [`Inlay`]s should be displayed.
  31///
  32/// See the [`display_map` module documentation](crate::display_map) for more information.
  33pub struct InlayMap {
  34    snapshot: InlaySnapshot,
  35    inlays: Vec<Inlay>,
  36}
  37
  38#[derive(Clone)]
  39pub struct InlaySnapshot {
  40    pub buffer: MultiBufferSnapshot,
  41    transforms: SumTree<Transform>,
  42    pub version: usize,
  43}
  44
  45impl std::ops::Deref for InlaySnapshot {
  46    type Target = MultiBufferSnapshot;
  47
  48    fn deref(&self) -> &Self::Target {
  49        &self.buffer
  50    }
  51}
  52
  53#[derive(Clone, Debug)]
  54enum Transform {
  55    Isomorphic(MBTextSummary),
  56    Inlay(Inlay),
  57}
  58
  59impl sum_tree::Item for Transform {
  60    type Summary = TransformSummary;
  61
  62    #[ztracing::instrument(skip_all)]
  63    fn summary(&self, _: ()) -> Self::Summary {
  64        match self {
  65            Transform::Isomorphic(summary) => TransformSummary {
  66                input: *summary,
  67                output: *summary,
  68            },
  69            Transform::Inlay(inlay) => TransformSummary {
  70                input: MBTextSummary::default(),
  71                output: MBTextSummary::from(inlay.text().summary()),
  72            },
  73        }
  74    }
  75}
  76
  77#[derive(Clone, Debug, Default)]
  78struct TransformSummary {
  79    /// Summary of the text before inlays have been applied.
  80    input: MBTextSummary,
  81    /// Summary of the text after inlays have been applied.
  82    output: MBTextSummary,
  83}
  84
  85impl sum_tree::ContextLessSummary for TransformSummary {
  86    fn zero() -> Self {
  87        Default::default()
  88    }
  89
  90    fn add_summary(&mut self, other: &Self) {
  91        self.input += other.input;
  92        self.output += other.output;
  93    }
  94}
  95
  96pub type InlayEdit = Edit<InlayOffset>;
  97
  98#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
  99pub struct InlayOffset(pub MultiBufferOffset);
 100
 101impl Add for InlayOffset {
 102    type Output = Self;
 103
 104    fn add(self, rhs: Self) -> Self::Output {
 105        Self(self.0 + rhs.0)
 106    }
 107}
 108
 109impl Sub for InlayOffset {
 110    type Output = <MultiBufferOffset as Sub>::Output;
 111
 112    fn sub(self, rhs: Self) -> Self::Output {
 113        self.0 - rhs.0
 114    }
 115}
 116
 117impl<T> SubAssign<T> for InlayOffset
 118where
 119    MultiBufferOffset: SubAssign<T>,
 120{
 121    fn sub_assign(&mut self, rhs: T) {
 122        self.0 -= rhs;
 123    }
 124}
 125
 126impl<T> Add<T> for InlayOffset
 127where
 128    MultiBufferOffset: Add<T, Output = MultiBufferOffset>,
 129{
 130    type Output = Self;
 131
 132    fn add(self, rhs: T) -> Self::Output {
 133        Self(self.0 + rhs)
 134    }
 135}
 136
 137impl AddAssign for InlayOffset {
 138    fn add_assign(&mut self, rhs: Self) {
 139        self.0 += rhs.0;
 140    }
 141}
 142
 143impl<T> AddAssign<T> for InlayOffset
 144where
 145    MultiBufferOffset: AddAssign<T>,
 146{
 147    fn add_assign(&mut self, rhs: T) {
 148        self.0 += rhs;
 149    }
 150}
 151
 152impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
 153    fn zero(_cx: ()) -> Self {
 154        Default::default()
 155    }
 156
 157    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
 158        self.0 += summary.output.len;
 159    }
 160}
 161
 162#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 163pub struct InlayPoint(pub Point);
 164
 165impl Add for InlayPoint {
 166    type Output = Self;
 167
 168    fn add(self, rhs: Self) -> Self::Output {
 169        Self(self.0 + rhs.0)
 170    }
 171}
 172
 173impl Sub for InlayPoint {
 174    type Output = Self;
 175
 176    fn sub(self, rhs: Self) -> Self::Output {
 177        Self(self.0 - rhs.0)
 178    }
 179}
 180
 181impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
 182    fn zero(_cx: ()) -> Self {
 183        Default::default()
 184    }
 185
 186    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
 187        self.0 += &summary.output.lines;
 188    }
 189}
 190
 191impl<'a> sum_tree::Dimension<'a, TransformSummary> for MultiBufferOffset {
 192    fn zero(_cx: ()) -> Self {
 193        Default::default()
 194    }
 195
 196    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
 197        *self += summary.input.len;
 198    }
 199}
 200
 201impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
 202    fn zero(_cx: ()) -> Self {
 203        Default::default()
 204    }
 205
 206    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
 207        *self += &summary.input.lines;
 208    }
 209}
 210
 211#[derive(Clone)]
 212pub struct InlayBufferRows<'a> {
 213    transforms: Cursor<'a, 'static, Transform, Dimensions<InlayPoint, Point>>,
 214    buffer_rows: MultiBufferRows<'a>,
 215    inlay_row: u32,
 216    max_buffer_row: MultiBufferRow,
 217}
 218
 219pub struct InlayChunks<'a> {
 220    transforms: Cursor<'a, 'static, Transform, Dimensions<InlayOffset, MultiBufferOffset>>,
 221    buffer_chunks: CustomHighlightsChunks<'a>,
 222    buffer_chunk: Option<Chunk<'a>>,
 223    inlay_chunks: Option<text::ChunkWithBitmaps<'a>>,
 224    /// text, char bitmap, tabs bitmap
 225    inlay_chunk: Option<ChunkBitmaps<'a>>,
 226    output_offset: InlayOffset,
 227    max_output_offset: InlayOffset,
 228    highlight_styles: HighlightStyles,
 229    highlights: Highlights<'a>,
 230    snapshot: &'a InlaySnapshot,
 231}
 232
 233#[derive(Clone)]
 234pub struct InlayChunk<'a> {
 235    pub chunk: Chunk<'a>,
 236    /// Whether the inlay should be customly rendered.
 237    pub renderer: Option<ChunkRenderer>,
 238}
 239
 240impl InlayChunks<'_> {
 241    #[ztracing::instrument(skip_all)]
 242    pub fn seek(&mut self, new_range: Range<InlayOffset>) {
 243        self.transforms.seek(&new_range.start, Bias::Right);
 244
 245        let buffer_range = self.snapshot.to_buffer_offset(new_range.start)
 246            ..self.snapshot.to_buffer_offset(new_range.end);
 247        self.buffer_chunks.seek(buffer_range);
 248        self.inlay_chunks = None;
 249        self.buffer_chunk = None;
 250        self.output_offset = new_range.start;
 251        self.max_output_offset = new_range.end;
 252    }
 253
 254    pub fn offset(&self) -> InlayOffset {
 255        self.output_offset
 256    }
 257}
 258
 259impl<'a> Iterator for InlayChunks<'a> {
 260    type Item = InlayChunk<'a>;
 261
 262    #[ztracing::instrument(skip_all)]
 263    fn next(&mut self) -> Option<Self::Item> {
 264        if self.output_offset == self.max_output_offset {
 265            return None;
 266        }
 267
 268        let chunk = match self.transforms.item()? {
 269            Transform::Isomorphic(_) => {
 270                let chunk = self
 271                    .buffer_chunk
 272                    .get_or_insert_with(|| self.buffer_chunks.next().unwrap());
 273                if chunk.text.is_empty() {
 274                    *chunk = self.buffer_chunks.next().unwrap();
 275                }
 276
 277                let desired_bytes = self.transforms.end().0.0 - self.output_offset.0;
 278
 279                // If we're already at the transform boundary, skip to the next transform
 280                if desired_bytes == 0 {
 281                    self.inlay_chunks = None;
 282                    self.transforms.next();
 283                    return self.next();
 284                }
 285
 286                // Determine split index handling edge cases
 287                let split_index = if desired_bytes >= chunk.text.len() {
 288                    chunk.text.len()
 289                } else {
 290                    chunk.text.ceil_char_boundary(desired_bytes)
 291                };
 292
 293                let (prefix, suffix) = chunk.text.split_at(split_index);
 294                self.output_offset.0 += prefix.len();
 295
 296                let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
 297                let chars = chunk.chars & mask;
 298                let tabs = chunk.tabs & mask;
 299                let newlines = chunk.newlines & mask;
 300
 301                chunk.chars = chunk.chars.unbounded_shr(split_index as u32);
 302                chunk.tabs = chunk.tabs.unbounded_shr(split_index as u32);
 303                chunk.newlines = chunk.newlines.unbounded_shr(split_index as u32);
 304                chunk.text = suffix;
 305
 306                InlayChunk {
 307                    chunk: Chunk {
 308                        text: prefix,
 309                        chars,
 310                        tabs,
 311                        newlines,
 312                        ..chunk.clone()
 313                    },
 314                    renderer: None,
 315                }
 316            }
 317            Transform::Inlay(inlay) => {
 318                let mut inlay_style_and_highlight = None;
 319                if let Some(inlay_highlights) = self.highlights.inlay_highlights {
 320                    for (_, inlay_id_to_data) in inlay_highlights.iter() {
 321                        let style_and_highlight = inlay_id_to_data.get(&inlay.id);
 322                        if style_and_highlight.is_some() {
 323                            inlay_style_and_highlight = style_and_highlight;
 324                            break;
 325                        }
 326                    }
 327                }
 328
 329                let mut renderer = None;
 330                let mut highlight_style = match inlay.id {
 331                    InlayId::EditPrediction(_) => self.highlight_styles.edit_prediction.map(|s| {
 332                        if inlay.text().chars().all(|c| c.is_whitespace()) {
 333                            s.whitespace
 334                        } else {
 335                            s.insertion
 336                        }
 337                    }),
 338                    InlayId::Hint(_) => self.highlight_styles.inlay_hint,
 339                    InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
 340                    InlayId::ReplResult(_) => {
 341                        let text = inlay.text().to_string();
 342                        renderer = Some(ChunkRenderer {
 343                            id: ChunkRendererId::Inlay(inlay.id),
 344                            render: Arc::new(move |cx| {
 345                                let colors = cx.theme().colors();
 346                                div()
 347                                    .flex()
 348                                    .flex_row()
 349                                    .items_center()
 350                                    .child(div().w_4())
 351                                    .child(
 352                                        div()
 353                                            .px_1()
 354                                            .rounded_sm()
 355                                            .bg(colors.surface_background)
 356                                            .text_color(colors.text_muted)
 357                                            .text_xs()
 358                                            .child(text.trim().to_string()),
 359                                    )
 360                                    .into_any_element()
 361                            }),
 362                            constrain_width: false,
 363                            measured_width: None,
 364                        });
 365                        self.highlight_styles.inlay_hint
 366                    }
 367                    InlayId::Color(_) => {
 368                        if let InlayContent::Color(color) = inlay.content {
 369                            renderer = Some(ChunkRenderer {
 370                                id: ChunkRendererId::Inlay(inlay.id),
 371                                render: Arc::new(move |cx| {
 372                                    div()
 373                                        .relative()
 374                                        .size_3p5()
 375                                        .child(
 376                                            div()
 377                                                .absolute()
 378                                                .right_1()
 379                                                .size_3()
 380                                                .border_1()
 381                                                .border_color(
 382                                                    if cx.theme().appearance().is_light() {
 383                                                        gpui::black().opacity(0.5)
 384                                                    } else {
 385                                                        gpui::white().opacity(0.5)
 386                                                    },
 387                                                )
 388                                                .bg(color),
 389                                        )
 390                                        .into_any_element()
 391                                }),
 392                                constrain_width: false,
 393                                measured_width: None,
 394                            });
 395                        }
 396                        self.highlight_styles.inlay_hint
 397                    }
 398                };
 399                let next_inlay_highlight_endpoint;
 400                let offset_in_inlay = self.output_offset - self.transforms.start().0;
 401                if let Some((style, highlight)) = inlay_style_and_highlight {
 402                    let range = &highlight.range;
 403                    if offset_in_inlay < range.start {
 404                        next_inlay_highlight_endpoint = range.start - offset_in_inlay;
 405                    } else if offset_in_inlay >= range.end {
 406                        next_inlay_highlight_endpoint = usize::MAX;
 407                    } else {
 408                        next_inlay_highlight_endpoint = range.end - offset_in_inlay;
 409                        highlight_style = highlight_style
 410                            .map(|highlight| highlight.highlight(*style))
 411                            .or_else(|| Some(*style));
 412                    }
 413                } else {
 414                    next_inlay_highlight_endpoint = usize::MAX;
 415                }
 416
 417                let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
 418                    let start = offset_in_inlay;
 419                    let end = cmp::min(self.max_output_offset, self.transforms.end().0)
 420                        - self.transforms.start().0;
 421                    let chunks = inlay.text().chunks_in_range(start..end);
 422                    text::ChunkWithBitmaps(chunks)
 423                });
 424                let ChunkBitmaps {
 425                    text: inlay_chunk,
 426                    chars,
 427                    tabs,
 428                    newlines,
 429                } = self
 430                    .inlay_chunk
 431                    .get_or_insert_with(|| inlay_chunks.next().unwrap());
 432
 433                // Determine split index handling edge cases
 434                let split_index = if next_inlay_highlight_endpoint >= inlay_chunk.len() {
 435                    inlay_chunk.len()
 436                } else if next_inlay_highlight_endpoint == 0 {
 437                    // Need to take at least one character to make progress
 438                    inlay_chunk
 439                        .chars()
 440                        .next()
 441                        .map(|c| c.len_utf8())
 442                        .unwrap_or(1)
 443                } else {
 444                    inlay_chunk.ceil_char_boundary(next_inlay_highlight_endpoint)
 445                };
 446
 447                let (chunk, remainder) = inlay_chunk.split_at(split_index);
 448                *inlay_chunk = remainder;
 449
 450                let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
 451                let new_chars = *chars & mask;
 452                let new_tabs = *tabs & mask;
 453                let new_newlines = *newlines & mask;
 454
 455                *chars = chars.unbounded_shr(split_index as u32);
 456                *tabs = tabs.unbounded_shr(split_index as u32);
 457                *newlines = newlines.unbounded_shr(split_index as u32);
 458
 459                if inlay_chunk.is_empty() {
 460                    self.inlay_chunk = None;
 461                }
 462
 463                self.output_offset.0 += chunk.len();
 464
 465                InlayChunk {
 466                    chunk: Chunk {
 467                        text: chunk,
 468                        chars: new_chars,
 469                        tabs: new_tabs,
 470                        newlines: new_newlines,
 471                        highlight_style,
 472                        is_inlay: true,
 473                        ..Chunk::default()
 474                    },
 475                    renderer,
 476                }
 477            }
 478        };
 479
 480        if self.output_offset >= self.transforms.end().0 {
 481            self.inlay_chunks = None;
 482            self.transforms.next();
 483        }
 484
 485        Some(chunk)
 486    }
 487}
 488
 489impl InlayBufferRows<'_> {
 490    #[ztracing::instrument(skip_all)]
 491    pub fn seek(&mut self, row: u32) {
 492        let inlay_point = InlayPoint::new(row, 0);
 493        self.transforms.seek(&inlay_point, Bias::Left);
 494
 495        let mut buffer_point = self.transforms.start().1;
 496        let buffer_row = MultiBufferRow(if row == 0 {
 497            0
 498        } else {
 499            match self.transforms.item() {
 500                Some(Transform::Isomorphic(_)) => {
 501                    buffer_point += inlay_point.0 - self.transforms.start().0.0;
 502                    buffer_point.row
 503                }
 504                _ => cmp::min(buffer_point.row + 1, self.max_buffer_row.0),
 505            }
 506        });
 507        self.inlay_row = inlay_point.row();
 508        self.buffer_rows.seek(buffer_row);
 509    }
 510}
 511
 512impl Iterator for InlayBufferRows<'_> {
 513    type Item = RowInfo;
 514
 515    #[ztracing::instrument(skip_all)]
 516    fn next(&mut self) -> Option<Self::Item> {
 517        let buffer_row = if self.inlay_row == 0 {
 518            self.buffer_rows.next().unwrap()
 519        } else {
 520            match self.transforms.item()? {
 521                Transform::Inlay(_) => Default::default(),
 522                Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
 523            }
 524        };
 525
 526        self.inlay_row += 1;
 527        self.transforms
 528            .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left);
 529
 530        Some(buffer_row)
 531    }
 532}
 533
 534impl InlayPoint {
 535    pub fn new(row: u32, column: u32) -> Self {
 536        Self(Point::new(row, column))
 537    }
 538
 539    pub fn row(self) -> u32 {
 540        self.0.row
 541    }
 542}
 543
 544impl InlayMap {
 545    #[ztracing::instrument(skip_all)]
 546    pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
 547        let version = 0;
 548        let snapshot = InlaySnapshot {
 549            buffer: buffer.clone(),
 550            transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), ()),
 551            version,
 552        };
 553
 554        (
 555            Self {
 556                snapshot: snapshot.clone(),
 557                inlays: Vec::new(),
 558            },
 559            snapshot,
 560        )
 561    }
 562
 563    #[ztracing::instrument(skip_all)]
 564    pub fn sync(
 565        &mut self,
 566        buffer_snapshot: MultiBufferSnapshot,
 567        mut buffer_edits: Vec<text::Edit<MultiBufferOffset>>,
 568    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 569        let snapshot = &mut self.snapshot;
 570
 571        if buffer_edits.is_empty()
 572            && snapshot.buffer.trailing_excerpt_update_count()
 573                != buffer_snapshot.trailing_excerpt_update_count()
 574        {
 575            buffer_edits.push(Edit {
 576                old: snapshot.buffer.len()..snapshot.buffer.len(),
 577                new: buffer_snapshot.len()..buffer_snapshot.len(),
 578            });
 579        }
 580
 581        if buffer_edits.is_empty() {
 582            if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
 583                || snapshot.buffer.non_text_state_update_count()
 584                    != buffer_snapshot.non_text_state_update_count()
 585                || snapshot.buffer.trailing_excerpt_update_count()
 586                    != buffer_snapshot.trailing_excerpt_update_count()
 587            {
 588                snapshot.version += 1;
 589            }
 590
 591            snapshot.buffer = buffer_snapshot;
 592            (snapshot.clone(), Vec::new())
 593        } else {
 594            let mut inlay_edits = Patch::default();
 595            let mut new_transforms = SumTree::default();
 596            let mut cursor = snapshot
 597                .transforms
 598                .cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
 599            let mut buffer_edits_iter = buffer_edits.iter().peekable();
 600            while let Some(buffer_edit) = buffer_edits_iter.next() {
 601                new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left), ());
 602                if let Some(Transform::Isomorphic(transform)) = cursor.item()
 603                    && cursor.end().0 == buffer_edit.old.start
 604                {
 605                    push_isomorphic(&mut new_transforms, *transform);
 606                    cursor.next();
 607                }
 608
 609                // Remove all the inlays and transforms contained by the edit.
 610                let old_start = cursor.start().1 + (buffer_edit.old.start - cursor.start().0);
 611                cursor.seek(&buffer_edit.old.end, Bias::Right);
 612                let old_end = cursor.start().1 + (buffer_edit.old.end - cursor.start().0);
 613
 614                // Push the unchanged prefix.
 615                let prefix_start = new_transforms.summary().input.len;
 616                let prefix_end = buffer_edit.new.start;
 617                push_isomorphic(
 618                    &mut new_transforms,
 619                    buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
 620                );
 621                let new_start = InlayOffset(new_transforms.summary().output.len);
 622
 623                let start_ix = match self.inlays.binary_search_by(|probe| {
 624                    probe
 625                        .position
 626                        .to_offset(&buffer_snapshot)
 627                        .cmp(&buffer_edit.new.start)
 628                        .then(std::cmp::Ordering::Greater)
 629                }) {
 630                    Ok(ix) | Err(ix) => ix,
 631                };
 632
 633                for inlay in &self.inlays[start_ix..] {
 634                    if !inlay.position.is_valid(&buffer_snapshot) {
 635                        continue;
 636                    }
 637                    let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
 638                    if buffer_offset > buffer_edit.new.end {
 639                        break;
 640                    }
 641
 642                    let prefix_start = new_transforms.summary().input.len;
 643                    let prefix_end = buffer_offset;
 644                    push_isomorphic(
 645                        &mut new_transforms,
 646                        buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
 647                    );
 648
 649                    new_transforms.push(Transform::Inlay(inlay.clone()), ());
 650                }
 651
 652                // Apply the rest of the edit.
 653                let transform_start = new_transforms.summary().input.len;
 654                push_isomorphic(
 655                    &mut new_transforms,
 656                    buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
 657                );
 658                let new_end = InlayOffset(new_transforms.summary().output.len);
 659                inlay_edits.push(Edit {
 660                    old: old_start..old_end,
 661                    new: new_start..new_end,
 662                });
 663
 664                // If the next edit doesn't intersect the current isomorphic transform, then
 665                // we can push its remainder.
 666                if buffer_edits_iter
 667                    .peek()
 668                    .is_none_or(|edit| edit.old.start >= cursor.end().0)
 669                {
 670                    let transform_start = new_transforms.summary().input.len;
 671                    let transform_end =
 672                        buffer_edit.new.end + (cursor.end().0 - buffer_edit.old.end);
 673                    push_isomorphic(
 674                        &mut new_transforms,
 675                        buffer_snapshot.text_summary_for_range(transform_start..transform_end),
 676                    );
 677                    cursor.next();
 678                }
 679            }
 680
 681            new_transforms.append(cursor.suffix(), ());
 682            if new_transforms.is_empty() {
 683                new_transforms.push(Transform::Isomorphic(Default::default()), ());
 684            }
 685
 686            drop(cursor);
 687            snapshot.transforms = new_transforms;
 688            snapshot.version += 1;
 689            snapshot.buffer = buffer_snapshot;
 690            snapshot.check_invariants();
 691
 692            (snapshot.clone(), inlay_edits.into_inner())
 693        }
 694    }
 695
 696    #[ztracing::instrument(skip_all)]
 697    pub fn splice(
 698        &mut self,
 699        to_remove: &[InlayId],
 700        to_insert: Vec<Inlay>,
 701    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 702        let snapshot = &mut self.snapshot;
 703        let mut edits = BTreeSet::new();
 704
 705        self.inlays.retain(|inlay| {
 706            let retain = !to_remove.contains(&inlay.id);
 707            if !retain {
 708                let offset = inlay.position.to_offset(&snapshot.buffer);
 709                edits.insert(offset);
 710            }
 711            retain
 712        });
 713
 714        for inlay_to_insert in to_insert {
 715            // Avoid inserting empty inlays.
 716            if inlay_to_insert.text().is_empty() {
 717                continue;
 718            }
 719
 720            let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
 721            match self.inlays.binary_search_by(|probe| {
 722                probe
 723                    .position
 724                    .cmp(&inlay_to_insert.position, &snapshot.buffer)
 725                    .then(std::cmp::Ordering::Less)
 726            }) {
 727                Ok(ix) | Err(ix) => {
 728                    self.inlays.insert(ix, inlay_to_insert);
 729                }
 730            }
 731
 732            edits.insert(offset);
 733        }
 734
 735        let buffer_edits = edits
 736            .into_iter()
 737            .map(|offset| Edit {
 738                old: offset..offset,
 739                new: offset..offset,
 740            })
 741            .collect();
 742        let buffer_snapshot = snapshot.buffer.clone();
 743        let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
 744        (snapshot, edits)
 745    }
 746
 747    #[ztracing::instrument(skip_all)]
 748    pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> + Default {
 749        self.inlays.iter()
 750    }
 751
 752    #[cfg(test)]
 753    #[ztracing::instrument(skip_all)]
 754    pub(crate) fn randomly_mutate(
 755        &mut self,
 756        next_inlay_id: &mut usize,
 757        rng: &mut rand::rngs::StdRng,
 758    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 759        use rand::prelude::*;
 760        use util::post_inc;
 761
 762        let mut to_remove = Vec::new();
 763        let mut to_insert = Vec::new();
 764        let snapshot = &mut self.snapshot;
 765        for i in 0..rng.random_range(1..=5) {
 766            if self.inlays.is_empty() || rng.random() {
 767                let position = snapshot
 768                    .buffer
 769                    .random_byte_range(MultiBufferOffset(0), rng)
 770                    .start;
 771                let bias = if rng.random() {
 772                    Bias::Left
 773                } else {
 774                    Bias::Right
 775                };
 776                let len = if rng.random_bool(0.01) {
 777                    0
 778                } else {
 779                    rng.random_range(1..=5)
 780                };
 781                let text = util::RandomCharIter::new(&mut *rng)
 782                    .filter(|ch| *ch != '\r')
 783                    .take(len)
 784                    .collect::<String>();
 785
 786                let next_inlay = if i % 2 == 0 {
 787                    Inlay::mock_hint(
 788                        post_inc(next_inlay_id),
 789                        snapshot.buffer.anchor_at(position, bias),
 790                        &text,
 791                    )
 792                } else {
 793                    Inlay::edit_prediction(
 794                        post_inc(next_inlay_id),
 795                        snapshot.buffer.anchor_at(position, bias),
 796                        &text,
 797                    )
 798                };
 799                let inlay_id = next_inlay.id;
 800                log::info!(
 801                    "creating inlay {inlay_id:?} at buffer offset {position} with bias {bias:?} and text {text:?}"
 802                );
 803                to_insert.push(next_inlay);
 804            } else {
 805                to_remove.push(
 806                    self.inlays
 807                        .iter()
 808                        .choose(rng)
 809                        .map(|inlay| inlay.id)
 810                        .unwrap(),
 811                );
 812            }
 813        }
 814        log::info!("removing inlays: {:?}", to_remove);
 815
 816        let (snapshot, edits) = self.splice(&to_remove, to_insert);
 817        (snapshot, edits)
 818    }
 819}
 820
 821impl InlaySnapshot {
 822    #[ztracing::instrument(skip_all)]
 823    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
 824        let (start, _, item) = self.transforms.find::<Dimensions<
 825            InlayOffset,
 826            InlayPoint,
 827            MultiBufferOffset,
 828        >, _>((), &offset, Bias::Right);
 829        let overshoot = offset.0 - start.0.0;
 830        match item {
 831            Some(Transform::Isomorphic(_)) => {
 832                let buffer_offset_start = start.2;
 833                let buffer_offset_end = buffer_offset_start + overshoot;
 834                let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
 835                let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
 836                InlayPoint(start.1.0 + (buffer_end - buffer_start))
 837            }
 838            Some(Transform::Inlay(inlay)) => {
 839                let overshoot = inlay.text().offset_to_point(overshoot);
 840                InlayPoint(start.1.0 + overshoot)
 841            }
 842            None => self.max_point(),
 843        }
 844    }
 845
 846    #[ztracing::instrument(skip_all)]
 847    pub fn len(&self) -> InlayOffset {
 848        InlayOffset(self.transforms.summary().output.len)
 849    }
 850
 851    #[ztracing::instrument(skip_all)]
 852    pub fn max_point(&self) -> InlayPoint {
 853        InlayPoint(self.transforms.summary().output.lines)
 854    }
 855
 856    #[ztracing::instrument(skip_all, fields(point))]
 857    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
 858        let (start, _, item) = self
 859            .transforms
 860            .find::<Dimensions<InlayPoint, InlayOffset, Point>, _>((), &point, Bias::Right);
 861        let overshoot = point.0 - start.0.0;
 862        match item {
 863            Some(Transform::Isomorphic(_)) => {
 864                let buffer_point_start = start.2;
 865                let buffer_point_end = buffer_point_start + overshoot;
 866                let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
 867                let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
 868                InlayOffset(start.1.0 + (buffer_offset_end - buffer_offset_start))
 869            }
 870            Some(Transform::Inlay(inlay)) => {
 871                let overshoot = inlay.text().point_to_offset(overshoot);
 872                InlayOffset(start.1.0 + overshoot)
 873            }
 874            None => self.len(),
 875        }
 876    }
 877    #[ztracing::instrument(skip_all)]
 878    pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
 879        let (start, _, item) =
 880            self.transforms
 881                .find::<Dimensions<InlayPoint, Point>, _>((), &point, Bias::Right);
 882        match item {
 883            Some(Transform::Isomorphic(_)) => {
 884                let overshoot = point.0 - start.0.0;
 885                start.1 + overshoot
 886            }
 887            Some(Transform::Inlay(_)) => start.1,
 888            None => self.buffer.max_point(),
 889        }
 890    }
 891    #[ztracing::instrument(skip_all)]
 892    pub fn to_buffer_offset(&self, offset: InlayOffset) -> MultiBufferOffset {
 893        let (start, _, item) = self
 894            .transforms
 895            .find::<Dimensions<InlayOffset, MultiBufferOffset>, _>((), &offset, Bias::Right);
 896        match item {
 897            Some(Transform::Isomorphic(_)) => {
 898                let overshoot = offset - start.0;
 899                start.1 + overshoot
 900            }
 901            Some(Transform::Inlay(_)) => start.1,
 902            None => self.buffer.len(),
 903        }
 904    }
 905
 906    #[ztracing::instrument(skip_all)]
 907    pub fn to_inlay_offset(&self, offset: MultiBufferOffset) -> InlayOffset {
 908        let mut cursor = self
 909            .transforms
 910            .cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
 911        cursor.seek(&offset, Bias::Left);
 912        loop {
 913            match cursor.item() {
 914                Some(Transform::Isomorphic(_)) => {
 915                    if offset == cursor.end().0 {
 916                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 917                            if inlay.position.bias() == Bias::Right {
 918                                break;
 919                            } else {
 920                                cursor.next();
 921                            }
 922                        }
 923                        return cursor.end().1;
 924                    } else {
 925                        let overshoot = offset - cursor.start().0;
 926                        return InlayOffset(cursor.start().1.0 + overshoot);
 927                    }
 928                }
 929                Some(Transform::Inlay(inlay)) => {
 930                    if inlay.position.bias() == Bias::Left {
 931                        cursor.next();
 932                    } else {
 933                        return cursor.start().1;
 934                    }
 935                }
 936                None => {
 937                    return self.len();
 938                }
 939            }
 940        }
 941    }
 942
 943    #[ztracing::instrument(skip_all)]
 944    pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
 945        self.inlay_point_cursor().map(point, Bias::Left)
 946    }
 947
 948    /// Converts a buffer offset range into one or more `InlayOffset` ranges that
 949    /// cover only the actual buffer text, skipping any inlay hint text that falls
 950    /// within the range. When there are no inlays the returned vec contains a
 951    /// single element identical to the input mapped into inlay-offset space.
 952    pub fn buffer_offset_to_inlay_ranges(
 953        &self,
 954        range: Range<MultiBufferOffset>,
 955    ) -> impl Iterator<Item = Range<InlayOffset>> {
 956        let mut cursor = self
 957            .transforms
 958            .cursor::<Dimensions<MultiBufferOffset, InlayOffset>>(());
 959        cursor.seek(&range.start, Bias::Right);
 960
 961        std::iter::from_fn(move || {
 962            loop {
 963                match cursor.item()? {
 964                    Transform::Isomorphic(_) => {
 965                        let seg_buffer_start = cursor.start().0;
 966                        let seg_buffer_end = cursor.end().0;
 967                        let seg_inlay_start = cursor.start().1;
 968
 969                        let overlap_start = cmp::max(range.start, seg_buffer_start);
 970                        let overlap_end = cmp::min(range.end, seg_buffer_end);
 971
 972                        let past_end = seg_buffer_end >= range.end;
 973                        cursor.next();
 974
 975                        if overlap_start < overlap_end {
 976                            let inlay_start =
 977                                InlayOffset(seg_inlay_start.0 + (overlap_start - seg_buffer_start));
 978                            let inlay_end =
 979                                InlayOffset(seg_inlay_start.0 + (overlap_end - seg_buffer_start));
 980                            return Some(inlay_start..inlay_end);
 981                        }
 982
 983                        if past_end {
 984                            return None;
 985                        }
 986                    }
 987                    Transform::Inlay(_) => cursor.next(),
 988                }
 989            }
 990        })
 991    }
 992
 993    #[ztracing::instrument(skip_all)]
 994    pub fn inlay_point_cursor(&self) -> InlayPointCursor<'_> {
 995        let cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(());
 996        InlayPointCursor {
 997            cursor,
 998            transforms: &self.transforms,
 999        }
1000    }
1001
1002    #[ztracing::instrument(skip_all)]
1003    pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
1004        let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
1005        cursor.seek(&point, Bias::Left);
1006        loop {
1007            match cursor.item() {
1008                Some(Transform::Isomorphic(transform)) => {
1009                    if cursor.start().0 == point {
1010                        if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
1011                            if inlay.position.bias() == Bias::Left {
1012                                return point;
1013                            } else if bias == Bias::Left {
1014                                cursor.prev();
1015                            } else if transform.first_line_chars == 0 {
1016                                point.0 += Point::new(1, 0);
1017                            } else {
1018                                point.0 += Point::new(0, 1);
1019                            }
1020                        } else {
1021                            return point;
1022                        }
1023                    } else if cursor.end().0 == point {
1024                        if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
1025                            if inlay.position.bias() == Bias::Right {
1026                                return point;
1027                            } else if bias == Bias::Right {
1028                                cursor.next();
1029                            } else if point.0.column == 0 {
1030                                point.0.row -= 1;
1031                                point.0.column = self.line_len(point.0.row);
1032                            } else {
1033                                point.0.column -= 1;
1034                            }
1035                        } else {
1036                            return point;
1037                        }
1038                    } else {
1039                        let overshoot = point.0 - cursor.start().0.0;
1040                        let buffer_point = cursor.start().1 + overshoot;
1041                        let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
1042                        let clipped_overshoot = clipped_buffer_point - cursor.start().1;
1043                        let clipped_point = InlayPoint(cursor.start().0.0 + clipped_overshoot);
1044                        if clipped_point == point {
1045                            return clipped_point;
1046                        } else {
1047                            point = clipped_point;
1048                        }
1049                    }
1050                }
1051                Some(Transform::Inlay(inlay)) => {
1052                    if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
1053                        match cursor.prev_item() {
1054                            Some(Transform::Inlay(inlay)) => {
1055                                if inlay.position.bias() == Bias::Left {
1056                                    return point;
1057                                }
1058                            }
1059                            _ => return point,
1060                        }
1061                    } else if point == cursor.end().0 && inlay.position.bias() == Bias::Left {
1062                        match cursor.next_item() {
1063                            Some(Transform::Inlay(inlay)) => {
1064                                if inlay.position.bias() == Bias::Right {
1065                                    return point;
1066                                }
1067                            }
1068                            _ => return point,
1069                        }
1070                    }
1071
1072                    if bias == Bias::Left {
1073                        point = cursor.start().0;
1074                        cursor.prev();
1075                    } else {
1076                        cursor.next();
1077                        point = cursor.start().0;
1078                    }
1079                }
1080                None => {
1081                    bias = bias.invert();
1082                    if bias == Bias::Left {
1083                        point = cursor.start().0;
1084                        cursor.prev();
1085                    } else {
1086                        cursor.next();
1087                        point = cursor.start().0;
1088                    }
1089                }
1090            }
1091        }
1092    }
1093
1094    #[ztracing::instrument(skip_all)]
1095    pub fn text_summary(&self) -> MBTextSummary {
1096        self.transforms.summary().output
1097    }
1098
1099    #[ztracing::instrument(skip_all)]
1100    pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> MBTextSummary {
1101        let mut summary = MBTextSummary::default();
1102
1103        let mut cursor = self
1104            .transforms
1105            .cursor::<Dimensions<InlayOffset, MultiBufferOffset>>(());
1106        cursor.seek(&range.start, Bias::Right);
1107
1108        let overshoot = range.start.0 - cursor.start().0.0;
1109        match cursor.item() {
1110            Some(Transform::Isomorphic(_)) => {
1111                let buffer_start = cursor.start().1;
1112                let suffix_start = buffer_start + overshoot;
1113                let suffix_end =
1114                    buffer_start + (cmp::min(cursor.end().0, range.end).0 - cursor.start().0.0);
1115                summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
1116                cursor.next();
1117            }
1118            Some(Transform::Inlay(inlay)) => {
1119                let suffix_start = overshoot;
1120                let suffix_end = cmp::min(cursor.end().0, range.end).0 - cursor.start().0.0;
1121                summary = MBTextSummary::from(
1122                    inlay
1123                        .text()
1124                        .cursor(suffix_start)
1125                        .summary::<TextSummary>(suffix_end),
1126                );
1127                cursor.next();
1128            }
1129            None => {}
1130        }
1131
1132        if range.end > cursor.start().0 {
1133            summary += cursor
1134                .summary::<_, TransformSummary>(&range.end, Bias::Right)
1135                .output;
1136
1137            let overshoot = range.end.0 - cursor.start().0.0;
1138            match cursor.item() {
1139                Some(Transform::Isomorphic(_)) => {
1140                    let prefix_start = cursor.start().1;
1141                    let prefix_end = prefix_start + overshoot;
1142                    summary += self
1143                        .buffer
1144                        .text_summary_for_range::<MBTextSummary, _>(prefix_start..prefix_end);
1145                }
1146                Some(Transform::Inlay(inlay)) => {
1147                    let prefix_end = overshoot;
1148                    summary += inlay.text().cursor(0).summary::<TextSummary>(prefix_end);
1149                }
1150                None => {}
1151            }
1152        }
1153
1154        summary
1155    }
1156
1157    #[ztracing::instrument(skip_all)]
1158    pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
1159        let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
1160        let inlay_point = InlayPoint::new(row, 0);
1161        cursor.seek(&inlay_point, Bias::Left);
1162
1163        let max_buffer_row = self.buffer.max_row();
1164        let mut buffer_point = cursor.start().1;
1165        let buffer_row = if row == 0 {
1166            MultiBufferRow(0)
1167        } else {
1168            match cursor.item() {
1169                Some(Transform::Isomorphic(_)) => {
1170                    buffer_point += inlay_point.0 - cursor.start().0.0;
1171                    MultiBufferRow(buffer_point.row)
1172                }
1173                _ => cmp::min(MultiBufferRow(buffer_point.row + 1), max_buffer_row),
1174            }
1175        };
1176
1177        InlayBufferRows {
1178            transforms: cursor,
1179            inlay_row: inlay_point.row(),
1180            buffer_rows: self.buffer.row_infos(buffer_row),
1181            max_buffer_row,
1182        }
1183    }
1184
1185    #[ztracing::instrument(skip_all)]
1186    pub fn line_len(&self, row: u32) -> u32 {
1187        let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
1188        let line_end = if row >= self.max_point().row() {
1189            self.len().0
1190        } else {
1191            self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
1192        };
1193        (line_end - line_start) as u32
1194    }
1195
1196    #[ztracing::instrument(skip_all)]
1197    pub(crate) fn chunks<'a>(
1198        &'a self,
1199        range: Range<InlayOffset>,
1200        language_aware: bool,
1201        highlights: Highlights<'a>,
1202    ) -> InlayChunks<'a> {
1203        let mut cursor = self
1204            .transforms
1205            .cursor::<Dimensions<InlayOffset, MultiBufferOffset>>(());
1206        cursor.seek(&range.start, Bias::Right);
1207
1208        let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
1209        let buffer_chunks = CustomHighlightsChunks::new(
1210            buffer_range,
1211            language_aware,
1212            highlights.text_highlights,
1213            highlights.semantic_token_highlights,
1214            &self.buffer,
1215        );
1216
1217        InlayChunks {
1218            transforms: cursor,
1219            buffer_chunks,
1220            inlay_chunks: None,
1221            inlay_chunk: None,
1222            buffer_chunk: None,
1223            output_offset: range.start,
1224            max_output_offset: range.end,
1225            highlight_styles: highlights.styles,
1226            highlights,
1227            snapshot: self,
1228        }
1229    }
1230
1231    #[cfg(test)]
1232    #[ztracing::instrument(skip_all)]
1233    pub fn text(&self) -> String {
1234        self.chunks(Default::default()..self.len(), false, Highlights::default())
1235            .map(|chunk| chunk.chunk.text)
1236            .collect()
1237    }
1238
1239    #[ztracing::instrument(skip_all)]
1240    fn check_invariants(&self) {
1241        #[cfg(any(debug_assertions, feature = "test-support"))]
1242        {
1243            assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1244            let mut transforms = self.transforms.iter().peekable();
1245            while let Some(transform) = transforms.next() {
1246                let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1247                if let Some(next_transform) = transforms.peek() {
1248                    let next_transform_is_isomorphic =
1249                        matches!(next_transform, Transform::Isomorphic(_));
1250                    assert!(
1251                        !transform_is_isomorphic || !next_transform_is_isomorphic,
1252                        "two adjacent isomorphic transforms"
1253                    );
1254                }
1255            }
1256        }
1257    }
1258}
1259
1260pub struct InlayPointCursor<'transforms> {
1261    cursor: Cursor<'transforms, 'static, Transform, Dimensions<Point, InlayPoint>>,
1262    transforms: &'transforms SumTree<Transform>,
1263}
1264
1265impl InlayPointCursor<'_> {
1266    #[ztracing::instrument(skip_all)]
1267    pub fn map(&mut self, point: Point, bias: Bias) -> InlayPoint {
1268        let cursor = &mut self.cursor;
1269        if cursor.did_seek() {
1270            cursor.seek_forward(&point, Bias::Left);
1271        } else {
1272            cursor.seek(&point, Bias::Left);
1273        }
1274        loop {
1275            match cursor.item() {
1276                Some(Transform::Isomorphic(_)) => {
1277                    if point == cursor.end().0 {
1278                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
1279                            if bias == Bias::Left && inlay.position.bias() == Bias::Right {
1280                                break;
1281                            } else {
1282                                cursor.next();
1283                            }
1284                        }
1285                        return cursor.end().1;
1286                    } else {
1287                        let overshoot = point - cursor.start().0;
1288                        return InlayPoint(cursor.start().1.0 + overshoot);
1289                    }
1290                }
1291                Some(Transform::Inlay(inlay)) => {
1292                    if inlay.position.bias() == Bias::Left || bias == Bias::Right {
1293                        cursor.next();
1294                    } else {
1295                        return cursor.start().1;
1296                    }
1297                }
1298                None => {
1299                    return InlayPoint(self.transforms.summary().output.lines);
1300                }
1301            }
1302        }
1303    }
1304}
1305
1306fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: MBTextSummary) {
1307    if summary.len == MultiBufferOffset(0) {
1308        return;
1309    }
1310
1311    let mut summary = Some(summary);
1312    sum_tree.update_last(
1313        |transform| {
1314            if let Transform::Isomorphic(transform) = transform {
1315                *transform += summary.take().unwrap();
1316            }
1317        },
1318        (),
1319    );
1320
1321    if let Some(summary) = summary {
1322        sum_tree.push(Transform::Isomorphic(summary), ());
1323    }
1324}
1325
1326#[cfg(test)]
1327mod tests {
1328    use super::*;
1329    use crate::{
1330        MultiBuffer,
1331        display_map::{HighlightKey, InlayHighlights},
1332        hover_links::InlayHighlight,
1333    };
1334    use collections::HashMap;
1335    use gpui::{App, HighlightStyle};
1336    use multi_buffer::Anchor;
1337    use project::{InlayHint, InlayHintLabel, ResolveState};
1338    use rand::prelude::*;
1339    use settings::SettingsStore;
1340    use std::{cmp::Reverse, env, sync::Arc};
1341    use sum_tree::TreeMap;
1342    use text::{Patch, Rope};
1343    use util::RandomCharIter;
1344    use util::post_inc;
1345
1346    #[test]
1347    fn test_inlay_properties_label_padding() {
1348        assert_eq!(
1349            Inlay::hint(
1350                InlayId::Hint(0),
1351                Anchor::min(),
1352                &InlayHint {
1353                    label: InlayHintLabel::String("a".to_string()),
1354                    position: text::Anchor::MIN,
1355                    padding_left: false,
1356                    padding_right: false,
1357                    tooltip: None,
1358                    kind: None,
1359                    resolve_state: ResolveState::Resolved,
1360                },
1361            )
1362            .text()
1363            .to_string(),
1364            "a",
1365            "Should not pad label if not requested"
1366        );
1367
1368        assert_eq!(
1369            Inlay::hint(
1370                InlayId::Hint(0),
1371                Anchor::min(),
1372                &InlayHint {
1373                    label: InlayHintLabel::String("a".to_string()),
1374                    position: text::Anchor::MIN,
1375                    padding_left: true,
1376                    padding_right: true,
1377                    tooltip: None,
1378                    kind: None,
1379                    resolve_state: ResolveState::Resolved,
1380                },
1381            )
1382            .text()
1383            .to_string(),
1384            " a ",
1385            "Should pad label for every side requested"
1386        );
1387
1388        assert_eq!(
1389            Inlay::hint(
1390                InlayId::Hint(0),
1391                Anchor::min(),
1392                &InlayHint {
1393                    label: InlayHintLabel::String(" a ".to_string()),
1394                    position: text::Anchor::MIN,
1395                    padding_left: false,
1396                    padding_right: false,
1397                    tooltip: None,
1398                    kind: None,
1399                    resolve_state: ResolveState::Resolved,
1400                },
1401            )
1402            .text()
1403            .to_string(),
1404            " a ",
1405            "Should not change already padded label"
1406        );
1407
1408        assert_eq!(
1409            Inlay::hint(
1410                InlayId::Hint(0),
1411                Anchor::min(),
1412                &InlayHint {
1413                    label: InlayHintLabel::String(" a ".to_string()),
1414                    position: text::Anchor::MIN,
1415                    padding_left: true,
1416                    padding_right: true,
1417                    tooltip: None,
1418                    kind: None,
1419                    resolve_state: ResolveState::Resolved,
1420                },
1421            )
1422            .text()
1423            .to_string(),
1424            " a ",
1425            "Should not change already padded label"
1426        );
1427    }
1428
1429    #[gpui::test]
1430    fn test_inlay_hint_padding_with_multibyte_chars() {
1431        assert_eq!(
1432            Inlay::hint(
1433                InlayId::Hint(0),
1434                Anchor::min(),
1435                &InlayHint {
1436                    label: InlayHintLabel::String("🎨".to_string()),
1437                    position: text::Anchor::MIN,
1438                    padding_left: true,
1439                    padding_right: true,
1440                    tooltip: None,
1441                    kind: None,
1442                    resolve_state: ResolveState::Resolved,
1443                },
1444            )
1445            .text()
1446            .to_string(),
1447            " 🎨 ",
1448            "Should pad single emoji correctly"
1449        );
1450    }
1451
1452    #[gpui::test]
1453    fn test_basic_inlays(cx: &mut App) {
1454        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1455        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1456        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1457        assert_eq!(inlay_snapshot.text(), "abcdefghi");
1458        let mut next_inlay_id = 0;
1459
1460        let (inlay_snapshot, _) = inlay_map.splice(
1461            &[],
1462            vec![Inlay::mock_hint(
1463                post_inc(&mut next_inlay_id),
1464                buffer
1465                    .read(cx)
1466                    .snapshot(cx)
1467                    .anchor_after(MultiBufferOffset(3)),
1468                "|123|",
1469            )],
1470        );
1471        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1472        assert_eq!(
1473            inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1474            InlayPoint::new(0, 0)
1475        );
1476        assert_eq!(
1477            inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1478            InlayPoint::new(0, 1)
1479        );
1480        assert_eq!(
1481            inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1482            InlayPoint::new(0, 2)
1483        );
1484        assert_eq!(
1485            inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1486            InlayPoint::new(0, 3)
1487        );
1488        assert_eq!(
1489            inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1490            InlayPoint::new(0, 9)
1491        );
1492        assert_eq!(
1493            inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1494            InlayPoint::new(0, 10)
1495        );
1496        assert_eq!(
1497            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1498            InlayPoint::new(0, 0)
1499        );
1500        assert_eq!(
1501            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1502            InlayPoint::new(0, 0)
1503        );
1504        assert_eq!(
1505            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1506            InlayPoint::new(0, 3)
1507        );
1508        assert_eq!(
1509            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1510            InlayPoint::new(0, 3)
1511        );
1512        assert_eq!(
1513            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1514            InlayPoint::new(0, 3)
1515        );
1516        assert_eq!(
1517            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1518            InlayPoint::new(0, 9)
1519        );
1520
1521        // Edits before or after the inlay should not affect it.
1522        buffer.update(cx, |buffer, cx| {
1523            buffer.edit(
1524                [
1525                    (MultiBufferOffset(2)..MultiBufferOffset(3), "x"),
1526                    (MultiBufferOffset(3)..MultiBufferOffset(3), "y"),
1527                    (MultiBufferOffset(4)..MultiBufferOffset(4), "z"),
1528                ],
1529                None,
1530                cx,
1531            )
1532        });
1533        let (inlay_snapshot, _) = inlay_map.sync(
1534            buffer.read(cx).snapshot(cx),
1535            buffer_edits.consume().into_inner(),
1536        );
1537        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1538
1539        // An edit surrounding the inlay should invalidate it.
1540        buffer.update(cx, |buffer, cx| {
1541            buffer.edit(
1542                [(MultiBufferOffset(4)..MultiBufferOffset(5), "D")],
1543                None,
1544                cx,
1545            )
1546        });
1547        let (inlay_snapshot, _) = inlay_map.sync(
1548            buffer.read(cx).snapshot(cx),
1549            buffer_edits.consume().into_inner(),
1550        );
1551        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1552
1553        let (inlay_snapshot, _) = inlay_map.splice(
1554            &[],
1555            vec![
1556                Inlay::mock_hint(
1557                    post_inc(&mut next_inlay_id),
1558                    buffer
1559                        .read(cx)
1560                        .snapshot(cx)
1561                        .anchor_before(MultiBufferOffset(3)),
1562                    "|123|",
1563                ),
1564                Inlay::edit_prediction(
1565                    post_inc(&mut next_inlay_id),
1566                    buffer
1567                        .read(cx)
1568                        .snapshot(cx)
1569                        .anchor_after(MultiBufferOffset(3)),
1570                    "|456|",
1571                ),
1572            ],
1573        );
1574        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1575
1576        // Edits ending where the inlay starts should not move it if it has a left bias.
1577        buffer.update(cx, |buffer, cx| {
1578            buffer.edit(
1579                [(MultiBufferOffset(3)..MultiBufferOffset(3), "JKL")],
1580                None,
1581                cx,
1582            )
1583        });
1584        let (inlay_snapshot, _) = inlay_map.sync(
1585            buffer.read(cx).snapshot(cx),
1586            buffer_edits.consume().into_inner(),
1587        );
1588        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1589
1590        assert_eq!(
1591            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1592            InlayPoint::new(0, 0)
1593        );
1594        assert_eq!(
1595            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1596            InlayPoint::new(0, 0)
1597        );
1598
1599        assert_eq!(
1600            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1601            InlayPoint::new(0, 1)
1602        );
1603        assert_eq!(
1604            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1605            InlayPoint::new(0, 1)
1606        );
1607
1608        assert_eq!(
1609            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1610            InlayPoint::new(0, 2)
1611        );
1612        assert_eq!(
1613            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1614            InlayPoint::new(0, 2)
1615        );
1616
1617        assert_eq!(
1618            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1619            InlayPoint::new(0, 2)
1620        );
1621        assert_eq!(
1622            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1623            InlayPoint::new(0, 8)
1624        );
1625
1626        assert_eq!(
1627            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1628            InlayPoint::new(0, 2)
1629        );
1630        assert_eq!(
1631            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1632            InlayPoint::new(0, 8)
1633        );
1634
1635        assert_eq!(
1636            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1637            InlayPoint::new(0, 2)
1638        );
1639        assert_eq!(
1640            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1641            InlayPoint::new(0, 8)
1642        );
1643
1644        assert_eq!(
1645            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1646            InlayPoint::new(0, 2)
1647        );
1648        assert_eq!(
1649            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1650            InlayPoint::new(0, 8)
1651        );
1652
1653        assert_eq!(
1654            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1655            InlayPoint::new(0, 2)
1656        );
1657        assert_eq!(
1658            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1659            InlayPoint::new(0, 8)
1660        );
1661
1662        assert_eq!(
1663            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1664            InlayPoint::new(0, 8)
1665        );
1666        assert_eq!(
1667            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1668            InlayPoint::new(0, 8)
1669        );
1670
1671        assert_eq!(
1672            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1673            InlayPoint::new(0, 9)
1674        );
1675        assert_eq!(
1676            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1677            InlayPoint::new(0, 9)
1678        );
1679
1680        assert_eq!(
1681            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1682            InlayPoint::new(0, 10)
1683        );
1684        assert_eq!(
1685            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1686            InlayPoint::new(0, 10)
1687        );
1688
1689        assert_eq!(
1690            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1691            InlayPoint::new(0, 11)
1692        );
1693        assert_eq!(
1694            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1695            InlayPoint::new(0, 11)
1696        );
1697
1698        assert_eq!(
1699            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1700            InlayPoint::new(0, 11)
1701        );
1702        assert_eq!(
1703            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1704            InlayPoint::new(0, 17)
1705        );
1706
1707        assert_eq!(
1708            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1709            InlayPoint::new(0, 11)
1710        );
1711        assert_eq!(
1712            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1713            InlayPoint::new(0, 17)
1714        );
1715
1716        assert_eq!(
1717            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1718            InlayPoint::new(0, 11)
1719        );
1720        assert_eq!(
1721            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1722            InlayPoint::new(0, 17)
1723        );
1724
1725        assert_eq!(
1726            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1727            InlayPoint::new(0, 11)
1728        );
1729        assert_eq!(
1730            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1731            InlayPoint::new(0, 17)
1732        );
1733
1734        assert_eq!(
1735            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1736            InlayPoint::new(0, 11)
1737        );
1738        assert_eq!(
1739            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1740            InlayPoint::new(0, 17)
1741        );
1742
1743        assert_eq!(
1744            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1745            InlayPoint::new(0, 17)
1746        );
1747        assert_eq!(
1748            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1749            InlayPoint::new(0, 17)
1750        );
1751
1752        assert_eq!(
1753            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1754            InlayPoint::new(0, 18)
1755        );
1756        assert_eq!(
1757            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1758            InlayPoint::new(0, 18)
1759        );
1760
1761        // The inlays can be manually removed.
1762        let (inlay_snapshot, _) = inlay_map.splice(
1763            &inlay_map
1764                .inlays
1765                .iter()
1766                .map(|inlay| inlay.id)
1767                .collect::<Vec<InlayId>>(),
1768            Vec::new(),
1769        );
1770        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1771    }
1772
1773    #[gpui::test]
1774    fn test_inlay_buffer_rows(cx: &mut App) {
1775        let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1776        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1777        assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1778        let mut next_inlay_id = 0;
1779
1780        let (inlay_snapshot, _) = inlay_map.splice(
1781            &[],
1782            vec![
1783                Inlay::mock_hint(
1784                    post_inc(&mut next_inlay_id),
1785                    buffer
1786                        .read(cx)
1787                        .snapshot(cx)
1788                        .anchor_before(MultiBufferOffset(0)),
1789                    "|123|\n",
1790                ),
1791                Inlay::mock_hint(
1792                    post_inc(&mut next_inlay_id),
1793                    buffer
1794                        .read(cx)
1795                        .snapshot(cx)
1796                        .anchor_before(MultiBufferOffset(4)),
1797                    "|456|",
1798                ),
1799                Inlay::edit_prediction(
1800                    post_inc(&mut next_inlay_id),
1801                    buffer
1802                        .read(cx)
1803                        .snapshot(cx)
1804                        .anchor_before(MultiBufferOffset(7)),
1805                    "\n|567|\n",
1806                ),
1807            ],
1808        );
1809        assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1810        assert_eq!(
1811            inlay_snapshot
1812                .row_infos(0)
1813                .map(|info| info.buffer_row)
1814                .collect::<Vec<_>>(),
1815            vec![Some(0), None, Some(1), None, None, Some(2)]
1816        );
1817    }
1818
1819    #[gpui::test(iterations = 100)]
1820    fn test_random_inlays(cx: &mut App, mut rng: StdRng) {
1821        init_test(cx);
1822
1823        let operations = env::var("OPERATIONS")
1824            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1825            .unwrap_or(10);
1826
1827        let len = rng.random_range(0..30);
1828        let buffer = if rng.random() {
1829            let text = util::RandomCharIter::new(&mut rng)
1830                .take(len)
1831                .collect::<String>();
1832            MultiBuffer::build_simple(&text, cx)
1833        } else {
1834            MultiBuffer::build_random(&mut rng, cx)
1835        };
1836        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1837        let mut next_inlay_id = 0;
1838        log::info!("buffer text: {:?}", buffer_snapshot.text());
1839        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1840        for _ in 0..operations {
1841            let mut inlay_edits = Patch::default();
1842
1843            let mut prev_inlay_text = inlay_snapshot.text();
1844            let mut buffer_edits = Vec::new();
1845            match rng.random_range(0..=100) {
1846                0..=50 => {
1847                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1848                    log::info!("mutated text: {:?}", snapshot.text());
1849                    inlay_edits = Patch::new(edits);
1850                }
1851                _ => buffer.update(cx, |buffer, cx| {
1852                    let subscription = buffer.subscribe();
1853                    let edit_count = rng.random_range(1..=5);
1854                    buffer.randomly_mutate(&mut rng, edit_count, cx);
1855                    buffer_snapshot = buffer.snapshot(cx);
1856                    let edits = subscription.consume().into_inner();
1857                    log::info!("editing {:?}", edits);
1858                    buffer_edits.extend(edits);
1859                }),
1860            };
1861
1862            let (new_inlay_snapshot, new_inlay_edits) =
1863                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1864            inlay_snapshot = new_inlay_snapshot;
1865            inlay_edits = inlay_edits.compose(new_inlay_edits);
1866
1867            log::info!("buffer text: {:?}", buffer_snapshot.text());
1868            log::info!("inlay text: {:?}", inlay_snapshot.text());
1869
1870            let inlays = inlay_map
1871                .inlays
1872                .iter()
1873                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1874                .map(|inlay| {
1875                    let offset = inlay.position.to_offset(&buffer_snapshot);
1876                    (offset, inlay.clone())
1877                })
1878                .collect::<Vec<_>>();
1879            let mut expected_text = Rope::from(&buffer_snapshot.text());
1880            for (offset, inlay) in inlays.iter().rev() {
1881                expected_text.replace(offset.0..offset.0, &inlay.text().to_string());
1882            }
1883            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1884
1885            let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::<Vec<_>>();
1886            assert_eq!(
1887                expected_buffer_rows.len() as u32,
1888                expected_text.max_point().row + 1
1889            );
1890            for row_start in 0..expected_buffer_rows.len() {
1891                assert_eq!(
1892                    inlay_snapshot
1893                        .row_infos(row_start as u32)
1894                        .collect::<Vec<_>>(),
1895                    &expected_buffer_rows[row_start..],
1896                    "incorrect buffer rows starting at {}",
1897                    row_start
1898                );
1899            }
1900
1901            let mut text_highlights = HashMap::default();
1902            let text_highlight_count = rng.random_range(0_usize..10);
1903            let mut text_highlight_ranges = (0..text_highlight_count)
1904                .map(|_| buffer_snapshot.random_byte_range(MultiBufferOffset(0), &mut rng))
1905                .collect::<Vec<_>>();
1906            text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1907            log::info!("highlighting text ranges {text_highlight_ranges:?}");
1908            text_highlights.insert(
1909                HighlightKey::ColorizeBracket(0),
1910                Arc::new((
1911                    HighlightStyle::default(),
1912                    text_highlight_ranges
1913                        .into_iter()
1914                        .map(|range| {
1915                            buffer_snapshot.anchor_before(range.start)
1916                                ..buffer_snapshot.anchor_after(range.end)
1917                        })
1918                        .collect(),
1919                )),
1920            );
1921            let text_highlights = Arc::new(text_highlights);
1922
1923            let mut inlay_highlights = InlayHighlights::default();
1924            if !inlays.is_empty() {
1925                let inlay_highlight_count = rng.random_range(0..inlays.len());
1926                let mut inlay_indices = BTreeSet::default();
1927                while inlay_indices.len() < inlay_highlight_count {
1928                    inlay_indices.insert(rng.random_range(0..inlays.len()));
1929                }
1930                let new_highlights = TreeMap::from_ordered_entries(
1931                    inlay_indices
1932                        .into_iter()
1933                        .filter_map(|i| {
1934                            let (_, inlay) = &inlays[i];
1935                            let inlay_text_len = inlay.text().len();
1936                            match inlay_text_len {
1937                                0 => None,
1938                                1 => Some(InlayHighlight {
1939                                    inlay: inlay.id,
1940                                    inlay_position: inlay.position,
1941                                    range: 0..1,
1942                                }),
1943                                n => {
1944                                    let inlay_text = inlay.text().to_string();
1945                                    let mut highlight_end = rng.random_range(1..n);
1946                                    let mut highlight_start = rng.random_range(0..highlight_end);
1947                                    while !inlay_text.is_char_boundary(highlight_end) {
1948                                        highlight_end += 1;
1949                                    }
1950                                    while !inlay_text.is_char_boundary(highlight_start) {
1951                                        highlight_start -= 1;
1952                                    }
1953                                    Some(InlayHighlight {
1954                                        inlay: inlay.id,
1955                                        inlay_position: inlay.position,
1956                                        range: highlight_start..highlight_end,
1957                                    })
1958                                }
1959                            }
1960                        })
1961                        .map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight))),
1962                );
1963                log::info!("highlighting inlay ranges {new_highlights:?}");
1964                inlay_highlights.insert(HighlightKey::Editor, new_highlights);
1965            }
1966
1967            for _ in 0..5 {
1968                let mut end = rng.random_range(0..=inlay_snapshot.len().0.0);
1969                end = expected_text.clip_offset(end, Bias::Right);
1970                let mut start = rng.random_range(0..=end);
1971                start = expected_text.clip_offset(start, Bias::Right);
1972
1973                let range =
1974                    InlayOffset(MultiBufferOffset(start))..InlayOffset(MultiBufferOffset(end));
1975                log::info!("calling inlay_snapshot.chunks({range:?})");
1976                let actual_text = inlay_snapshot
1977                    .chunks(
1978                        range,
1979                        false,
1980                        Highlights {
1981                            text_highlights: Some(&text_highlights),
1982                            inlay_highlights: Some(&inlay_highlights),
1983                            ..Highlights::default()
1984                        },
1985                    )
1986                    .map(|chunk| chunk.chunk.text)
1987                    .collect::<String>();
1988                assert_eq!(
1989                    actual_text,
1990                    expected_text.slice(start..end).to_string(),
1991                    "incorrect text in range {:?}",
1992                    start..end
1993                );
1994
1995                assert_eq!(
1996                    inlay_snapshot.text_summary_for_range(
1997                        InlayOffset(MultiBufferOffset(start))..InlayOffset(MultiBufferOffset(end))
1998                    ),
1999                    MBTextSummary::from(expected_text.slice(start..end).summary())
2000                );
2001            }
2002
2003            for edit in inlay_edits {
2004                prev_inlay_text.replace_range(
2005                    edit.new.start.0.0..edit.new.start.0.0 + edit.old_len(),
2006                    &inlay_snapshot.text()[edit.new.start.0.0..edit.new.end.0.0],
2007                );
2008            }
2009            assert_eq!(prev_inlay_text, inlay_snapshot.text());
2010
2011            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
2012            assert_eq!(expected_text.len(), inlay_snapshot.len().0.0);
2013
2014            let mut buffer_point = Point::default();
2015            let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
2016            let mut buffer_chars = buffer_snapshot.chars_at(MultiBufferOffset(0));
2017            loop {
2018                // Ensure conversion from buffer coordinates to inlay coordinates
2019                // is consistent.
2020                let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
2021                assert_eq!(
2022                    inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
2023                    inlay_point
2024                );
2025
2026                // No matter which bias we clip an inlay point with, it doesn't move
2027                // because it was constructed from a buffer point.
2028                assert_eq!(
2029                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
2030                    inlay_point,
2031                    "invalid inlay point for buffer point {:?} when clipped left",
2032                    buffer_point
2033                );
2034                assert_eq!(
2035                    inlay_snapshot.clip_point(inlay_point, Bias::Right),
2036                    inlay_point,
2037                    "invalid inlay point for buffer point {:?} when clipped right",
2038                    buffer_point
2039                );
2040
2041                if let Some(ch) = buffer_chars.next() {
2042                    if ch == '\n' {
2043                        buffer_point += Point::new(1, 0);
2044                    } else {
2045                        buffer_point += Point::new(0, ch.len_utf8() as u32);
2046                    }
2047
2048                    // Ensure that moving forward in the buffer always moves the inlay point forward as well.
2049                    let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
2050                    assert!(new_inlay_point > inlay_point);
2051                    inlay_point = new_inlay_point;
2052                } else {
2053                    break;
2054                }
2055            }
2056
2057            let mut inlay_point = InlayPoint::default();
2058            let mut inlay_offset = InlayOffset::default();
2059            for ch in expected_text.chars() {
2060                assert_eq!(
2061                    inlay_snapshot.to_offset(inlay_point),
2062                    inlay_offset,
2063                    "invalid to_offset({:?})",
2064                    inlay_point
2065                );
2066                assert_eq!(
2067                    inlay_snapshot.to_point(inlay_offset),
2068                    inlay_point,
2069                    "invalid to_point({:?})",
2070                    inlay_offset
2071                );
2072
2073                let mut bytes = [0; 4];
2074                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
2075                    inlay_offset.0 += 1;
2076                    if *byte == b'\n' {
2077                        inlay_point.0 += Point::new(1, 0);
2078                    } else {
2079                        inlay_point.0 += Point::new(0, 1);
2080                    }
2081
2082                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
2083                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
2084                    assert!(
2085                        clipped_left_point <= clipped_right_point,
2086                        "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
2087                        inlay_point,
2088                        clipped_left_point,
2089                        clipped_right_point
2090                    );
2091
2092                    // Ensure the clipped points are at valid text locations.
2093                    assert_eq!(
2094                        clipped_left_point.0,
2095                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
2096                    );
2097                    assert_eq!(
2098                        clipped_right_point.0,
2099                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
2100                    );
2101
2102                    // Ensure the clipped points never overshoot the end of the map.
2103                    assert!(clipped_left_point <= inlay_snapshot.max_point());
2104                    assert!(clipped_right_point <= inlay_snapshot.max_point());
2105
2106                    // Ensure the clipped points are at valid buffer locations.
2107                    assert_eq!(
2108                        inlay_snapshot
2109                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
2110                        clipped_left_point,
2111                        "to_buffer_point({:?}) = {:?}",
2112                        clipped_left_point,
2113                        inlay_snapshot.to_buffer_point(clipped_left_point),
2114                    );
2115                    assert_eq!(
2116                        inlay_snapshot
2117                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
2118                        clipped_right_point,
2119                        "to_buffer_point({:?}) = {:?}",
2120                        clipped_right_point,
2121                        inlay_snapshot.to_buffer_point(clipped_right_point),
2122                    );
2123                }
2124            }
2125        }
2126    }
2127
2128    #[gpui::test(iterations = 100)]
2129    fn test_random_chunk_bitmaps(cx: &mut gpui::App, mut rng: StdRng) {
2130        init_test(cx);
2131
2132        // Generate random buffer using existing test infrastructure
2133        let text_len = rng.random_range(0..10000);
2134        let buffer = if rng.random() {
2135            let text = RandomCharIter::new(&mut rng)
2136                .take(text_len)
2137                .collect::<String>();
2138            MultiBuffer::build_simple(&text, cx)
2139        } else {
2140            MultiBuffer::build_random(&mut rng, cx)
2141        };
2142
2143        let buffer_snapshot = buffer.read(cx).snapshot(cx);
2144        let (mut inlay_map, _) = InlayMap::new(buffer_snapshot.clone());
2145
2146        // Perform random mutations to add inlays
2147        let mut next_inlay_id = 0;
2148        let mutation_count = rng.random_range(1..10);
2149        for _ in 0..mutation_count {
2150            inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
2151        }
2152
2153        let (snapshot, _) = inlay_map.sync(buffer_snapshot, vec![]);
2154
2155        // Get all chunks and verify their bitmaps
2156        let chunks = snapshot.chunks(
2157            InlayOffset(MultiBufferOffset(0))..snapshot.len(),
2158            false,
2159            Highlights::default(),
2160        );
2161
2162        for chunk in chunks.into_iter().map(|inlay_chunk| inlay_chunk.chunk) {
2163            let chunk_text = chunk.text;
2164            let chars_bitmap = chunk.chars;
2165            let tabs_bitmap = chunk.tabs;
2166
2167            // Check empty chunks have empty bitmaps
2168            if chunk_text.is_empty() {
2169                assert_eq!(
2170                    chars_bitmap, 0,
2171                    "Empty chunk should have empty chars bitmap"
2172                );
2173                assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
2174                continue;
2175            }
2176
2177            // Verify that chunk text doesn't exceed 128 bytes
2178            assert!(
2179                chunk_text.len() <= 128,
2180                "Chunk text length {} exceeds 128 bytes",
2181                chunk_text.len()
2182            );
2183
2184            // Verify chars bitmap
2185            let char_indices = chunk_text
2186                .char_indices()
2187                .map(|(i, _)| i)
2188                .collect::<Vec<_>>();
2189
2190            for byte_idx in 0..chunk_text.len() {
2191                let should_have_bit = char_indices.contains(&byte_idx);
2192                let has_bit = chars_bitmap & (1 << byte_idx) != 0;
2193
2194                if has_bit != should_have_bit {
2195                    eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
2196                    eprintln!("Char indices: {:?}", char_indices);
2197                    eprintln!("Chars bitmap: {:#b}", chars_bitmap);
2198                    assert_eq!(
2199                        has_bit, should_have_bit,
2200                        "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
2201                        byte_idx, chunk_text, should_have_bit, has_bit
2202                    );
2203                }
2204            }
2205
2206            // Verify tabs bitmap
2207            for (byte_idx, byte) in chunk_text.bytes().enumerate() {
2208                let is_tab = byte == b'\t';
2209                let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
2210
2211                if has_bit != is_tab {
2212                    eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
2213                    eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
2214                    assert_eq!(
2215                        has_bit, is_tab,
2216                        "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
2217                        byte_idx, chunk_text, byte as char, is_tab, has_bit
2218                    );
2219                }
2220            }
2221        }
2222    }
2223
2224    fn init_test(cx: &mut App) {
2225        let store = SettingsStore::test(cx);
2226        cx.set_global(store);
2227        theme::init(theme::LoadThemes::JustBase, cx);
2228    }
2229
2230    /// Helper to create test highlights for an inlay
2231    fn create_inlay_highlights(
2232        inlay_id: InlayId,
2233        highlight_range: Range<usize>,
2234        position: Anchor,
2235    ) -> TreeMap<HighlightKey, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
2236        let mut inlay_highlights = TreeMap::default();
2237        let mut type_highlights = TreeMap::default();
2238        type_highlights.insert(
2239            inlay_id,
2240            (
2241                HighlightStyle::default(),
2242                InlayHighlight {
2243                    inlay: inlay_id,
2244                    range: highlight_range,
2245                    inlay_position: position,
2246                },
2247            ),
2248        );
2249        inlay_highlights.insert(HighlightKey::Editor, type_highlights);
2250        inlay_highlights
2251    }
2252
2253    #[gpui::test]
2254    fn test_inlay_utf8_boundary_panic_fix(cx: &mut App) {
2255        init_test(cx);
2256
2257        // This test verifies that we handle UTF-8 character boundaries correctly
2258        // when splitting inlay text for highlighting. Previously, this would panic
2259        // when trying to split at byte 13, which is in the middle of the '…' character.
2260        //
2261        // See https://github.com/zed-industries/zed/issues/33641
2262        let buffer = MultiBuffer::build_simple("fn main() {}\n", cx);
2263        let (mut inlay_map, _) = InlayMap::new(buffer.read(cx).snapshot(cx));
2264
2265        // Create an inlay with text that contains a multi-byte character
2266        // The string "SortingDirec…" contains an ellipsis character '…' which is 3 bytes (E2 80 A6)
2267        let inlay_text = "SortingDirec…";
2268        let position = buffer.read(cx).snapshot(cx).anchor_before(Point::new(0, 5));
2269
2270        let inlay = Inlay {
2271            id: InlayId::Hint(0),
2272            position,
2273            content: InlayContent::Text(text::Rope::from(inlay_text)),
2274        };
2275
2276        let (inlay_snapshot, _) = inlay_map.splice(&[], vec![inlay]);
2277
2278        // Create highlights that request a split at byte 13, which is in the middle
2279        // of the '…' character (bytes 12..15). We include the full character.
2280        let inlay_highlights = create_inlay_highlights(InlayId::Hint(0), 0..13, position);
2281
2282        let highlights = crate::display_map::Highlights {
2283            text_highlights: None,
2284            inlay_highlights: Some(&inlay_highlights),
2285            semantic_token_highlights: None,
2286            styles: crate::display_map::HighlightStyles::default(),
2287        };
2288
2289        // Collect chunks - this previously would panic
2290        let chunks: Vec<_> = inlay_snapshot
2291            .chunks(
2292                InlayOffset(MultiBufferOffset(0))..inlay_snapshot.len(),
2293                false,
2294                highlights,
2295            )
2296            .collect();
2297
2298        // Verify the chunks are correct
2299        let full_text: String = chunks.iter().map(|c| c.chunk.text).collect();
2300        assert_eq!(full_text, "fn maSortingDirec…in() {}\n");
2301
2302        // Verify the highlighted portion includes the complete ellipsis character
2303        let highlighted_chunks: Vec<_> = chunks
2304            .iter()
2305            .filter(|c| c.chunk.highlight_style.is_some() && c.chunk.is_inlay)
2306            .collect();
2307
2308        assert_eq!(highlighted_chunks.len(), 1);
2309        assert_eq!(highlighted_chunks[0].chunk.text, "SortingDirec…");
2310    }
2311
2312    #[gpui::test]
2313    fn test_inlay_utf8_boundaries(cx: &mut App) {
2314        init_test(cx);
2315
2316        struct TestCase {
2317            inlay_text: &'static str,
2318            highlight_range: Range<usize>,
2319            expected_highlighted: &'static str,
2320            description: &'static str,
2321        }
2322
2323        let test_cases = vec![
2324            TestCase {
2325                inlay_text: "Hello👋World",
2326                highlight_range: 0..7,
2327                expected_highlighted: "Hello👋",
2328                description: "Emoji boundary - rounds up to include full emoji",
2329            },
2330            TestCase {
2331                inlay_text: "Test→End",
2332                highlight_range: 0..5,
2333                expected_highlighted: "Test→",
2334                description: "Arrow boundary - rounds up to include full arrow",
2335            },
2336            TestCase {
2337                inlay_text: "café",
2338                highlight_range: 0..4,
2339                expected_highlighted: "café",
2340                description: "Accented char boundary - rounds up to include full é",
2341            },
2342            TestCase {
2343                inlay_text: "🎨🎭🎪",
2344                highlight_range: 0..5,
2345                expected_highlighted: "🎨🎭",
2346                description: "Multiple emojis - partial highlight",
2347            },
2348            TestCase {
2349                inlay_text: "普通话",
2350                highlight_range: 0..4,
2351                expected_highlighted: "普通",
2352                description: "Chinese characters - partial highlight",
2353            },
2354            TestCase {
2355                inlay_text: "Hello",
2356                highlight_range: 0..2,
2357                expected_highlighted: "He",
2358                description: "ASCII only - no adjustment needed",
2359            },
2360            TestCase {
2361                inlay_text: "👋",
2362                highlight_range: 0..1,
2363                expected_highlighted: "👋",
2364                description: "Single emoji - partial byte range includes whole char",
2365            },
2366            TestCase {
2367                inlay_text: "Test",
2368                highlight_range: 0..0,
2369                expected_highlighted: "",
2370                description: "Empty range",
2371            },
2372            TestCase {
2373                inlay_text: "🎨ABC",
2374                highlight_range: 2..5,
2375                expected_highlighted: "A",
2376                description: "Range starting mid-emoji skips the emoji",
2377            },
2378        ];
2379
2380        for test_case in test_cases {
2381            let buffer = MultiBuffer::build_simple("test", cx);
2382            let (mut inlay_map, _) = InlayMap::new(buffer.read(cx).snapshot(cx));
2383            let position = buffer.read(cx).snapshot(cx).anchor_before(Point::new(0, 2));
2384
2385            let inlay = Inlay {
2386                id: InlayId::Hint(0),
2387                position,
2388                content: InlayContent::Text(text::Rope::from(test_case.inlay_text)),
2389            };
2390
2391            let (inlay_snapshot, _) = inlay_map.splice(&[], vec![inlay]);
2392            let inlay_highlights = create_inlay_highlights(
2393                InlayId::Hint(0),
2394                test_case.highlight_range.clone(),
2395                position,
2396            );
2397
2398            let highlights = crate::display_map::Highlights {
2399                text_highlights: None,
2400                inlay_highlights: Some(&inlay_highlights),
2401                semantic_token_highlights: None,
2402                styles: crate::display_map::HighlightStyles::default(),
2403            };
2404
2405            let chunks: Vec<_> = inlay_snapshot
2406                .chunks(
2407                    InlayOffset(MultiBufferOffset(0))..inlay_snapshot.len(),
2408                    false,
2409                    highlights,
2410                )
2411                .collect();
2412
2413            // Verify we got chunks and they total to the expected text
2414            let full_text: String = chunks.iter().map(|c| c.chunk.text).collect();
2415            assert_eq!(
2416                full_text,
2417                format!("te{}st", test_case.inlay_text),
2418                "Full text mismatch for case: {}",
2419                test_case.description
2420            );
2421
2422            // Verify that the highlighted portion matches expectations
2423            let highlighted_text: String = chunks
2424                .iter()
2425                .filter(|c| c.chunk.highlight_style.is_some() && c.chunk.is_inlay)
2426                .map(|c| c.chunk.text)
2427                .collect();
2428            assert_eq!(
2429                highlighted_text, test_case.expected_highlighted,
2430                "Highlighted text mismatch for case: {} (text: '{}', range: {:?})",
2431                test_case.description, test_case.inlay_text, test_case.highlight_range
2432            );
2433        }
2434    }
2435}