inlay_map.rs

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