inlay_map.rs

   1use super::{
   2    suggestion_map::{
   3        SuggestionBufferRows, SuggestionChunks, SuggestionEdit, SuggestionOffset, SuggestionPoint,
   4        SuggestionSnapshot,
   5    },
   6    TextHighlights,
   7};
   8use crate::{inlay_cache::InlayId, Anchor, MultiBufferSnapshot, ToPoint};
   9use collections::{BTreeSet, HashMap};
  10use gpui::fonts::HighlightStyle;
  11use language::{Chunk, Edit, Point, Rope, TextSummary};
  12use parking_lot::Mutex;
  13use std::{
  14    cmp,
  15    ops::{Add, AddAssign, Range, Sub},
  16};
  17use sum_tree::{Bias, Cursor, SumTree};
  18use text::Patch;
  19
  20pub struct InlayMap {
  21    snapshot: Mutex<InlaySnapshot>,
  22    inlays_by_id: HashMap<InlayId, Inlay>,
  23    inlays: Vec<Inlay>,
  24}
  25
  26#[derive(Clone)]
  27pub struct InlaySnapshot {
  28    // TODO kb merge these two together
  29    pub suggestion_snapshot: SuggestionSnapshot,
  30    transforms: SumTree<Transform>,
  31    pub version: usize,
  32}
  33
  34#[derive(Clone, Debug)]
  35enum Transform {
  36    Isomorphic(TextSummary),
  37    Inlay(Inlay),
  38}
  39
  40impl sum_tree::Item for Transform {
  41    type Summary = TransformSummary;
  42
  43    fn summary(&self) -> Self::Summary {
  44        match self {
  45            Transform::Isomorphic(summary) => TransformSummary {
  46                input: summary.clone(),
  47                output: summary.clone(),
  48            },
  49            Transform::Inlay(inlay) => TransformSummary {
  50                input: TextSummary::default(),
  51                output: inlay.text.summary(),
  52            },
  53        }
  54    }
  55}
  56
  57#[derive(Clone, Debug, Default)]
  58struct TransformSummary {
  59    input: TextSummary,
  60    output: TextSummary,
  61}
  62
  63impl sum_tree::Summary for TransformSummary {
  64    type Context = ();
  65
  66    fn add_summary(&mut self, other: &Self, _: &()) {
  67        self.input += &other.input;
  68        self.output += &other.output;
  69    }
  70}
  71
  72pub type InlayEdit = Edit<InlayOffset>;
  73
  74#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
  75pub struct InlayOffset(pub usize);
  76
  77impl Add for InlayOffset {
  78    type Output = Self;
  79
  80    fn add(self, rhs: Self) -> Self::Output {
  81        Self(self.0 + rhs.0)
  82    }
  83}
  84
  85impl Sub for InlayOffset {
  86    type Output = Self;
  87
  88    fn sub(self, rhs: Self) -> Self::Output {
  89        Self(self.0 - rhs.0)
  90    }
  91}
  92
  93impl AddAssign for InlayOffset {
  94    fn add_assign(&mut self, rhs: Self) {
  95        self.0 += rhs.0;
  96    }
  97}
  98
  99impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
 100    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 101        self.0 += &summary.output.len;
 102    }
 103}
 104
 105impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionOffset {
 106    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 107        self.0 += &summary.input.len;
 108    }
 109}
 110
 111#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 112pub struct InlayPoint(pub Point);
 113
 114impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
 115    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 116        self.0 += &summary.output.lines;
 117    }
 118}
 119
 120impl<'a> sum_tree::Dimension<'a, TransformSummary> for SuggestionPoint {
 121    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 122        self.0 += &summary.input.lines;
 123    }
 124}
 125
 126#[derive(Clone)]
 127pub struct InlayBufferRows<'a> {
 128    transforms: Cursor<'a, Transform, (InlayPoint, SuggestionPoint)>,
 129    suggestion_rows: SuggestionBufferRows<'a>,
 130    inlay_row: u32,
 131}
 132
 133pub struct InlayChunks<'a> {
 134    transforms: Cursor<'a, Transform, (InlayOffset, SuggestionOffset)>,
 135    suggestion_chunks: SuggestionChunks<'a>,
 136    suggestion_chunk: Option<Chunk<'a>>,
 137    inlay_chunks: Option<text::Chunks<'a>>,
 138    output_offset: InlayOffset,
 139    max_output_offset: InlayOffset,
 140    highlight_style: Option<HighlightStyle>,
 141}
 142
 143#[derive(Debug, Clone)]
 144pub struct Inlay {
 145    pub(super) id: InlayId,
 146    pub(super) position: Anchor,
 147    pub(super) text: Rope,
 148}
 149
 150#[derive(Debug, Clone)]
 151pub struct InlayProperties<T> {
 152    pub position: Anchor,
 153    pub text: T,
 154}
 155
 156impl<'a> Iterator for InlayChunks<'a> {
 157    type Item = Chunk<'a>;
 158
 159    fn next(&mut self) -> Option<Self::Item> {
 160        if self.output_offset == self.max_output_offset {
 161            return None;
 162        }
 163
 164        let chunk = match self.transforms.item()? {
 165            Transform::Isomorphic(_) => {
 166                let chunk = self
 167                    .suggestion_chunk
 168                    .get_or_insert_with(|| self.suggestion_chunks.next().unwrap());
 169                if chunk.text.is_empty() {
 170                    *chunk = self.suggestion_chunks.next().unwrap();
 171                }
 172
 173                let (prefix, suffix) = chunk.text.split_at(cmp::min(
 174                    self.transforms.end(&()).0 .0 - self.output_offset.0,
 175                    chunk.text.len(),
 176                ));
 177
 178                chunk.text = suffix;
 179                self.output_offset.0 += prefix.len();
 180                Chunk {
 181                    text: prefix,
 182                    ..chunk.clone()
 183                }
 184            }
 185            Transform::Inlay(inlay) => {
 186                let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
 187                    let start = self.output_offset - self.transforms.start().0;
 188                    let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
 189                        - self.transforms.start().0;
 190                    inlay.text.chunks_in_range(start.0..end.0)
 191                });
 192
 193                let chunk = inlay_chunks.next().unwrap();
 194                self.output_offset.0 += chunk.len();
 195                Chunk {
 196                    text: chunk,
 197                    highlight_style: self.highlight_style,
 198                    ..Default::default()
 199                }
 200            }
 201        };
 202
 203        if self.output_offset == self.transforms.end(&()).0 {
 204            self.inlay_chunks = None;
 205            self.transforms.next(&());
 206        }
 207
 208        Some(chunk)
 209    }
 210}
 211
 212impl<'a> Iterator for InlayBufferRows<'a> {
 213    type Item = Option<u32>;
 214
 215    fn next(&mut self) -> Option<Self::Item> {
 216        let mut buffer_row = None;
 217        while let Some(transform) = self.transforms.item() {
 218            match transform {
 219                Transform::Inlay(inlay) => {
 220                    if self.inlay_row == self.transforms.end(&()).0.row() {
 221                        self.transforms.next(&());
 222                    } else {
 223                        buffer_row = Some(None);
 224                        break;
 225                    }
 226                }
 227                Transform::Isomorphic(_) => {
 228                    buffer_row = Some(self.suggestion_rows.next().unwrap());
 229                    break;
 230                }
 231            }
 232        }
 233        self.inlay_row += 1;
 234        self.transforms
 235            .seek(&InlayPoint::new(self.inlay_row, 0), Bias::Right, &());
 236
 237        buffer_row
 238    }
 239}
 240
 241impl InlayPoint {
 242    pub fn new(row: u32, column: u32) -> Self {
 243        Self(Point::new(row, column))
 244    }
 245
 246    pub fn row(self) -> u32 {
 247        self.0.row
 248    }
 249
 250    pub fn column(self) -> u32 {
 251        self.0.column
 252    }
 253}
 254
 255impl InlayMap {
 256    pub fn new(suggestion_snapshot: SuggestionSnapshot) -> (Self, InlaySnapshot) {
 257        let snapshot = InlaySnapshot {
 258            suggestion_snapshot: suggestion_snapshot.clone(),
 259            version: 0,
 260            transforms: SumTree::from_item(
 261                Transform::Isomorphic(suggestion_snapshot.text_summary()),
 262                &(),
 263            ),
 264        };
 265
 266        (
 267            Self {
 268                snapshot: Mutex::new(snapshot.clone()),
 269                inlays_by_id: Default::default(),
 270                inlays: Default::default(),
 271            },
 272            snapshot,
 273        )
 274    }
 275
 276    pub fn sync(
 277        &mut self,
 278        suggestion_snapshot: SuggestionSnapshot,
 279        mut suggestion_edits: Vec<SuggestionEdit>,
 280    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 281        let mut snapshot = self.snapshot.lock();
 282
 283        let mut new_snapshot = snapshot.clone();
 284        if new_snapshot.suggestion_snapshot.version != suggestion_snapshot.version {
 285            new_snapshot.version += 1;
 286        }
 287
 288        if suggestion_snapshot
 289            .buffer_snapshot()
 290            .trailing_excerpt_update_count()
 291            != snapshot
 292                .suggestion_snapshot
 293                .buffer_snapshot()
 294                .trailing_excerpt_update_count()
 295        {
 296            if suggestion_edits.is_empty() {
 297                suggestion_edits.push(Edit {
 298                    old: snapshot.suggestion_snapshot.len()..snapshot.suggestion_snapshot.len(),
 299                    new: suggestion_snapshot.len()..suggestion_snapshot.len(),
 300                });
 301            }
 302        }
 303
 304        let mut inlay_edits = Patch::default();
 305        let mut new_transforms = SumTree::new();
 306        let mut cursor = snapshot
 307            .transforms
 308            .cursor::<(SuggestionOffset, InlayOffset)>();
 309        let mut suggestion_edits_iter = suggestion_edits.iter().peekable();
 310        while let Some(suggestion_edit) = suggestion_edits_iter.next() {
 311            new_transforms.push_tree(
 312                cursor.slice(&suggestion_edit.old.start, Bias::Left, &()),
 313                &(),
 314            );
 315            if let Some(Transform::Isomorphic(transform)) = cursor.item() {
 316                if cursor.end(&()).0 == suggestion_edit.old.start {
 317                    new_transforms.push(Transform::Isomorphic(transform.clone()), &());
 318                    cursor.next(&());
 319                }
 320            }
 321
 322            // Remove all the inlays and transforms contained by the edit.
 323            let old_start =
 324                cursor.start().1 + InlayOffset(suggestion_edit.old.start.0 - cursor.start().0 .0);
 325            cursor.seek(&suggestion_edit.old.end, Bias::Right, &());
 326            let old_end =
 327                cursor.start().1 + InlayOffset(suggestion_edit.old.end.0 - cursor.start().0 .0);
 328
 329            // Push the unchanged prefix.
 330            let prefix_start = SuggestionOffset(new_transforms.summary().input.len);
 331            let prefix_end = suggestion_edit.new.start;
 332            push_isomorphic(
 333                &mut new_transforms,
 334                suggestion_snapshot.text_summary_for_range(
 335                    suggestion_snapshot.to_point(prefix_start)
 336                        ..suggestion_snapshot.to_point(prefix_end),
 337                ),
 338            );
 339            let new_start = InlayOffset(new_transforms.summary().output.len);
 340
 341            let start_point = suggestion_snapshot
 342                .to_fold_point(suggestion_snapshot.to_point(suggestion_edit.new.start))
 343                .to_buffer_point(&suggestion_snapshot.fold_snapshot);
 344            let start_ix = match self.inlays.binary_search_by(|probe| {
 345                probe
 346                    .position
 347                    .to_point(&suggestion_snapshot.buffer_snapshot())
 348                    .cmp(&start_point)
 349                    .then(std::cmp::Ordering::Greater)
 350            }) {
 351                Ok(ix) | Err(ix) => ix,
 352            };
 353
 354            for inlay in &self.inlays[start_ix..] {
 355                let buffer_point = inlay
 356                    .position
 357                    .to_point(suggestion_snapshot.buffer_snapshot());
 358                let fold_point = suggestion_snapshot
 359                    .fold_snapshot
 360                    .to_fold_point(buffer_point, Bias::Left);
 361                let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point);
 362                let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point);
 363                if suggestion_offset > suggestion_edit.new.end {
 364                    break;
 365                }
 366
 367                let prefix_start = SuggestionOffset(new_transforms.summary().input.len);
 368                let prefix_end = suggestion_offset;
 369                push_isomorphic(
 370                    &mut new_transforms,
 371                    suggestion_snapshot.text_summary_for_range(
 372                        suggestion_snapshot.to_point(prefix_start)
 373                            ..suggestion_snapshot.to_point(prefix_end),
 374                    ),
 375                );
 376
 377                if inlay
 378                    .position
 379                    .is_valid(suggestion_snapshot.buffer_snapshot())
 380                {
 381                    new_transforms.push(Transform::Inlay(inlay.clone()), &());
 382                }
 383            }
 384
 385            // Apply the rest of the edit.
 386            let transform_start = SuggestionOffset(new_transforms.summary().input.len);
 387            push_isomorphic(
 388                &mut new_transforms,
 389                suggestion_snapshot.text_summary_for_range(
 390                    suggestion_snapshot.to_point(transform_start)
 391                        ..suggestion_snapshot.to_point(suggestion_edit.new.end),
 392                ),
 393            );
 394            let new_end = InlayOffset(new_transforms.summary().output.len);
 395            inlay_edits.push(Edit {
 396                old: old_start..old_end,
 397                new: new_start..new_end,
 398            });
 399
 400            // If the next edit doesn't intersect the current isomorphic transform, then
 401            // we can push its remainder.
 402            if suggestion_edits_iter
 403                .peek()
 404                .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
 405            {
 406                let transform_start = SuggestionOffset(new_transforms.summary().input.len);
 407                let transform_end =
 408                    suggestion_edit.new.end + (cursor.end(&()).0 - suggestion_edit.old.end);
 409                push_isomorphic(
 410                    &mut new_transforms,
 411                    suggestion_snapshot.text_summary_for_range(
 412                        suggestion_snapshot.to_point(transform_start)
 413                            ..suggestion_snapshot.to_point(transform_end),
 414                    ),
 415                );
 416                cursor.next(&());
 417            }
 418        }
 419
 420        new_transforms.push_tree(cursor.suffix(&()), &());
 421        new_snapshot.transforms = new_transforms;
 422        new_snapshot.suggestion_snapshot = suggestion_snapshot;
 423        new_snapshot.check_invariants();
 424        drop(cursor);
 425
 426        *snapshot = new_snapshot.clone();
 427        (new_snapshot, inlay_edits.into_inner())
 428    }
 429
 430    pub fn splice<T: Into<Rope>>(
 431        &mut self,
 432        to_remove: Vec<InlayId>,
 433        to_insert: Vec<(InlayId, InlayProperties<T>)>,
 434    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 435        let mut snapshot = self.snapshot.lock();
 436        snapshot.version += 1;
 437
 438        let mut edits = BTreeSet::new();
 439        for (id, properties) in to_insert {
 440            let inlay = Inlay {
 441                id,
 442                position: properties.position,
 443                text: properties.text.into(),
 444            };
 445            self.inlays_by_id.insert(inlay.id, inlay.clone());
 446            match self.inlays.binary_search_by(|probe| {
 447                probe
 448                    .position
 449                    .cmp(&inlay.position, snapshot.buffer_snapshot())
 450            }) {
 451                Ok(ix) | Err(ix) => {
 452                    self.inlays.insert(ix, inlay.clone());
 453                }
 454            }
 455
 456            let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot());
 457            let fold_point = snapshot
 458                .suggestion_snapshot
 459                .fold_snapshot
 460                .to_fold_point(buffer_point, Bias::Left);
 461            let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
 462            let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point);
 463            edits.insert(suggestion_offset);
 464        }
 465
 466        self.inlays.retain(|inlay| !to_remove.contains(&inlay.id));
 467        for inlay_id in to_remove {
 468            if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) {
 469                let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot());
 470                let fold_point = snapshot
 471                    .suggestion_snapshot
 472                    .fold_snapshot
 473                    .to_fold_point(buffer_point, Bias::Left);
 474                let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
 475                let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point);
 476                edits.insert(suggestion_offset);
 477            }
 478        }
 479
 480        let suggestion_snapshot = snapshot.suggestion_snapshot.clone();
 481        let suggestion_edits = edits
 482            .into_iter()
 483            .map(|offset| Edit {
 484                old: offset..offset,
 485                new: offset..offset,
 486            })
 487            .collect();
 488        drop(snapshot);
 489        self.sync(suggestion_snapshot, suggestion_edits)
 490    }
 491
 492    #[cfg(any(test, feature = "test-support"))]
 493    pub fn randomly_mutate(
 494        &mut self,
 495        next_inlay_id: &mut usize,
 496        rng: &mut rand::rngs::StdRng,
 497    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 498        use rand::prelude::*;
 499        use util::post_inc;
 500
 501        let mut to_remove = Vec::new();
 502        let mut to_insert = Vec::new();
 503        let snapshot = self.snapshot.lock();
 504        for _ in 0..rng.gen_range(1..=5) {
 505            if self.inlays.is_empty() || rng.gen() {
 506                let buffer_snapshot = snapshot.buffer_snapshot();
 507                let position = buffer_snapshot.random_byte_range(0, rng).start;
 508                let bias = if rng.gen() { Bias::Left } else { Bias::Right };
 509                let len = rng.gen_range(1..=5);
 510                let text = util::RandomCharIter::new(&mut *rng)
 511                    .take(len)
 512                    .collect::<String>();
 513                log::info!(
 514                    "creating inlay at buffer offset {} with bias {:?} and text {:?}",
 515                    position,
 516                    bias,
 517                    text
 518                );
 519                to_insert.push((
 520                    InlayId(post_inc(next_inlay_id)),
 521                    InlayProperties {
 522                        position: buffer_snapshot.anchor_at(position, bias),
 523                        text,
 524                    },
 525                ));
 526            } else {
 527                to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap());
 528            }
 529        }
 530        log::info!("removing inlays: {:?}", to_remove);
 531
 532        drop(snapshot);
 533        self.splice(to_remove, to_insert)
 534    }
 535}
 536
 537impl InlaySnapshot {
 538    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
 539        self.suggestion_snapshot.buffer_snapshot()
 540    }
 541
 542    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
 543        let mut cursor = self
 544            .transforms
 545            .cursor::<(InlayOffset, (InlayPoint, SuggestionOffset))>();
 546        cursor.seek(&offset, Bias::Right, &());
 547        let overshoot = offset.0 - cursor.start().0 .0;
 548        match cursor.item() {
 549            Some(Transform::Isomorphic(_)) => {
 550                let suggestion_offset_start = cursor.start().1 .1;
 551                let suggestion_offset_end = SuggestionOffset(suggestion_offset_start.0 + overshoot);
 552                let suggestion_start = self.suggestion_snapshot.to_point(suggestion_offset_start);
 553                let suggestion_end = self.suggestion_snapshot.to_point(suggestion_offset_end);
 554                InlayPoint(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0))
 555            }
 556            Some(Transform::Inlay(inlay)) => {
 557                let overshoot = inlay.text.offset_to_point(overshoot);
 558                InlayPoint(cursor.start().1 .0 .0 + overshoot)
 559            }
 560            None => self.max_point(),
 561        }
 562    }
 563
 564    pub fn len(&self) -> InlayOffset {
 565        InlayOffset(self.transforms.summary().output.len)
 566    }
 567
 568    pub fn max_point(&self) -> InlayPoint {
 569        InlayPoint(self.transforms.summary().output.lines)
 570    }
 571
 572    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
 573        let mut cursor = self
 574            .transforms
 575            .cursor::<(InlayPoint, (InlayOffset, SuggestionPoint))>();
 576        cursor.seek(&point, Bias::Right, &());
 577        let overshoot = point.0 - cursor.start().0 .0;
 578        match cursor.item() {
 579            Some(Transform::Isomorphic(_)) => {
 580                let suggestion_point_start = cursor.start().1 .1;
 581                let suggestion_point_end = SuggestionPoint(suggestion_point_start.0 + overshoot);
 582                let suggestion_start = self.suggestion_snapshot.to_offset(suggestion_point_start);
 583                let suggestion_end = self.suggestion_snapshot.to_offset(suggestion_point_end);
 584                InlayOffset(cursor.start().1 .0 .0 + (suggestion_end.0 - suggestion_start.0))
 585            }
 586            Some(Transform::Inlay(inlay)) => {
 587                let overshoot = inlay.text.point_to_offset(overshoot);
 588                InlayOffset(cursor.start().1 .0 .0 + overshoot)
 589            }
 590            None => self.len(),
 591        }
 592    }
 593
 594    pub fn chars_at(&self, start: InlayPoint) -> impl '_ + Iterator<Item = char> {
 595        self.chunks(self.to_offset(start)..self.len(), false, None, None)
 596            .flat_map(|chunk| chunk.text.chars())
 597    }
 598
 599    pub fn to_suggestion_point(&self, point: InlayPoint) -> SuggestionPoint {
 600        let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
 601        cursor.seek(&point, Bias::Right, &());
 602        match cursor.item() {
 603            Some(Transform::Isomorphic(_)) => {
 604                let overshoot = point.0 - cursor.start().0 .0;
 605                SuggestionPoint(cursor.start().1 .0 + overshoot)
 606            }
 607            Some(Transform::Inlay(_)) => cursor.start().1,
 608            None => self.suggestion_snapshot.max_point(),
 609        }
 610    }
 611
 612    pub fn to_suggestion_offset(&self, offset: InlayOffset) -> SuggestionOffset {
 613        let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>();
 614        cursor.seek(&offset, Bias::Right, &());
 615        match cursor.item() {
 616            Some(Transform::Isomorphic(_)) => {
 617                let overshoot = offset - cursor.start().0;
 618                cursor.start().1 + SuggestionOffset(overshoot.0)
 619            }
 620            Some(Transform::Inlay(_)) => cursor.start().1,
 621            None => self.suggestion_snapshot.len(),
 622        }
 623    }
 624
 625    pub fn to_inlay_point(&self, point: SuggestionPoint) -> InlayPoint {
 626        let mut cursor = self.transforms.cursor::<(SuggestionPoint, InlayPoint)>();
 627        cursor.seek(&point, Bias::Left, &());
 628        match cursor.item() {
 629            Some(Transform::Isomorphic(_)) => {
 630                let overshoot = point.0 - cursor.start().0 .0;
 631                InlayPoint(cursor.start().1 .0 + overshoot)
 632            }
 633            Some(Transform::Inlay(_)) => cursor.start().1,
 634            None => self.max_point(),
 635        }
 636    }
 637
 638    pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint {
 639        let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
 640        cursor.seek(&point, Bias::Left, &());
 641
 642        let mut bias = bias;
 643        let mut skipped_inlay = false;
 644        loop {
 645            match cursor.item() {
 646                Some(Transform::Isomorphic(transform)) => {
 647                    let overshoot = if skipped_inlay {
 648                        match bias {
 649                            Bias::Left => transform.lines,
 650                            Bias::Right => {
 651                                if transform.first_line_chars == 0 {
 652                                    Point::new(1, 0)
 653                                } else {
 654                                    Point::new(0, 1)
 655                                }
 656                            }
 657                        }
 658                    } else {
 659                        point.0 - cursor.start().0 .0
 660                    };
 661                    let suggestion_point = SuggestionPoint(cursor.start().1 .0 + overshoot);
 662                    let clipped_suggestion_point =
 663                        self.suggestion_snapshot.clip_point(suggestion_point, bias);
 664                    let clipped_overshoot = clipped_suggestion_point.0 - cursor.start().1 .0;
 665                    return InlayPoint(cursor.start().0 .0 + clipped_overshoot);
 666                }
 667                Some(Transform::Inlay(_)) => skipped_inlay = true,
 668                None => match bias {
 669                    Bias::Left => return Default::default(),
 670                    Bias::Right => bias = Bias::Left,
 671                },
 672            }
 673
 674            if bias == Bias::Left {
 675                cursor.prev(&());
 676            } else {
 677                cursor.next(&());
 678            }
 679        }
 680    }
 681
 682    pub fn text_summary_for_range(&self, range: Range<InlayPoint>) -> TextSummary {
 683        let mut summary = TextSummary::default();
 684
 685        let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
 686        cursor.seek(&range.start, Bias::Right, &());
 687
 688        let overshoot = range.start.0 - cursor.start().0 .0;
 689        match cursor.item() {
 690            Some(Transform::Isomorphic(_)) => {
 691                let suggestion_start = cursor.start().1 .0;
 692                let suffix_start = SuggestionPoint(suggestion_start + overshoot);
 693                let suffix_end = SuggestionPoint(
 694                    suggestion_start
 695                        + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0),
 696                );
 697                summary = self
 698                    .suggestion_snapshot
 699                    .text_summary_for_range(suffix_start..suffix_end);
 700                cursor.next(&());
 701            }
 702            Some(Transform::Inlay(inlay)) => {
 703                let suffix_start = inlay.text.point_to_offset(overshoot);
 704                let suffix_end = inlay.text.point_to_offset(
 705                    cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0,
 706                );
 707                summary = inlay.text.cursor(suffix_start).summary(suffix_end);
 708                cursor.next(&());
 709            }
 710            None => {}
 711        }
 712
 713        if range.end > cursor.start().0 {
 714            summary += cursor
 715                .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
 716                .output;
 717
 718            let overshoot = range.end.0 - cursor.start().0 .0;
 719            match cursor.item() {
 720                Some(Transform::Isomorphic(_)) => {
 721                    let prefix_start = cursor.start().1;
 722                    let prefix_end = SuggestionPoint(prefix_start.0 + overshoot);
 723                    summary += self
 724                        .suggestion_snapshot
 725                        .text_summary_for_range(prefix_start..prefix_end);
 726                }
 727                Some(Transform::Inlay(inlay)) => {
 728                    let prefix_end = inlay.text.point_to_offset(overshoot);
 729                    summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
 730                }
 731                None => {}
 732            }
 733        }
 734
 735        summary
 736    }
 737
 738    pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
 739        let mut cursor = self.transforms.cursor::<(InlayPoint, SuggestionPoint)>();
 740        let inlay_point = InlayPoint::new(row, 0);
 741        cursor.seek(&inlay_point, Bias::Right, &());
 742        let mut suggestion_point = cursor.start().1;
 743        if let Some(Transform::Isomorphic(_)) = cursor.item() {
 744            suggestion_point.0 += inlay_point.0 - cursor.start().0 .0;
 745        }
 746
 747        InlayBufferRows {
 748            transforms: cursor,
 749            inlay_row: inlay_point.row(),
 750            suggestion_row,
 751            suggestion_rows: self.suggestion_snapshot.buffer_rows(suggestion_point.row()),
 752        }
 753    }
 754
 755    pub fn line_len(&self, row: u32) -> u32 {
 756        let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
 757        let line_end = if row >= self.max_point().row() {
 758            self.len().0
 759        } else {
 760            self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
 761        };
 762        (line_end - line_start) as u32
 763    }
 764
 765    pub fn chunks<'a>(
 766        &'a self,
 767        range: Range<InlayOffset>,
 768        language_aware: bool,
 769        text_highlights: Option<&'a TextHighlights>,
 770        suggestion_highlight: Option<HighlightStyle>,
 771    ) -> InlayChunks<'a> {
 772        let mut cursor = self.transforms.cursor::<(InlayOffset, SuggestionOffset)>();
 773        cursor.seek(&range.start, Bias::Right, &());
 774
 775        let suggestion_range =
 776            self.to_suggestion_offset(range.start)..self.to_suggestion_offset(range.end);
 777        let suggestion_chunks = self.suggestion_snapshot.chunks(
 778            suggestion_range,
 779            language_aware,
 780            text_highlights,
 781            suggestion_highlight,
 782        );
 783
 784        InlayChunks {
 785            transforms: cursor,
 786            suggestion_chunks,
 787            inlay_chunks: None,
 788            suggestion_chunk: None,
 789            output_offset: range.start,
 790            max_output_offset: range.end,
 791            highlight_style: suggestion_highlight,
 792        }
 793    }
 794
 795    #[cfg(test)]
 796    pub fn text(&self) -> String {
 797        self.chunks(Default::default()..self.len(), false, None, None)
 798            .map(|chunk| chunk.text)
 799            .collect()
 800    }
 801
 802    fn check_invariants(&self) {
 803        #[cfg(any(debug_assertions, feature = "test-support"))]
 804        {
 805            assert_eq!(
 806                self.transforms.summary().input,
 807                self.suggestion_snapshot.text_summary()
 808            );
 809        }
 810    }
 811}
 812
 813fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
 814    if summary.len == 0 {
 815        return;
 816    }
 817
 818    let mut summary = Some(summary);
 819    sum_tree.update_last(
 820        |transform| {
 821            if let Transform::Isomorphic(transform) = transform {
 822                *transform += summary.take().unwrap();
 823            }
 824        },
 825        &(),
 826    );
 827
 828    if let Some(summary) = summary {
 829        sum_tree.push(Transform::Isomorphic(summary), &());
 830    }
 831}
 832
 833#[cfg(test)]
 834mod tests {
 835    use super::*;
 836    use crate::{
 837        display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap},
 838        MultiBuffer,
 839    };
 840    use gpui::AppContext;
 841    use rand::prelude::*;
 842    use settings::SettingsStore;
 843    use std::env;
 844    use text::Patch;
 845    use util::post_inc;
 846
 847    #[gpui::test]
 848    fn test_basic_inlays(cx: &mut AppContext) {
 849        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
 850        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
 851        let (fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
 852        let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
 853        let (mut inlay_map, inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
 854        assert_eq!(inlay_snapshot.text(), "abcdefghi");
 855        let mut next_inlay_id = 0;
 856
 857        let (inlay_snapshot, _) = inlay_map.splice(
 858            Vec::new(),
 859            vec![(
 860                InlayId(post_inc(&mut next_inlay_id)),
 861                InlayProperties {
 862                    position: buffer.read(cx).snapshot(cx).anchor_after(3),
 863                    text: "|123|",
 864                },
 865            )],
 866        );
 867        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
 868        assert_eq!(
 869            inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 0)),
 870            InlayPoint::new(0, 0)
 871        );
 872        assert_eq!(
 873            inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 1)),
 874            InlayPoint::new(0, 1)
 875        );
 876        assert_eq!(
 877            inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 2)),
 878            InlayPoint::new(0, 2)
 879        );
 880        assert_eq!(
 881            inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 3)),
 882            InlayPoint::new(0, 3)
 883        );
 884        assert_eq!(
 885            inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 4)),
 886            InlayPoint::new(0, 9)
 887        );
 888        assert_eq!(
 889            inlay_snapshot.to_inlay_point(SuggestionPoint::new(0, 5)),
 890            InlayPoint::new(0, 10)
 891        );
 892        assert_eq!(
 893            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
 894            InlayPoint::new(0, 0)
 895        );
 896        assert_eq!(
 897            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
 898            InlayPoint::new(0, 0)
 899        );
 900        assert_eq!(
 901            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
 902            InlayPoint::new(0, 3)
 903        );
 904        assert_eq!(
 905            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
 906            InlayPoint::new(0, 3)
 907        );
 908        assert_eq!(
 909            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
 910            InlayPoint::new(0, 3)
 911        );
 912        assert_eq!(
 913            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
 914            InlayPoint::new(0, 9)
 915        );
 916
 917        // Edits before or after the inlay should not affect it.
 918        buffer.update(cx, |buffer, cx| {
 919            buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
 920        });
 921        let (fold_snapshot, fold_edits) = fold_map.read(
 922            buffer.read(cx).snapshot(cx),
 923            buffer_edits.consume().into_inner(),
 924        );
 925        let (suggestion_snapshot, suggestion_edits) =
 926            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
 927        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
 928        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
 929
 930        // An edit surrounding the inlay should invalidate it.
 931        buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
 932        let (fold_snapshot, fold_edits) = fold_map.read(
 933            buffer.read(cx).snapshot(cx),
 934            buffer_edits.consume().into_inner(),
 935        );
 936        let (suggestion_snapshot, suggestion_edits) =
 937            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
 938        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
 939        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
 940
 941        let (inlay_snapshot, _) = inlay_map.splice(
 942            Vec::new(),
 943            vec![
 944                (
 945                    InlayId(post_inc(&mut next_inlay_id)),
 946                    InlayProperties {
 947                        position: buffer.read(cx).snapshot(cx).anchor_before(3),
 948                        text: "|123|",
 949                    },
 950                ),
 951                (
 952                    InlayId(post_inc(&mut next_inlay_id)),
 953                    InlayProperties {
 954                        position: buffer.read(cx).snapshot(cx).anchor_after(3),
 955                        text: "|456|",
 956                    },
 957                ),
 958            ],
 959        );
 960        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
 961
 962        // Edits ending where the inlay starts should not move it if it has a left bias.
 963        buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
 964        let (fold_snapshot, fold_edits) = fold_map.read(
 965            buffer.read(cx).snapshot(cx),
 966            buffer_edits.consume().into_inner(),
 967        );
 968        let (suggestion_snapshot, suggestion_edits) =
 969            suggestion_map.sync(fold_snapshot.clone(), fold_edits);
 970        let (inlay_snapshot, _) = inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits);
 971        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
 972
 973        // The inlays can be manually removed.
 974        let (inlay_snapshot, _) = inlay_map
 975            .splice::<String>(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new());
 976        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
 977    }
 978
 979    #[gpui::test(iterations = 100)]
 980    fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
 981        init_test(cx);
 982
 983        let operations = env::var("OPERATIONS")
 984            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 985            .unwrap_or(10);
 986
 987        let len = rng.gen_range(0..30);
 988        let buffer = if rng.gen() {
 989            let text = util::RandomCharIter::new(&mut rng)
 990                .take(len)
 991                .collect::<String>();
 992            MultiBuffer::build_simple(&text, cx)
 993        } else {
 994            MultiBuffer::build_random(&mut rng, cx)
 995        };
 996        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
 997        log::info!("buffer text: {:?}", buffer_snapshot.text());
 998
 999        let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
