inlay_map.rs

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