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