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