inlay_map.rs

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