inlay_map.rs

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