inlay_map.rs

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