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