1000        let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
1001        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(suggestion_snapshot.clone());
1002        let mut next_inlay_id = 0;
1003
1004        for _ in 0..operations {
1005            let mut suggestion_edits = Patch::default();
1006            let mut inlay_edits = Patch::default();
1007
1008            let mut prev_inlay_text = inlay_snapshot.text();
1009            let mut buffer_edits = Vec::new();
1010            match rng.gen_range(0..=100) {
1011                0..=29 => {
1012                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1013                    log::info!("mutated text: {:?}", snapshot.text());
1014                    inlay_edits = Patch::new(edits);
1015                }
1016                30..=59 => {
1017                    for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
1018                        fold_snapshot = new_fold_snapshot;
1019                        let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits);
1020                        suggestion_edits = suggestion_edits.compose(edits);
1021                    }
1022                }
1023                _ => buffer.update(cx, |buffer, cx| {
1024                    let subscription = buffer.subscribe();
1025                    let edit_count = rng.gen_range(1..=5);
1026                    buffer.randomly_mutate(&mut rng, edit_count, cx);
1027                    buffer_snapshot = buffer.snapshot(cx);
1028                    let edits = subscription.consume().into_inner();
1029                    log::info!("editing {:?}", edits);
1030                    buffer_edits.extend(edits);
1031                }),
1032            };
1033
1034            let (new_fold_snapshot, fold_edits) =
1035                fold_map.read(buffer_snapshot.clone(), buffer_edits);
1036            fold_snapshot = new_fold_snapshot;
1037            let (new_suggestion_snapshot, new_suggestion_edits) =
1038                suggestion_map.sync(fold_snapshot.clone(), fold_edits);
1039            suggestion_snapshot = new_suggestion_snapshot;
1040            suggestion_edits = suggestion_edits.compose(new_suggestion_edits);
1041            let (new_inlay_snapshot, new_inlay_edits) =
1042                inlay_map.sync(suggestion_snapshot.clone(), suggestion_edits.into_inner());
1043            inlay_snapshot = new_inlay_snapshot;
1044            inlay_edits = inlay_edits.compose(new_inlay_edits);
1045
1046            log::info!("buffer text: {:?}", buffer_snapshot.text());
1047            log::info!("folds text: {:?}", fold_snapshot.text());
1048            log::info!("suggestions text: {:?}", suggestion_snapshot.text());
1049            log::info!("inlay text: {:?}", inlay_snapshot.text());
1050
1051            let inlays = inlay_map
1052                .inlays
1053                .iter()
1054                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1055                .map(|inlay| {
1056                    let buffer_point = inlay.position.to_point(&buffer_snapshot);
1057                    let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left);
1058                    let suggestion_point = suggestion_snapshot.to_suggestion_point(fold_point);
1059                    let suggestion_offset = suggestion_snapshot.to_offset(suggestion_point);
1060                    (suggestion_offset, inlay.clone())
1061                })
1062                .collect::<Vec<_>>();
1063            let mut expected_text = Rope::from(suggestion_snapshot.text().as_str());
1064            for (offset, inlay) in inlays.into_iter().rev() {
1065                expected_text.replace(offset.0..offset.0, &inlay.text.to_string());
1066            }
1067            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1068
1069            let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1070            dbg!(&expected_buffer_rows);
1071            for row_start in 0..expected_buffer_rows.len() {
1072                assert_eq!(
1073                    inlay_snapshot
1074                        .buffer_rows(row_start as u32)
1075                        .collect::<Vec<_>>(),
1076                    &expected_buffer_rows[row_start..],
1077                    "incorrect buffer rows starting at {}",
1078                    row_start
1079                );
1080            }
1081
1082            for _ in 0..5 {
1083                let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1084                end = expected_text.clip_offset(end, Bias::Right);
1085                let mut start = rng.gen_range(0..=end);
1086                start = expected_text.clip_offset(start, Bias::Right);
1087
1088                let actual_text = inlay_snapshot
1089                    .chunks(InlayOffset(start)..InlayOffset(end), false, None, None)
1090                    .map(|chunk| chunk.text)
1091                    .collect::<String>();
1092                assert_eq!(
1093                    actual_text,
1094                    expected_text.slice(start..end).to_string(),
1095                    "incorrect text in range {:?}",
1096                    start..end
1097                );
1098
1099                let start_point = InlayPoint(expected_text.offset_to_point(start));
1100                let end_point = InlayPoint(expected_text.offset_to_point(end));
1101                assert_eq!(
1102                    inlay_snapshot.text_summary_for_range(start_point..end_point),
1103                    expected_text.slice(start..end).summary()
1104                );
1105            }
1106
1107            for edit in inlay_edits {
1108                prev_inlay_text.replace_range(
1109                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1110                    &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1111                );
1112            }
1113            assert_eq!(prev_inlay_text, inlay_snapshot.text());
1114
1115            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1116            assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1117
1118            let mut inlay_point = InlayPoint::default();
1119            let mut inlay_offset = InlayOffset::default();
1120            for ch in expected_text.chars() {
1121                assert_eq!(
1122                    inlay_snapshot.to_offset(inlay_point),
1123                    inlay_offset,
1124                    "invalid to_offset({:?})",
1125                    inlay_point
1126                );
1127                assert_eq!(
1128                    inlay_snapshot.to_point(inlay_offset),
1129                    inlay_point,
1130                    "invalid to_point({:?})",
1131                    inlay_offset
1132                );
1133                assert_eq!(
1134                    inlay_snapshot.to_inlay_point(inlay_snapshot.to_suggestion_point(inlay_point)),
1135                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
1136                    "to_suggestion_point({:?}) = {:?}",
1137                    inlay_point,
1138                    inlay_snapshot.to_suggestion_point(inlay_point),
1139                );
1140
1141                let mut bytes = [0; 4];
1142                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1143                    inlay_offset.0 += 1;
1144                    if *byte == b'\n' {
1145                        inlay_point.0 += Point::new(1, 0);
1146                    } else {
1147                        inlay_point.0 += Point::new(0, 1);
1148                    }
1149
1150                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1151                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1152                    assert!(
1153                        clipped_left_point <= clipped_right_point,
1154                        "clipped left point {:?} is greater than clipped right point {:?}",
1155                        clipped_left_point,
1156                        clipped_right_point
1157                    );
1158
1159                    // Ensure the clipped points are at valid text locations.
1160                    assert_eq!(
1161                        clipped_left_point.0,
1162                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
1163                    );
1164                    assert_eq!(
1165                        clipped_right_point.0,
1166                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
1167                    );
1168
1169                    // Ensure the clipped points never overshoot the end of the map.
1170                    assert!(clipped_left_point <= inlay_snapshot.max_point());
1171                    assert!(clipped_right_point <= inlay_snapshot.max_point());
1172                }
1173            }
1174        }
1175    }
1176
1177    fn init_test(cx: &mut AppContext) {
1178        cx.set_global(SettingsStore::test(cx));
1179        theme::init((), cx);
1180    }
1181}