inlay_map.rs

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