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