inlay_map.rs

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