inlay_map.rs

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