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