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