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