inlay_map.rs

   1use crate::{ChunkRenderer, HighlightStyles, InlayId};
   2use collections::BTreeSet;
   3use gpui::{Hsla, Rgba};
   4use language::{Chunk, Edit, Point, TextSummary};
   5use multi_buffer::{
   6    Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset,
   7};
   8use std::{
   9    cmp,
  10    ops::{Add, AddAssign, Range, Sub, SubAssign},
  11    sync::Arc,
  12};
  13use sum_tree::{Bias, Cursor, SumTree};
  14use text::{Patch, Rope};
  15use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
  16
  17use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId};
  18
  19/// Decides where the [`Inlay`]s should be displayed.
  20///
  21/// See the [`display_map` module documentation](crate::display_map) for more information.
  22pub struct InlayMap {
  23    snapshot: InlaySnapshot,
  24    inlays: Vec<Inlay>,
  25}
  26
  27#[derive(Clone)]
  28pub struct InlaySnapshot {
  29    pub buffer: MultiBufferSnapshot,
  30    transforms: SumTree<Transform>,
  31    pub version: usize,
  32}
  33
  34#[derive(Clone, Debug)]
  35enum Transform {
  36    Isomorphic(TextSummary),
  37    Inlay(Inlay),
  38}
  39
  40#[derive(Debug, Clone)]
  41pub struct Inlay {
  42    pub id: InlayId,
  43    pub position: Anchor,
  44    pub text: text::Rope,
  45    color: Option<Hsla>,
  46}
  47
  48impl Inlay {
  49    pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self {
  50        let mut text = hint.text();
  51        if hint.padding_right && !text.ends_with(' ') {
  52            text.push(' ');
  53        }
  54        if hint.padding_left && !text.starts_with(' ') {
  55            text.insert(0, ' ');
  56        }
  57        Self {
  58            id: InlayId::Hint(id),
  59            position,
  60            text: text.into(),
  61            color: None,
  62        }
  63    }
  64
  65    #[cfg(any(test, feature = "test-support"))]
  66    pub fn mock_hint(id: usize, position: Anchor, text: impl Into<Rope>) -> Self {
  67        Self {
  68            id: InlayId::Hint(id),
  69            position,
  70            text: text.into(),
  71            color: None,
  72        }
  73    }
  74
  75    pub fn color(id: usize, position: Anchor, color: Rgba) -> Self {
  76        Self {
  77            id: InlayId::Color(id),
  78            position,
  79            text: Rope::from(""),
  80            color: Some(Hsla::from(color)),
  81        }
  82    }
  83
  84    pub fn inline_completion<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
  85        Self {
  86            id: InlayId::InlineCompletion(id),
  87            position,
  88            text: text.into(),
  89            color: None,
  90        }
  91    }
  92
  93    pub fn debugger<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
  94        Self {
  95            id: InlayId::DebuggerValue(id),
  96            position,
  97            text: text.into(),
  98            color: None,
  99        }
 100    }
 101
 102    #[cfg(any(test, feature = "test-support"))]
 103    pub fn get_color(&self) -> Option<Hsla> {
 104        self.color
 105    }
 106}
 107
 108impl sum_tree::Item for Transform {
 109    type Summary = TransformSummary;
 110
 111    fn summary(&self, _: &()) -> Self::Summary {
 112        match self {
 113            Transform::Isomorphic(summary) => TransformSummary {
 114                input: *summary,
 115                output: *summary,
 116            },
 117            Transform::Inlay(inlay) => TransformSummary {
 118                input: TextSummary::default(),
 119                output: inlay.text.summary(),
 120            },
 121        }
 122    }
 123}
 124
 125#[derive(Clone, Debug, Default)]
 126struct TransformSummary {
 127    input: TextSummary,
 128    output: TextSummary,
 129}
 130
 131impl sum_tree::Summary for TransformSummary {
 132    type Context = ();
 133
 134    fn zero(_cx: &()) -> Self {
 135        Default::default()
 136    }
 137
 138    fn add_summary(&mut self, other: &Self, _: &()) {
 139        self.input += &other.input;
 140        self.output += &other.output;
 141    }
 142}
 143
 144pub type InlayEdit = Edit<InlayOffset>;
 145
 146#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 147pub struct InlayOffset(pub usize);
 148
 149impl Add for InlayOffset {
 150    type Output = Self;
 151
 152    fn add(self, rhs: Self) -> Self::Output {
 153        Self(self.0 + rhs.0)
 154    }
 155}
 156
 157impl Sub for InlayOffset {
 158    type Output = Self;
 159
 160    fn sub(self, rhs: Self) -> Self::Output {
 161        Self(self.0 - rhs.0)
 162    }
 163}
 164
 165impl AddAssign for InlayOffset {
 166    fn add_assign(&mut self, rhs: Self) {
 167        self.0 += rhs.0;
 168    }
 169}
 170
 171impl SubAssign for InlayOffset {
 172    fn sub_assign(&mut self, rhs: Self) {
 173        self.0 -= rhs.0;
 174    }
 175}
 176
 177impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
 178    fn zero(_cx: &()) -> Self {
 179        Default::default()
 180    }
 181
 182    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 183        self.0 += &summary.output.len;
 184    }
 185}
 186
 187#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 188pub struct InlayPoint(pub Point);
 189
 190impl Add for InlayPoint {
 191    type Output = Self;
 192
 193    fn add(self, rhs: Self) -> Self::Output {
 194        Self(self.0 + rhs.0)
 195    }
 196}
 197
 198impl Sub for InlayPoint {
 199    type Output = Self;
 200
 201    fn sub(self, rhs: Self) -> Self::Output {
 202        Self(self.0 - rhs.0)
 203    }
 204}
 205
 206impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
 207    fn zero(_cx: &()) -> Self {
 208        Default::default()
 209    }
 210
 211    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 212        self.0 += &summary.output.lines;
 213    }
 214}
 215
 216impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
 217    fn zero(_cx: &()) -> Self {
 218        Default::default()
 219    }
 220
 221    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 222        *self += &summary.input.len;
 223    }
 224}
 225
 226impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
 227    fn zero(_cx: &()) -> Self {
 228        Default::default()
 229    }
 230
 231    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 232        *self += &summary.input.lines;
 233    }
 234}
 235
 236#[derive(Clone)]
 237pub struct InlayBufferRows<'a> {
 238    transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
 239    buffer_rows: MultiBufferRows<'a>,
 240    inlay_row: u32,
 241    max_buffer_row: MultiBufferRow,
 242}
 243
 244pub struct InlayChunks<'a> {
 245    transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
 246    buffer_chunks: CustomHighlightsChunks<'a>,
 247    buffer_chunk: Option<Chunk<'a>>,
 248    inlay_chunks: Option<text::Chunks<'a>>,
 249    inlay_chunk: Option<&'a str>,
 250    output_offset: InlayOffset,
 251    max_output_offset: InlayOffset,
 252    highlight_styles: HighlightStyles,
 253    highlights: Highlights<'a>,
 254    snapshot: &'a InlaySnapshot,
 255}
 256
 257#[derive(Clone)]
 258pub struct InlayChunk<'a> {
 259    pub chunk: Chunk<'a>,
 260    /// Whether the inlay should be customly rendered.
 261    pub renderer: Option<ChunkRenderer>,
 262}
 263
 264impl InlayChunks<'_> {
 265    pub fn seek(&mut self, new_range: Range<InlayOffset>) {
 266        self.transforms.seek(&new_range.start, Bias::Right, &());
 267
 268        let buffer_range = self.snapshot.to_buffer_offset(new_range.start)
 269            ..self.snapshot.to_buffer_offset(new_range.end);
 270        self.buffer_chunks.seek(buffer_range);
 271        self.inlay_chunks = None;
 272        self.buffer_chunk = None;
 273        self.output_offset = new_range.start;
 274        self.max_output_offset = new_range.end;
 275    }
 276
 277    pub fn offset(&self) -> InlayOffset {
 278        self.output_offset
 279    }
 280}
 281
 282impl<'a> Iterator for InlayChunks<'a> {
 283    type Item = InlayChunk<'a>;
 284
 285    fn next(&mut self) -> Option<Self::Item> {
 286        if self.output_offset == self.max_output_offset {
 287            return None;
 288        }
 289
 290        let chunk = match self.transforms.item()? {
 291            Transform::Isomorphic(_) => {
 292                let chunk = self
 293                    .buffer_chunk
 294                    .get_or_insert_with(|| self.buffer_chunks.next().unwrap());
 295                if chunk.text.is_empty() {
 296                    *chunk = self.buffer_chunks.next().unwrap();
 297                }
 298
 299                let (prefix, suffix) = chunk.text.split_at(
 300                    chunk
 301                        .text
 302                        .len()
 303                        .min(self.transforms.end(&()).0.0 - self.output_offset.0),
 304                );
 305
 306                chunk.text = suffix;
 307                self.output_offset.0 += prefix.len();
 308                InlayChunk {
 309                    chunk: Chunk {
 310                        text: prefix,
 311                        ..chunk.clone()
 312                    },
 313                    renderer: None,
 314                }
 315            }
 316            Transform::Inlay(inlay) => {
 317                let mut inlay_style_and_highlight = None;
 318                if let Some(inlay_highlights) = self.highlights.inlay_highlights {
 319                    for (_, inlay_id_to_data) in inlay_highlights.iter() {
 320                        let style_and_highlight = inlay_id_to_data.get(&inlay.id);
 321                        if style_and_highlight.is_some() {
 322                            inlay_style_and_highlight = style_and_highlight;
 323                            break;
 324                        }
 325                    }
 326                }
 327
 328                let mut renderer = None;
 329                let mut highlight_style = match inlay.id {
 330                    InlayId::InlineCompletion(_) => {
 331                        self.highlight_styles.inline_completion.map(|s| {
 332                            if inlay.text.chars().all(|c| c.is_whitespace()) {
 333                                s.whitespace
 334                            } else {
 335                                s.insertion
 336                            }
 337                        })
 338                    }
 339                    InlayId::Hint(_) => self.highlight_styles.inlay_hint,
 340                    InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
 341                    InlayId::Color(_) => {
 342                        if let Some(color) = inlay.color {
 343                            renderer = Some(ChunkRenderer {
 344                                id: ChunkRendererId::Inlay(inlay.id),
 345                                render: Arc::new(move |cx| {
 346                                    div()
 347                                        .relative()
 348                                        .size_3p5()
 349                                        .child(
 350                                            div()
 351                                                .absolute()
 352                                                .right_1()
 353                                                .size_3()
 354                                                .border_1()
 355                                                .border_color(cx.theme().colors().border)
 356                                                .bg(color),
 357                                        )
 358                                        .into_any_element()
 359                                }),
 360                                constrain_width: false,
 361                                measured_width: None,
 362                            });
 363                        }
 364                        self.highlight_styles.inlay_hint
 365                    }
 366                };
 367                let next_inlay_highlight_endpoint;
 368                let offset_in_inlay = self.output_offset - self.transforms.start().0;
 369                if let Some((style, highlight)) = inlay_style_and_highlight {
 370                    let range = &highlight.range;
 371                    if offset_in_inlay.0 < range.start {
 372                        next_inlay_highlight_endpoint = range.start - offset_in_inlay.0;
 373                    } else if offset_in_inlay.0 >= range.end {
 374                        next_inlay_highlight_endpoint = usize::MAX;
 375                    } else {
 376                        next_inlay_highlight_endpoint = range.end - offset_in_inlay.0;
 377                        highlight_style
 378                            .get_or_insert_with(Default::default)
 379                            .highlight(*style);
 380                    }
 381                } else {
 382                    next_inlay_highlight_endpoint = usize::MAX;
 383                }
 384
 385                let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
 386                    let start = offset_in_inlay;
 387                    let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
 388                        - self.transforms.start().0;
 389                    inlay.text.chunks_in_range(start.0..end.0)
 390                });
 391                let inlay_chunk = self
 392                    .inlay_chunk
 393                    .get_or_insert_with(|| inlay_chunks.next().unwrap());
 394                let (chunk, remainder) =
 395                    inlay_chunk.split_at(inlay_chunk.len().min(next_inlay_highlight_endpoint));
 396                *inlay_chunk = remainder;
 397                if inlay_chunk.is_empty() {
 398                    self.inlay_chunk = None;
 399                }
 400
 401                self.output_offset.0 += chunk.len();
 402
 403                InlayChunk {
 404                    chunk: Chunk {
 405                        text: chunk,
 406                        highlight_style,
 407                        is_inlay: true,
 408                        ..Chunk::default()
 409                    },
 410                    renderer,
 411                }
 412            }
 413        };
 414
 415        if self.output_offset == self.transforms.end(&()).0 {
 416            self.inlay_chunks = None;
 417            self.transforms.next(&());
 418        }
 419
 420        Some(chunk)
 421    }
 422}
 423
 424impl InlayBufferRows<'_> {
 425    pub fn seek(&mut self, row: u32) {
 426        let inlay_point = InlayPoint::new(row, 0);
 427        self.transforms.seek(&inlay_point, Bias::Left, &());
 428
 429        let mut buffer_point = self.transforms.start().1;
 430        let buffer_row = MultiBufferRow(if row == 0 {
 431            0
 432        } else {
 433            match self.transforms.item() {
 434                Some(Transform::Isomorphic(_)) => {
 435                    buffer_point += inlay_point.0 - self.transforms.start().0.0;
 436                    buffer_point.row
 437                }
 438                _ => cmp::min(buffer_point.row + 1, self.max_buffer_row.0),
 439            }
 440        });
 441        self.inlay_row = inlay_point.row();
 442        self.buffer_rows.seek(buffer_row);
 443    }
 444}
 445
 446impl Iterator for InlayBufferRows<'_> {
 447    type Item = RowInfo;
 448
 449    fn next(&mut self) -> Option<Self::Item> {
 450        let buffer_row = if self.inlay_row == 0 {
 451            self.buffer_rows.next().unwrap()
 452        } else {
 453            match self.transforms.item()? {
 454                Transform::Inlay(_) => Default::default(),
 455                Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
 456            }
 457        };
 458
 459        self.inlay_row += 1;
 460        self.transforms
 461            .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
 462
 463        Some(buffer_row)
 464    }
 465}
 466
 467impl InlayPoint {
 468    pub fn new(row: u32, column: u32) -> Self {
 469        Self(Point::new(row, column))
 470    }
 471
 472    pub fn row(self) -> u32 {
 473        self.0.row
 474    }
 475}
 476
 477impl InlayMap {
 478    pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
 479        let version = 0;
 480        let snapshot = InlaySnapshot {
 481            buffer: buffer.clone(),
 482            transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
 483            version,
 484        };
 485
 486        (
 487            Self {
 488                snapshot: snapshot.clone(),
 489                inlays: Vec::new(),
 490            },
 491            snapshot,
 492        )
 493    }
 494
 495    pub fn sync(
 496        &mut self,
 497        buffer_snapshot: MultiBufferSnapshot,
 498        mut buffer_edits: Vec<text::Edit<usize>>,
 499    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 500        let snapshot = &mut self.snapshot;
 501
 502        if buffer_edits.is_empty()
 503            && snapshot.buffer.trailing_excerpt_update_count()
 504                != buffer_snapshot.trailing_excerpt_update_count()
 505        {
 506            buffer_edits.push(Edit {
 507                old: snapshot.buffer.len()..snapshot.buffer.len(),
 508                new: buffer_snapshot.len()..buffer_snapshot.len(),
 509            });
 510        }
 511
 512        if buffer_edits.is_empty() {
 513            if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
 514                || snapshot.buffer.non_text_state_update_count()
 515                    != buffer_snapshot.non_text_state_update_count()
 516                || snapshot.buffer.trailing_excerpt_update_count()
 517                    != buffer_snapshot.trailing_excerpt_update_count()
 518            {
 519                snapshot.version += 1;
 520            }
 521
 522            snapshot.buffer = buffer_snapshot;
 523            (snapshot.clone(), Vec::new())
 524        } else {
 525            let mut inlay_edits = Patch::default();
 526            let mut new_transforms = SumTree::default();
 527            let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(&());
 528            let mut buffer_edits_iter = buffer_edits.iter().peekable();
 529            while let Some(buffer_edit) = buffer_edits_iter.next() {
 530                new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
 531                if let Some(Transform::Isomorphic(transform)) = cursor.item() {
 532                    if cursor.end(&()).0 == buffer_edit.old.start {
 533                        push_isomorphic(&mut new_transforms, *transform);
 534                        cursor.next(&());
 535                    }
 536                }
 537
 538                // Remove all the inlays and transforms contained by the edit.
 539                let old_start =
 540                    cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
 541                cursor.seek(&buffer_edit.old.end, Bias::Right, &());
 542                let old_end =
 543                    cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
 544
 545                // Push the unchanged prefix.
 546                let prefix_start = new_transforms.summary().input.len;
 547                let prefix_end = buffer_edit.new.start;
 548                push_isomorphic(
 549                    &mut new_transforms,
 550                    buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
 551                );
 552                let new_start = InlayOffset(new_transforms.summary().output.len);
 553
 554                let start_ix = match self.inlays.binary_search_by(|probe| {
 555                    probe
 556                        .position
 557                        .to_offset(&buffer_snapshot)
 558                        .cmp(&buffer_edit.new.start)
 559                        .then(std::cmp::Ordering::Greater)
 560                }) {
 561                    Ok(ix) | Err(ix) => ix,
 562                };
 563
 564                for inlay in &self.inlays[start_ix..] {
 565                    if !inlay.position.is_valid(&buffer_snapshot) {
 566                        continue;
 567                    }
 568                    let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
 569                    if buffer_offset > buffer_edit.new.end {
 570                        break;
 571                    }
 572
 573                    let prefix_start = new_transforms.summary().input.len;
 574                    let prefix_end = buffer_offset;
 575                    push_isomorphic(
 576                        &mut new_transforms,
 577                        buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
 578                    );
 579
 580                    new_transforms.push(Transform::Inlay(inlay.clone()), &());
 581                }
 582
 583                // Apply the rest of the edit.
 584                let transform_start = new_transforms.summary().input.len;
 585                push_isomorphic(
 586                    &mut new_transforms,
 587                    buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
 588                );
 589                let new_end = InlayOffset(new_transforms.summary().output.len);
 590                inlay_edits.push(Edit {
 591                    old: old_start..old_end,
 592                    new: new_start..new_end,
 593                });
 594
 595                // If the next edit doesn't intersect the current isomorphic transform, then
 596                // we can push its remainder.
 597                if buffer_edits_iter
 598                    .peek()
 599                    .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
 600                {
 601                    let transform_start = new_transforms.summary().input.len;
 602                    let transform_end =
 603                        buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
 604                    push_isomorphic(
 605                        &mut new_transforms,
 606                        buffer_snapshot.text_summary_for_range(transform_start..transform_end),
 607                    );
 608                    cursor.next(&());
 609                }
 610            }
 611
 612            new_transforms.append(cursor.suffix(&()), &());
 613            if new_transforms.is_empty() {
 614                new_transforms.push(Transform::Isomorphic(Default::default()), &());
 615            }
 616
 617            drop(cursor);
 618            snapshot.transforms = new_transforms;
 619            snapshot.version += 1;
 620            snapshot.buffer = buffer_snapshot;
 621            snapshot.check_invariants();
 622
 623            (snapshot.clone(), inlay_edits.into_inner())
 624        }
 625    }
 626
 627    pub fn splice(
 628        &mut self,
 629        to_remove: &[InlayId],
 630        to_insert: Vec<Inlay>,
 631    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 632        let snapshot = &mut self.snapshot;
 633        let mut edits = BTreeSet::new();
 634
 635        self.inlays.retain(|inlay| {
 636            let retain = !to_remove.contains(&inlay.id);
 637            if !retain {
 638                let offset = inlay.position.to_offset(&snapshot.buffer);
 639                edits.insert(offset);
 640            }
 641            retain
 642        });
 643
 644        for inlay_to_insert in to_insert {
 645            // Avoid inserting empty inlays.
 646            if inlay_to_insert.text.is_empty() {
 647                continue;
 648            }
 649
 650            let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
 651            match self.inlays.binary_search_by(|probe| {
 652                probe
 653                    .position
 654                    .cmp(&inlay_to_insert.position, &snapshot.buffer)
 655                    .then(std::cmp::Ordering::Less)
 656            }) {
 657                Ok(ix) | Err(ix) => {
 658                    self.inlays.insert(ix, inlay_to_insert);
 659                }
 660            }
 661
 662            edits.insert(offset);
 663        }
 664
 665        let buffer_edits = edits
 666            .into_iter()
 667            .map(|offset| Edit {
 668                old: offset..offset,
 669                new: offset..offset,
 670            })
 671            .collect();
 672        let buffer_snapshot = snapshot.buffer.clone();
 673        let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
 674        (snapshot, edits)
 675    }
 676
 677    pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
 678        self.inlays.iter()
 679    }
 680
 681    #[cfg(test)]
 682    pub(crate) fn randomly_mutate(
 683        &mut self,
 684        next_inlay_id: &mut usize,
 685        rng: &mut rand::rngs::StdRng,
 686    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 687        use rand::prelude::*;
 688        use util::post_inc;
 689
 690        let mut to_remove = Vec::new();
 691        let mut to_insert = Vec::new();
 692        let snapshot = &mut self.snapshot;
 693        for i in 0..rng.gen_range(1..=5) {
 694            if self.inlays.is_empty() || rng.r#gen() {
 695                let position = snapshot.buffer.random_byte_range(0, rng).start;
 696                let bias = if rng.r#gen() { Bias::Left } else { Bias::Right };
 697                let len = if rng.gen_bool(0.01) {
 698                    0
 699                } else {
 700                    rng.gen_range(1..=5)
 701                };
 702                let text = util::RandomCharIter::new(&mut *rng)
 703                    .filter(|ch| *ch != '\r')
 704                    .take(len)
 705                    .collect::<String>();
 706
 707                let next_inlay = if i % 2 == 0 {
 708                    Inlay::mock_hint(
 709                        post_inc(next_inlay_id),
 710                        snapshot.buffer.anchor_at(position, bias),
 711                        text.clone(),
 712                    )
 713                } else {
 714                    Inlay::inline_completion(
 715                        post_inc(next_inlay_id),
 716                        snapshot.buffer.anchor_at(position, bias),
 717                        text.clone(),
 718                    )
 719                };
 720                let inlay_id = next_inlay.id;
 721                log::info!(
 722                    "creating inlay {inlay_id:?} at buffer offset {position} with bias {bias:?} and text {text:?}"
 723                );
 724                to_insert.push(next_inlay);
 725            } else {
 726                to_remove.push(
 727                    self.inlays
 728                        .iter()
 729                        .choose(rng)
 730                        .map(|inlay| inlay.id)
 731                        .unwrap(),
 732                );
 733            }
 734        }
 735        log::info!("removing inlays: {:?}", to_remove);
 736
 737        let (snapshot, edits) = self.splice(&to_remove, to_insert);
 738        (snapshot, edits)
 739    }
 740}
 741
 742impl InlaySnapshot {
 743    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
 744        let mut cursor = self
 745            .transforms
 746            .cursor::<(InlayOffset, (InlayPoint, usize))>(&());
 747        cursor.seek(&offset, Bias::Right, &());
 748        let overshoot = offset.0 - cursor.start().0.0;
 749        match cursor.item() {
 750            Some(Transform::Isomorphic(_)) => {
 751                let buffer_offset_start = cursor.start().1.1;
 752                let buffer_offset_end = buffer_offset_start + overshoot;
 753                let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
 754                let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
 755                InlayPoint(cursor.start().1.0.0 + (buffer_end - buffer_start))
 756            }
 757            Some(Transform::Inlay(inlay)) => {
 758                let overshoot = inlay.text.offset_to_point(overshoot);
 759                InlayPoint(cursor.start().1.0.0 + overshoot)
 760            }
 761            None => self.max_point(),
 762        }
 763    }
 764
 765    pub fn len(&self) -> InlayOffset {
 766        InlayOffset(self.transforms.summary().output.len)
 767    }
 768
 769    pub fn max_point(&self) -> InlayPoint {
 770        InlayPoint(self.transforms.summary().output.lines)
 771    }
 772
 773    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
 774        let mut cursor = self
 775            .transforms
 776            .cursor::<(InlayPoint, (InlayOffset, Point))>(&());
 777        cursor.seek(&point, Bias::Right, &());
 778        let overshoot = point.0 - cursor.start().0.0;
 779        match cursor.item() {
 780            Some(Transform::Isomorphic(_)) => {
 781                let buffer_point_start = cursor.start().1.1;
 782                let buffer_point_end = buffer_point_start + overshoot;
 783                let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
 784                let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
 785                InlayOffset(cursor.start().1.0.0 + (buffer_offset_end - buffer_offset_start))
 786            }
 787            Some(Transform::Inlay(inlay)) => {
 788                let overshoot = inlay.text.point_to_offset(overshoot);
 789                InlayOffset(cursor.start().1.0.0 + overshoot)
 790            }
 791            None => self.len(),
 792        }
 793    }
 794    pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
 795        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
 796        cursor.seek(&point, Bias::Right, &());
 797        match cursor.item() {
 798            Some(Transform::Isomorphic(_)) => {
 799                let overshoot = point.0 - cursor.start().0.0;
 800                cursor.start().1 + overshoot
 801            }
 802            Some(Transform::Inlay(_)) => cursor.start().1,
 803            None => self.buffer.max_point(),
 804        }
 805    }
 806    pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
 807        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
 808        cursor.seek(&offset, Bias::Right, &());
 809        match cursor.item() {
 810            Some(Transform::Isomorphic(_)) => {
 811                let overshoot = offset - cursor.start().0;
 812                cursor.start().1 + overshoot.0
 813            }
 814            Some(Transform::Inlay(_)) => cursor.start().1,
 815            None => self.buffer.len(),
 816        }
 817    }
 818
 819    pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
 820        let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(&());
 821        cursor.seek(&offset, Bias::Left, &());
 822        loop {
 823            match cursor.item() {
 824                Some(Transform::Isomorphic(_)) => {
 825                    if offset == cursor.end(&()).0 {
 826                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 827                            if inlay.position.bias() == Bias::Right {
 828                                break;
 829                            } else {
 830                                cursor.next(&());
 831                            }
 832                        }
 833                        return cursor.end(&()).1;
 834                    } else {
 835                        let overshoot = offset - cursor.start().0;
 836                        return InlayOffset(cursor.start().1.0 + overshoot);
 837                    }
 838                }
 839                Some(Transform::Inlay(inlay)) => {
 840                    if inlay.position.bias() == Bias::Left {
 841                        cursor.next(&());
 842                    } else {
 843                        return cursor.start().1;
 844                    }
 845                }
 846                None => {
 847                    return self.len();
 848                }
 849            }
 850        }
 851    }
 852    pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
 853        let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&());
 854        cursor.seek(&point, Bias::Left, &());
 855        loop {
 856            match cursor.item() {
 857                Some(Transform::Isomorphic(_)) => {
 858                    if point == cursor.end(&()).0 {
 859                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 860                            if inlay.position.bias() == Bias::Right {
 861                                break;
 862                            } else {
 863                                cursor.next(&());
 864                            }
 865                        }
 866                        return cursor.end(&()).1;
 867                    } else {
 868                        let overshoot = point - cursor.start().0;
 869                        return InlayPoint(cursor.start().1.0 + overshoot);
 870                    }
 871                }
 872                Some(Transform::Inlay(inlay)) => {
 873                    if inlay.position.bias() == Bias::Left {
 874                        cursor.next(&());
 875                    } else {
 876                        return cursor.start().1;
 877                    }
 878                }
 879                None => {
 880                    return self.max_point();
 881                }
 882            }
 883        }
 884    }
 885
 886    pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
 887        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
 888        cursor.seek(&point, Bias::Left, &());
 889        loop {
 890            match cursor.item() {
 891                Some(Transform::Isomorphic(transform)) => {
 892                    if cursor.start().0 == point {
 893                        if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
 894                            if inlay.position.bias() == Bias::Left {
 895                                return point;
 896                            } else if bias == Bias::Left {
 897                                cursor.prev(&());
 898                            } else if transform.first_line_chars == 0 {
 899                                point.0 += Point::new(1, 0);
 900                            } else {
 901                                point.0 += Point::new(0, 1);
 902                            }
 903                        } else {
 904                            return point;
 905                        }
 906                    } else if cursor.end(&()).0 == point {
 907                        if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 908                            if inlay.position.bias() == Bias::Right {
 909                                return point;
 910                            } else if bias == Bias::Right {
 911                                cursor.next(&());
 912                            } else if point.0.column == 0 {
 913                                point.0.row -= 1;
 914                                point.0.column = self.line_len(point.0.row);
 915                            } else {
 916                                point.0.column -= 1;
 917                            }
 918                        } else {
 919                            return point;
 920                        }
 921                    } else {
 922                        let overshoot = point.0 - cursor.start().0.0;
 923                        let buffer_point = cursor.start().1 + overshoot;
 924                        let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
 925                        let clipped_overshoot = clipped_buffer_point - cursor.start().1;
 926                        let clipped_point = InlayPoint(cursor.start().0.0 + clipped_overshoot);
 927                        if clipped_point == point {
 928                            return clipped_point;
 929                        } else {
 930                            point = clipped_point;
 931                        }
 932                    }
 933                }
 934                Some(Transform::Inlay(inlay)) => {
 935                    if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
 936                        match cursor.prev_item() {
 937                            Some(Transform::Inlay(inlay)) => {
 938                                if inlay.position.bias() == Bias::Left {
 939                                    return point;
 940                                }
 941                            }
 942                            _ => return point,
 943                        }
 944                    } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left {
 945                        match cursor.next_item() {
 946                            Some(Transform::Inlay(inlay)) => {
 947                                if inlay.position.bias() == Bias::Right {
 948                                    return point;
 949                                }
 950                            }
 951                            _ => return point,
 952                        }
 953                    }
 954
 955                    if bias == Bias::Left {
 956                        point = cursor.start().0;
 957                        cursor.prev(&());
 958                    } else {
 959                        cursor.next(&());
 960                        point = cursor.start().0;
 961                    }
 962                }
 963                None => {
 964                    bias = bias.invert();
 965                    if bias == Bias::Left {
 966                        point = cursor.start().0;
 967                        cursor.prev(&());
 968                    } else {
 969                        cursor.next(&());
 970                        point = cursor.start().0;
 971                    }
 972                }
 973            }
 974        }
 975    }
 976
 977    pub fn text_summary(&self) -> TextSummary {
 978        self.transforms.summary().output
 979    }
 980
 981    pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
 982        let mut summary = TextSummary::default();
 983
 984        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
 985        cursor.seek(&range.start, Bias::Right, &());
 986
 987        let overshoot = range.start.0 - cursor.start().0.0;
 988        match cursor.item() {
 989            Some(Transform::Isomorphic(_)) => {
 990                let buffer_start = cursor.start().1;
 991                let suffix_start = buffer_start + overshoot;
 992                let suffix_end =
 993                    buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0.0);
 994                summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
 995                cursor.next(&());
 996            }
 997            Some(Transform::Inlay(inlay)) => {
 998                let suffix_start = overshoot;
 999                let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0.0;
1000                summary = inlay.text.cursor(suffix_start).summary(suffix_end);
1001                cursor.next(&());
1002            }
1003            None => {}
1004        }
1005
1006        if range.end > cursor.start().0 {
1007            summary += cursor
1008                .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
1009                .output;
1010
1011            let overshoot = range.end.0 - cursor.start().0.0;
1012            match cursor.item() {
1013                Some(Transform::Isomorphic(_)) => {
1014                    let prefix_start = cursor.start().1;
1015                    let prefix_end = prefix_start + overshoot;
1016                    summary += self
1017                        .buffer
1018                        .text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
1019                }
1020                Some(Transform::Inlay(inlay)) => {
1021                    let prefix_end = overshoot;
1022                    summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
1023                }
1024                None => {}
1025            }
1026        }
1027
1028        summary
1029    }
1030
1031    pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
1032        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
1033        let inlay_point = InlayPoint::new(row, 0);
1034        cursor.seek(&inlay_point, Bias::Left, &());
1035
1036        let max_buffer_row = self.buffer.max_row();
1037        let mut buffer_point = cursor.start().1;
1038        let buffer_row = if row == 0 {
1039            MultiBufferRow(0)
1040        } else {
1041            match cursor.item() {
1042                Some(Transform::Isomorphic(_)) => {
1043                    buffer_point += inlay_point.0 - cursor.start().0.0;
1044                    MultiBufferRow(buffer_point.row)
1045                }
1046                _ => cmp::min(MultiBufferRow(buffer_point.row + 1), max_buffer_row),
1047            }
1048        };
1049
1050        InlayBufferRows {
1051            transforms: cursor,
1052            inlay_row: inlay_point.row(),
1053            buffer_rows: self.buffer.row_infos(buffer_row),
1054            max_buffer_row,
1055        }
1056    }
1057
1058    pub fn line_len(&self, row: u32) -> u32 {
1059        let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
1060        let line_end = if row >= self.max_point().row() {
1061            self.len().0
1062        } else {
1063            self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
1064        };
1065        (line_end - line_start) as u32
1066    }
1067
1068    pub(crate) fn chunks<'a>(
1069        &'a self,
1070        range: Range<InlayOffset>,
1071        language_aware: bool,
1072        highlights: Highlights<'a>,
1073    ) -> InlayChunks<'a> {
1074        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
1075        cursor.seek(&range.start, Bias::Right, &());
1076
1077        let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
1078        let buffer_chunks = CustomHighlightsChunks::new(
1079            buffer_range,
1080            language_aware,
1081            highlights.text_highlights,
1082            &self.buffer,
1083        );
1084
1085        InlayChunks {
1086            transforms: cursor,
1087            buffer_chunks,
1088            inlay_chunks: None,
1089            inlay_chunk: None,
1090            buffer_chunk: None,
1091            output_offset: range.start,
1092            max_output_offset: range.end,
1093            highlight_styles: highlights.styles,
1094            highlights,
1095            snapshot: self,
1096        }
1097    }
1098
1099    #[cfg(test)]
1100    pub fn text(&self) -> String {
1101        self.chunks(Default::default()..self.len(), false, Highlights::default())
1102            .map(|chunk| chunk.chunk.text)
1103            .collect()
1104    }
1105
1106    fn check_invariants(&self) {
1107        #[cfg(any(debug_assertions, feature = "test-support"))]
1108        {
1109            assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1110            let mut transforms = self.transforms.iter().peekable();
1111            while let Some(transform) = transforms.next() {
1112                let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1113                if let Some(next_transform) = transforms.peek() {
1114                    let next_transform_is_isomorphic =
1115                        matches!(next_transform, Transform::Isomorphic(_));
1116                    assert!(
1117                        !transform_is_isomorphic || !next_transform_is_isomorphic,
1118                        "two adjacent isomorphic transforms"
1119                    );
1120                }
1121            }
1122        }
1123    }
1124}
1125
1126fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
1127    if summary.len == 0 {
1128        return;
1129    }
1130
1131    let mut summary = Some(summary);
1132    sum_tree.update_last(
1133        |transform| {
1134            if let Transform::Isomorphic(transform) = transform {
1135                *transform += summary.take().unwrap();
1136            }
1137        },
1138        &(),
1139    );
1140
1141    if let Some(summary) = summary {
1142        sum_tree.push(Transform::Isomorphic(summary), &());
1143    }
1144}
1145
1146#[cfg(test)]
1147mod tests {
1148    use super::*;
1149    use crate::{
1150        InlayId, MultiBuffer,
1151        display_map::{HighlightKey, InlayHighlights, TextHighlights},
1152        hover_links::InlayHighlight,
1153    };
1154    use gpui::{App, HighlightStyle};
1155    use project::{InlayHint, InlayHintLabel, ResolveState};
1156    use rand::prelude::*;
1157    use settings::SettingsStore;
1158    use std::{any::TypeId, cmp::Reverse, env, sync::Arc};
1159    use sum_tree::TreeMap;
1160    use text::Patch;
1161    use util::post_inc;
1162
1163    #[test]
1164    fn test_inlay_properties_label_padding() {
1165        assert_eq!(
1166            Inlay::hint(
1167                0,
1168                Anchor::min(),
1169                &InlayHint {
1170                    label: InlayHintLabel::String("a".to_string()),
1171                    position: text::Anchor::default(),
1172                    padding_left: false,
1173                    padding_right: false,
1174                    tooltip: None,
1175                    kind: None,
1176                    resolve_state: ResolveState::Resolved,
1177                },
1178            )
1179            .text
1180            .to_string(),
1181            "a",
1182            "Should not pad label if not requested"
1183        );
1184
1185        assert_eq!(
1186            Inlay::hint(
1187                0,
1188                Anchor::min(),
1189                &InlayHint {
1190                    label: InlayHintLabel::String("a".to_string()),
1191                    position: text::Anchor::default(),
1192                    padding_left: true,
1193                    padding_right: true,
1194                    tooltip: None,
1195                    kind: None,
1196                    resolve_state: ResolveState::Resolved,
1197                },
1198            )
1199            .text
1200            .to_string(),
1201            " a ",
1202            "Should pad label for every side requested"
1203        );
1204
1205        assert_eq!(
1206            Inlay::hint(
1207                0,
1208                Anchor::min(),
1209                &InlayHint {
1210                    label: InlayHintLabel::String(" a ".to_string()),
1211                    position: text::Anchor::default(),
1212                    padding_left: false,
1213                    padding_right: false,
1214                    tooltip: None,
1215                    kind: None,
1216                    resolve_state: ResolveState::Resolved,
1217                },
1218            )
1219            .text
1220            .to_string(),
1221            " a ",
1222            "Should not change already padded label"
1223        );
1224
1225        assert_eq!(
1226            Inlay::hint(
1227                0,
1228                Anchor::min(),
1229                &InlayHint {
1230                    label: InlayHintLabel::String(" a ".to_string()),
1231                    position: text::Anchor::default(),
1232                    padding_left: true,
1233                    padding_right: true,
1234                    tooltip: None,
1235                    kind: None,
1236                    resolve_state: ResolveState::Resolved,
1237                },
1238            )
1239            .text
1240            .to_string(),
1241            " a ",
1242            "Should not change already padded label"
1243        );
1244    }
1245
1246    #[gpui::test]
1247    fn test_basic_inlays(cx: &mut App) {
1248        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1249        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1250        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1251        assert_eq!(inlay_snapshot.text(), "abcdefghi");
1252        let mut next_inlay_id = 0;
1253
1254        let (inlay_snapshot, _) = inlay_map.splice(
1255            &[],
1256            vec![Inlay::mock_hint(
1257                post_inc(&mut next_inlay_id),
1258                buffer.read(cx).snapshot(cx).anchor_after(3),
1259                "|123|",
1260            )],
1261        );
1262        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1263        assert_eq!(
1264            inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1265            InlayPoint::new(0, 0)
1266        );
1267        assert_eq!(
1268            inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1269            InlayPoint::new(0, 1)
1270        );
1271        assert_eq!(
1272            inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1273            InlayPoint::new(0, 2)
1274        );
1275        assert_eq!(
1276            inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1277            InlayPoint::new(0, 3)
1278        );
1279        assert_eq!(
1280            inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1281            InlayPoint::new(0, 9)
1282        );
1283        assert_eq!(
1284            inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1285            InlayPoint::new(0, 10)
1286        );
1287        assert_eq!(
1288            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1289            InlayPoint::new(0, 0)
1290        );
1291        assert_eq!(
1292            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1293            InlayPoint::new(0, 0)
1294        );
1295        assert_eq!(
1296            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1297            InlayPoint::new(0, 3)
1298        );
1299        assert_eq!(
1300            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1301            InlayPoint::new(0, 3)
1302        );
1303        assert_eq!(
1304            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1305            InlayPoint::new(0, 3)
1306        );
1307        assert_eq!(
1308            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1309            InlayPoint::new(0, 9)
1310        );
1311
1312        // Edits before or after the inlay should not affect it.
1313        buffer.update(cx, |buffer, cx| {
1314            buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1315        });
1316        let (inlay_snapshot, _) = inlay_map.sync(
1317            buffer.read(cx).snapshot(cx),
1318            buffer_edits.consume().into_inner(),
1319        );
1320        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1321
1322        // An edit surrounding the inlay should invalidate it.
1323        buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1324        let (inlay_snapshot, _) = inlay_map.sync(
1325            buffer.read(cx).snapshot(cx),
1326            buffer_edits.consume().into_inner(),
1327        );
1328        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1329
1330        let (inlay_snapshot, _) = inlay_map.splice(
1331            &[],
1332            vec![
1333                Inlay::mock_hint(
1334                    post_inc(&mut next_inlay_id),
1335                    buffer.read(cx).snapshot(cx).anchor_before(3),
1336                    "|123|",
1337                ),
1338                Inlay::inline_completion(
1339                    post_inc(&mut next_inlay_id),
1340                    buffer.read(cx).snapshot(cx).anchor_after(3),
1341                    "|456|",
1342                ),
1343            ],
1344        );
1345        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1346
1347        // Edits ending where the inlay starts should not move it if it has a left bias.
1348        buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1349        let (inlay_snapshot, _) = inlay_map.sync(
1350            buffer.read(cx).snapshot(cx),
1351            buffer_edits.consume().into_inner(),
1352        );
1353        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1354
1355        assert_eq!(
1356            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1357            InlayPoint::new(0, 0)
1358        );
1359        assert_eq!(
1360            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1361            InlayPoint::new(0, 0)
1362        );
1363
1364        assert_eq!(
1365            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1366            InlayPoint::new(0, 1)
1367        );
1368        assert_eq!(
1369            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1370            InlayPoint::new(0, 1)
1371        );
1372
1373        assert_eq!(
1374            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1375            InlayPoint::new(0, 2)
1376        );
1377        assert_eq!(
1378            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1379            InlayPoint::new(0, 2)
1380        );
1381
1382        assert_eq!(
1383            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1384            InlayPoint::new(0, 2)
1385        );
1386        assert_eq!(
1387            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1388            InlayPoint::new(0, 8)
1389        );
1390
1391        assert_eq!(
1392            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1393            InlayPoint::new(0, 2)
1394        );
1395        assert_eq!(
1396            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1397            InlayPoint::new(0, 8)
1398        );
1399
1400        assert_eq!(
1401            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1402            InlayPoint::new(0, 2)
1403        );
1404        assert_eq!(
1405            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1406            InlayPoint::new(0, 8)
1407        );
1408
1409        assert_eq!(
1410            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1411            InlayPoint::new(0, 2)
1412        );
1413        assert_eq!(
1414            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1415            InlayPoint::new(0, 8)
1416        );
1417
1418        assert_eq!(
1419            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1420            InlayPoint::new(0, 2)
1421        );
1422        assert_eq!(
1423            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1424            InlayPoint::new(0, 8)
1425        );
1426
1427        assert_eq!(
1428            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1429            InlayPoint::new(0, 8)
1430        );
1431        assert_eq!(
1432            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1433            InlayPoint::new(0, 8)
1434        );
1435
1436        assert_eq!(
1437            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1438            InlayPoint::new(0, 9)
1439        );
1440        assert_eq!(
1441            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1442            InlayPoint::new(0, 9)
1443        );
1444
1445        assert_eq!(
1446            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1447            InlayPoint::new(0, 10)
1448        );
1449        assert_eq!(
1450            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1451            InlayPoint::new(0, 10)
1452        );
1453
1454        assert_eq!(
1455            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1456            InlayPoint::new(0, 11)
1457        );
1458        assert_eq!(
1459            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1460            InlayPoint::new(0, 11)
1461        );
1462
1463        assert_eq!(
1464            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1465            InlayPoint::new(0, 11)
1466        );
1467        assert_eq!(
1468            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1469            InlayPoint::new(0, 17)
1470        );
1471
1472        assert_eq!(
1473            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1474            InlayPoint::new(0, 11)
1475        );
1476        assert_eq!(
1477            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1478            InlayPoint::new(0, 17)
1479        );
1480
1481        assert_eq!(
1482            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1483            InlayPoint::new(0, 11)
1484        );
1485        assert_eq!(
1486            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1487            InlayPoint::new(0, 17)
1488        );
1489
1490        assert_eq!(
1491            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1492            InlayPoint::new(0, 11)
1493        );
1494        assert_eq!(
1495            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1496            InlayPoint::new(0, 17)
1497        );
1498
1499        assert_eq!(
1500            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1501            InlayPoint::new(0, 11)
1502        );
1503        assert_eq!(
1504            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1505            InlayPoint::new(0, 17)
1506        );
1507
1508        assert_eq!(
1509            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1510            InlayPoint::new(0, 17)
1511        );
1512        assert_eq!(
1513            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1514            InlayPoint::new(0, 17)
1515        );
1516
1517        assert_eq!(
1518            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1519            InlayPoint::new(0, 18)
1520        );
1521        assert_eq!(
1522            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1523            InlayPoint::new(0, 18)
1524        );
1525
1526        // The inlays can be manually removed.
1527        let (inlay_snapshot, _) = inlay_map.splice(
1528            &inlay_map
1529                .inlays
1530                .iter()
1531                .map(|inlay| inlay.id)
1532                .collect::<Vec<InlayId>>(),
1533            Vec::new(),
1534        );
1535        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1536    }
1537
1538    #[gpui::test]
1539    fn test_inlay_buffer_rows(cx: &mut App) {
1540        let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1541        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1542        assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1543        let mut next_inlay_id = 0;
1544
1545        let (inlay_snapshot, _) = inlay_map.splice(
1546            &[],
1547            vec![
1548                Inlay::mock_hint(
1549                    post_inc(&mut next_inlay_id),
1550                    buffer.read(cx).snapshot(cx).anchor_before(0),
1551                    "|123|\n",
1552                ),
1553                Inlay::mock_hint(
1554                    post_inc(&mut next_inlay_id),
1555                    buffer.read(cx).snapshot(cx).anchor_before(4),
1556                    "|456|",
1557                ),
1558                Inlay::inline_completion(
1559                    post_inc(&mut next_inlay_id),
1560                    buffer.read(cx).snapshot(cx).anchor_before(7),
1561                    "\n|567|\n",
1562                ),
1563            ],
1564        );
1565        assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1566        assert_eq!(
1567            inlay_snapshot
1568                .row_infos(0)
1569                .map(|info| info.buffer_row)
1570                .collect::<Vec<_>>(),
1571            vec![Some(0), None, Some(1), None, None, Some(2)]
1572        );
1573    }
1574
1575    #[gpui::test(iterations = 100)]
1576    fn test_random_inlays(cx: &mut App, mut rng: StdRng) {
1577        init_test(cx);
1578
1579        let operations = env::var("OPERATIONS")
1580            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1581            .unwrap_or(10);
1582
1583        let len = rng.gen_range(0..30);
1584        let buffer = if rng.r#gen() {
1585            let text = util::RandomCharIter::new(&mut rng)
1586                .take(len)
1587                .collect::<String>();
1588            MultiBuffer::build_simple(&text, cx)
1589        } else {
1590            MultiBuffer::build_random(&mut rng, cx)
1591        };
1592        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1593        let mut next_inlay_id = 0;
1594        log::info!("buffer text: {:?}", buffer_snapshot.text());
1595        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1596        for _ in 0..operations {
1597            let mut inlay_edits = Patch::default();
1598
1599            let mut prev_inlay_text = inlay_snapshot.text();
1600            let mut buffer_edits = Vec::new();
1601            match rng.gen_range(0..=100) {
1602                0..=50 => {
1603                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1604                    log::info!("mutated text: {:?}", snapshot.text());
1605                    inlay_edits = Patch::new(edits);
1606                }
1607                _ => buffer.update(cx, |buffer, cx| {
1608                    let subscription = buffer.subscribe();
1609                    let edit_count = rng.gen_range(1..=5);
1610                    buffer.randomly_mutate(&mut rng, edit_count, cx);
1611                    buffer_snapshot = buffer.snapshot(cx);
1612                    let edits = subscription.consume().into_inner();
1613                    log::info!("editing {:?}", edits);
1614                    buffer_edits.extend(edits);
1615                }),
1616            };
1617
1618            let (new_inlay_snapshot, new_inlay_edits) =
1619                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1620            inlay_snapshot = new_inlay_snapshot;
1621            inlay_edits = inlay_edits.compose(new_inlay_edits);
1622
1623            log::info!("buffer text: {:?}", buffer_snapshot.text());
1624            log::info!("inlay text: {:?}", inlay_snapshot.text());
1625
1626            let inlays = inlay_map
1627                .inlays
1628                .iter()
1629                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1630                .map(|inlay| {
1631                    let offset = inlay.position.to_offset(&buffer_snapshot);
1632                    (offset, inlay.clone())
1633                })
1634                .collect::<Vec<_>>();
1635            let mut expected_text = Rope::from(buffer_snapshot.text());
1636            for (offset, inlay) in inlays.iter().rev() {
1637                expected_text.replace(*offset..*offset, &inlay.text.to_string());
1638            }
1639            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1640
1641            let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::<Vec<_>>();
1642            assert_eq!(
1643                expected_buffer_rows.len() as u32,
1644                expected_text.max_point().row + 1
1645            );
1646            for row_start in 0..expected_buffer_rows.len() {
1647                assert_eq!(
1648                    inlay_snapshot
1649                        .row_infos(row_start as u32)
1650                        .collect::<Vec<_>>(),
1651                    &expected_buffer_rows[row_start..],
1652                    "incorrect buffer rows starting at {}",
1653                    row_start
1654                );
1655            }
1656
1657            let mut text_highlights = TextHighlights::default();
1658            let text_highlight_count = rng.gen_range(0_usize..10);
1659            let mut text_highlight_ranges = (0..text_highlight_count)
1660                .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
1661                .collect::<Vec<_>>();
1662            text_highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1663            log::info!("highlighting text ranges {text_highlight_ranges:?}");
1664            text_highlights.insert(
1665                HighlightKey::Type(TypeId::of::<()>()),
1666                Arc::new((
1667                    HighlightStyle::default(),
1668                    text_highlight_ranges
1669                        .into_iter()
1670                        .map(|range| {
1671                            buffer_snapshot.anchor_before(range.start)
1672                                ..buffer_snapshot.anchor_after(range.end)
1673                        })
1674                        .collect(),
1675                )),
1676            );
1677
1678            let mut inlay_highlights = InlayHighlights::default();
1679            if !inlays.is_empty() {
1680                let inlay_highlight_count = rng.gen_range(0..inlays.len());
1681                let mut inlay_indices = BTreeSet::default();
1682                while inlay_indices.len() < inlay_highlight_count {
1683                    inlay_indices.insert(rng.gen_range(0..inlays.len()));
1684                }
1685                let new_highlights = TreeMap::from_ordered_entries(
1686                    inlay_indices
1687                        .into_iter()
1688                        .filter_map(|i| {
1689                            let (_, inlay) = &inlays[i];
1690                            let inlay_text_len = inlay.text.len();
1691                            match inlay_text_len {
1692                                0 => None,
1693                                1 => Some(InlayHighlight {
1694                                    inlay: inlay.id,
1695                                    inlay_position: inlay.position,
1696                                    range: 0..1,
1697                                }),
1698                                n => {
1699                                    let inlay_text = inlay.text.to_string();
1700                                    let mut highlight_end = rng.gen_range(1..n);
1701                                    let mut highlight_start = rng.gen_range(0..highlight_end);
1702                                    while !inlay_text.is_char_boundary(highlight_end) {
1703                                        highlight_end += 1;
1704                                    }
1705                                    while !inlay_text.is_char_boundary(highlight_start) {
1706                                        highlight_start -= 1;
1707                                    }
1708                                    Some(InlayHighlight {
1709                                        inlay: inlay.id,
1710                                        inlay_position: inlay.position,
1711                                        range: highlight_start..highlight_end,
1712                                    })
1713                                }
1714                            }
1715                        })
1716                        .map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight))),
1717                );
1718                log::info!("highlighting inlay ranges {new_highlights:?}");
1719                inlay_highlights.insert(TypeId::of::<()>(), new_highlights);
1720            }
1721
1722            for _ in 0..5 {
1723                let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1724                end = expected_text.clip_offset(end, Bias::Right);
1725                let mut start = rng.gen_range(0..=end);
1726                start = expected_text.clip_offset(start, Bias::Right);
1727
1728                let range = InlayOffset(start)..InlayOffset(end);
1729                log::info!("calling inlay_snapshot.chunks({range:?})");
1730                let actual_text = inlay_snapshot
1731                    .chunks(
1732                        range,
1733                        false,
1734                        Highlights {
1735                            text_highlights: Some(&text_highlights),
1736                            inlay_highlights: Some(&inlay_highlights),
1737                            ..Highlights::default()
1738                        },
1739                    )
1740                    .map(|chunk| chunk.chunk.text)
1741                    .collect::<String>();
1742                assert_eq!(
1743                    actual_text,
1744                    expected_text.slice(start..end).to_string(),
1745                    "incorrect text in range {:?}",
1746                    start..end
1747                );
1748
1749                assert_eq!(
1750                    inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1751                    expected_text.slice(start..end).summary()
1752                );
1753            }
1754
1755            for edit in inlay_edits {
1756                prev_inlay_text.replace_range(
1757                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1758                    &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1759                );
1760            }
1761            assert_eq!(prev_inlay_text, inlay_snapshot.text());
1762
1763            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1764            assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1765
1766            let mut buffer_point = Point::default();
1767            let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1768            let mut buffer_chars = buffer_snapshot.chars_at(0);
1769            loop {
1770                // Ensure conversion from buffer coordinates to inlay coordinates
1771                // is consistent.
1772                let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
1773                assert_eq!(
1774                    inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
1775                    inlay_point
1776                );
1777
1778                // No matter which bias we clip an inlay point with, it doesn't move
1779                // because it was constructed from a buffer point.
1780                assert_eq!(
1781                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
1782                    inlay_point,
1783                    "invalid inlay point for buffer point {:?} when clipped left",
1784                    buffer_point
1785                );
1786                assert_eq!(
1787                    inlay_snapshot.clip_point(inlay_point, Bias::Right),
1788                    inlay_point,
1789                    "invalid inlay point for buffer point {:?} when clipped right",
1790                    buffer_point
1791                );
1792
1793                if let Some(ch) = buffer_chars.next() {
1794                    if ch == '\n' {
1795                        buffer_point += Point::new(1, 0);
1796                    } else {
1797                        buffer_point += Point::new(0, ch.len_utf8() as u32);
1798                    }
1799
1800                    // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1801                    let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1802                    assert!(new_inlay_point > inlay_point);
1803                    inlay_point = new_inlay_point;
1804                } else {
1805                    break;
1806                }
1807            }
1808
1809            let mut inlay_point = InlayPoint::default();
1810            let mut inlay_offset = InlayOffset::default();
1811            for ch in expected_text.chars() {
1812                assert_eq!(
1813                    inlay_snapshot.to_offset(inlay_point),
1814                    inlay_offset,
1815                    "invalid to_offset({:?})",
1816                    inlay_point
1817                );
1818                assert_eq!(
1819                    inlay_snapshot.to_point(inlay_offset),
1820                    inlay_point,
1821                    "invalid to_point({:?})",
1822                    inlay_offset
1823                );
1824
1825                let mut bytes = [0; 4];
1826                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1827                    inlay_offset.0 += 1;
1828                    if *byte == b'\n' {
1829                        inlay_point.0 += Point::new(1, 0);
1830                    } else {
1831                        inlay_point.0 += Point::new(0, 1);
1832                    }
1833
1834                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1835                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1836                    assert!(
1837                        clipped_left_point <= clipped_right_point,
1838                        "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1839                        inlay_point,
1840                        clipped_left_point,
1841                        clipped_right_point
1842                    );
1843
1844                    // Ensure the clipped points are at valid text locations.
1845                    assert_eq!(
1846                        clipped_left_point.0,
1847                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
1848                    );
1849                    assert_eq!(
1850                        clipped_right_point.0,
1851                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
1852                    );
1853
1854                    // Ensure the clipped points never overshoot the end of the map.
1855                    assert!(clipped_left_point <= inlay_snapshot.max_point());
1856                    assert!(clipped_right_point <= inlay_snapshot.max_point());
1857
1858                    // Ensure the clipped points are at valid buffer locations.
1859                    assert_eq!(
1860                        inlay_snapshot
1861                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1862                        clipped_left_point,
1863                        "to_buffer_point({:?}) = {:?}",
1864                        clipped_left_point,
1865                        inlay_snapshot.to_buffer_point(clipped_left_point),
1866                    );
1867                    assert_eq!(
1868                        inlay_snapshot
1869                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1870                        clipped_right_point,
1871                        "to_buffer_point({:?}) = {:?}",
1872                        clipped_right_point,
1873                        inlay_snapshot.to_buffer_point(clipped_right_point),
1874                    );
1875                }
1876            }
1877        }
1878    }
1879
1880    fn init_test(cx: &mut App) {
1881        let store = SettingsStore::test(cx);
1882        cx.set_global(store);
1883        theme::init(theme::LoadThemes::JustBase, cx);
1884    }
1885}