inlay_map.rs

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