inlay_map.rs

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