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