inlay_map.rs

   1use crate::{
   2    multi_buffer::{MultiBufferChunks, MultiBufferRows},
   3    Anchor, InlayId, MultiBufferSnapshot, ToOffset,
   4};
   5use collections::{BTreeMap, BTreeSet};
   6use gpui::fonts::HighlightStyle;
   7use language::{Chunk, Edit, Point, TextSummary};
   8use std::{
   9    any::TypeId,
  10    cmp,
  11    iter::Peekable,
  12    ops::{Add, AddAssign, Range, Sub, SubAssign},
  13    vec,
  14};
  15use sum_tree::{Bias, Cursor, SumTree};
  16use text::{Patch, Rope};
  17
  18use super::TextHighlights;
  19
  20pub struct InlayMap {
  21    snapshot: InlaySnapshot,
  22    inlays: Vec<Inlay>,
  23}
  24
  25#[derive(Clone)]
  26pub struct InlaySnapshot {
  27    pub buffer: MultiBufferSnapshot,
  28    transforms: SumTree<Transform>,
  29    pub version: usize,
  30}
  31
  32#[derive(Clone, Debug)]
  33enum Transform {
  34    Isomorphic(TextSummary),
  35    Inlay(Inlay),
  36}
  37
  38#[derive(Debug, Clone)]
  39pub struct Inlay {
  40    pub id: InlayId,
  41    pub position: Anchor,
  42    pub text: text::Rope,
  43}
  44
  45impl Inlay {
  46    pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self {
  47        let mut text = hint.text();
  48        if hint.padding_right && !text.ends_with(' ') {
  49            text.push(' ');
  50        }
  51        if hint.padding_left && !text.starts_with(' ') {
  52            text.insert(0, ' ');
  53        }
  54        Self {
  55            id: InlayId::Hint(id),
  56            position,
  57            text: text.into(),
  58        }
  59    }
  60
  61    pub fn suggestion<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
  62        Self {
  63            id: InlayId::Suggestion(id),
  64            position,
  65            text: text.into(),
  66        }
  67    }
  68}
  69
  70impl sum_tree::Item for Transform {
  71    type Summary = TransformSummary;
  72
  73    fn summary(&self) -> Self::Summary {
  74        match self {
  75            Transform::Isomorphic(summary) => TransformSummary {
  76                input: summary.clone(),
  77                output: summary.clone(),
  78            },
  79            Transform::Inlay(inlay) => TransformSummary {
  80                input: TextSummary::default(),
  81                output: inlay.text.summary(),
  82            },
  83        }
  84    }
  85}
  86
  87#[derive(Clone, Debug, Default)]
  88struct TransformSummary {
  89    input: TextSummary,
  90    output: TextSummary,
  91}
  92
  93impl sum_tree::Summary for TransformSummary {
  94    type Context = ();
  95
  96    fn add_summary(&mut self, other: &Self, _: &()) {
  97        self.input += &other.input;
  98        self.output += &other.output;
  99    }
 100}
 101
 102pub type InlayEdit = Edit<InlayOffset>;
 103
 104#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 105pub struct InlayOffset(pub usize);
 106
 107impl Add for InlayOffset {
 108    type Output = Self;
 109
 110    fn add(self, rhs: Self) -> Self::Output {
 111        Self(self.0 + rhs.0)
 112    }
 113}
 114
 115impl Sub for InlayOffset {
 116    type Output = Self;
 117
 118    fn sub(self, rhs: Self) -> Self::Output {
 119        Self(self.0 - rhs.0)
 120    }
 121}
 122
 123impl AddAssign for InlayOffset {
 124    fn add_assign(&mut self, rhs: Self) {
 125        self.0 += rhs.0;
 126    }
 127}
 128
 129impl SubAssign for InlayOffset {
 130    fn sub_assign(&mut self, rhs: Self) {
 131        self.0 -= rhs.0;
 132    }
 133}
 134
 135impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
 136    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 137        self.0 += &summary.output.len;
 138    }
 139}
 140
 141#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 142pub struct InlayPoint(pub Point);
 143
 144impl Add for InlayPoint {
 145    type Output = Self;
 146
 147    fn add(self, rhs: Self) -> Self::Output {
 148        Self(self.0 + rhs.0)
 149    }
 150}
 151
 152impl Sub for InlayPoint {
 153    type Output = Self;
 154
 155    fn sub(self, rhs: Self) -> Self::Output {
 156        Self(self.0 - rhs.0)
 157    }
 158}
 159
 160impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
 161    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 162        self.0 += &summary.output.lines;
 163    }
 164}
 165
 166impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
 167    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 168        *self += &summary.input.len;
 169    }
 170}
 171
 172impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
 173    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 174        *self += &summary.input.lines;
 175    }
 176}
 177
 178#[derive(Clone)]
 179pub struct InlayBufferRows<'a> {
 180    transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
 181    buffer_rows: MultiBufferRows<'a>,
 182    inlay_row: u32,
 183    max_buffer_row: u32,
 184}
 185
 186#[derive(Copy, Clone, Eq, PartialEq)]
 187struct HighlightEndpoint {
 188    offset: InlayOffset,
 189    is_start: bool,
 190    tag: Option<TypeId>,
 191    style: HighlightStyle,
 192}
 193
 194impl PartialOrd for HighlightEndpoint {
 195    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
 196        Some(self.cmp(other))
 197    }
 198}
 199
 200impl Ord for HighlightEndpoint {
 201    fn cmp(&self, other: &Self) -> cmp::Ordering {
 202        self.offset
 203            .cmp(&other.offset)
 204            .then_with(|| other.is_start.cmp(&self.is_start))
 205    }
 206}
 207
 208pub struct InlayChunks<'a> {
 209    transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
 210    buffer_chunks: MultiBufferChunks<'a>,
 211    buffer_chunk: Option<Chunk<'a>>,
 212    inlay_chunks: Option<text::Chunks<'a>>,
 213    output_offset: InlayOffset,
 214    max_output_offset: InlayOffset,
 215    hint_highlight_style: Option<HighlightStyle>,
 216    suggestion_highlight_style: Option<HighlightStyle>,
 217    highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
 218    active_highlights: BTreeMap<Option<TypeId>, HighlightStyle>,
 219    snapshot: &'a InlaySnapshot,
 220}
 221
 222impl<'a> InlayChunks<'a> {
 223    pub fn seek(&mut self, offset: InlayOffset) {
 224        self.transforms.seek(&offset, Bias::Right, &());
 225
 226        let buffer_offset = self.snapshot.to_buffer_offset(offset);
 227        self.buffer_chunks.seek(buffer_offset);
 228        self.inlay_chunks = None;
 229        self.buffer_chunk = None;
 230        self.output_offset = offset;
 231    }
 232
 233    pub fn offset(&self) -> InlayOffset {
 234        self.output_offset
 235    }
 236}
 237
 238impl<'a> Iterator for InlayChunks<'a> {
 239    type Item = Chunk<'a>;
 240
 241    fn next(&mut self) -> Option<Self::Item> {
 242        if self.output_offset == self.max_output_offset {
 243            return None;
 244        }
 245
 246        let mut next_highlight_endpoint = InlayOffset(usize::MAX);
 247        while let Some(endpoint) = self.highlight_endpoints.peek().copied() {
 248            if endpoint.offset <= self.output_offset {
 249                if endpoint.is_start {
 250                    self.active_highlights.insert(endpoint.tag, endpoint.style);
 251                } else {
 252                    self.active_highlights.remove(&endpoint.tag);
 253                }
 254                self.highlight_endpoints.next();
 255            } else {
 256                next_highlight_endpoint = endpoint.offset;
 257                break;
 258            }
 259        }
 260
 261        let chunk = match self.transforms.item()? {
 262            Transform::Isomorphic(_) => {
 263                let chunk = self
 264                    .buffer_chunk
 265                    .get_or_insert_with(|| self.buffer_chunks.next().unwrap());
 266                if chunk.text.is_empty() {
 267                    *chunk = self.buffer_chunks.next().unwrap();
 268                }
 269
 270                let (prefix, suffix) = chunk.text.split_at(
 271                    chunk
 272                        .text
 273                        .len()
 274                        .min(self.transforms.end(&()).0 .0 - self.output_offset.0)
 275                        .min(next_highlight_endpoint.0 - self.output_offset.0),
 276                );
 277
 278                chunk.text = suffix;
 279                self.output_offset.0 += prefix.len();
 280                let mut prefix = Chunk {
 281                    text: prefix,
 282                    ..chunk.clone()
 283                };
 284                if !self.active_highlights.is_empty() {
 285                    let mut highlight_style = HighlightStyle::default();
 286                    for active_highlight in self.active_highlights.values() {
 287                        highlight_style.highlight(*active_highlight);
 288                    }
 289                    prefix.highlight_style = Some(highlight_style);
 290                }
 291                prefix
 292            }
 293            Transform::Inlay(inlay) => {
 294                let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| {
 295                    let start = self.output_offset - self.transforms.start().0;
 296                    let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0)
 297                        - self.transforms.start().0;
 298                    inlay.text.chunks_in_range(start.0..end.0)
 299                });
 300
 301                let chunk = inlay_chunks.next().unwrap();
 302                self.output_offset.0 += chunk.len();
 303                let highlight_style = match inlay.id {
 304                    InlayId::Suggestion(_) => self.suggestion_highlight_style,
 305                    InlayId::Hint(_) => self.hint_highlight_style,
 306                };
 307                Chunk {
 308                    text: chunk,
 309                    highlight_style,
 310                    ..Default::default()
 311                }
 312            }
 313        };
 314
 315        if self.output_offset == self.transforms.end(&()).0 {
 316            self.inlay_chunks = None;
 317            self.transforms.next(&());
 318        }
 319
 320        Some(chunk)
 321    }
 322}
 323
 324impl<'a> InlayBufferRows<'a> {
 325    pub fn seek(&mut self, row: u32) {
 326        let inlay_point = InlayPoint::new(row, 0);
 327        self.transforms.seek(&inlay_point, Bias::Left, &());
 328
 329        let mut buffer_point = self.transforms.start().1;
 330        let buffer_row = if row == 0 {
 331            0
 332        } else {
 333            match self.transforms.item() {
 334                Some(Transform::Isomorphic(_)) => {
 335                    buffer_point += inlay_point.0 - self.transforms.start().0 .0;
 336                    buffer_point.row
 337                }
 338                _ => cmp::min(buffer_point.row + 1, self.max_buffer_row),
 339            }
 340        };
 341        self.inlay_row = inlay_point.row();
 342        self.buffer_rows.seek(buffer_row);
 343    }
 344}
 345
 346impl<'a> Iterator for InlayBufferRows<'a> {
 347    type Item = Option<u32>;
 348
 349    fn next(&mut self) -> Option<Self::Item> {
 350        let buffer_row = if self.inlay_row == 0 {
 351            self.buffer_rows.next().unwrap()
 352        } else {
 353            match self.transforms.item()? {
 354                Transform::Inlay(_) => None,
 355                Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
 356            }
 357        };
 358
 359        self.inlay_row += 1;
 360        self.transforms
 361            .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
 362
 363        Some(buffer_row)
 364    }
 365}
 366
 367impl InlayPoint {
 368    pub fn new(row: u32, column: u32) -> Self {
 369        Self(Point::new(row, column))
 370    }
 371
 372    pub fn row(self) -> u32 {
 373        self.0.row
 374    }
 375}
 376
 377impl InlayMap {
 378    pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
 379        let version = 0;
 380        let snapshot = InlaySnapshot {
 381            buffer: buffer.clone(),
 382            transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
 383            version,
 384        };
 385
 386        (
 387            Self {
 388                snapshot: snapshot.clone(),
 389                inlays: Vec::new(),
 390            },
 391            snapshot,
 392        )
 393    }
 394
 395    pub fn sync(
 396        &mut self,
 397        buffer_snapshot: MultiBufferSnapshot,
 398        mut buffer_edits: Vec<text::Edit<usize>>,
 399    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 400        let snapshot = &mut self.snapshot;
 401
 402        if buffer_edits.is_empty() {
 403            if snapshot.buffer.trailing_excerpt_update_count()
 404                != buffer_snapshot.trailing_excerpt_update_count()
 405            {
 406                buffer_edits.push(Edit {
 407                    old: snapshot.buffer.len()..snapshot.buffer.len(),
 408                    new: buffer_snapshot.len()..buffer_snapshot.len(),
 409                });
 410            }
 411        }
 412
 413        if buffer_edits.is_empty() {
 414            if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
 415                || snapshot.buffer.parse_count() != buffer_snapshot.parse_count()
 416                || snapshot.buffer.diagnostics_update_count()
 417                    != buffer_snapshot.diagnostics_update_count()
 418                || snapshot.buffer.git_diff_update_count()
 419                    != buffer_snapshot.git_diff_update_count()
 420                || snapshot.buffer.trailing_excerpt_update_count()
 421                    != buffer_snapshot.trailing_excerpt_update_count()
 422            {
 423                snapshot.version += 1;
 424            }
 425
 426            snapshot.buffer = buffer_snapshot;
 427            (snapshot.clone(), Vec::new())
 428        } else {
 429            let mut inlay_edits = Patch::default();
 430            let mut new_transforms = SumTree::new();
 431            let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>();
 432            let mut buffer_edits_iter = buffer_edits.iter().peekable();
 433            while let Some(buffer_edit) = buffer_edits_iter.next() {
 434                new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
 435                if let Some(Transform::Isomorphic(transform)) = cursor.item() {
 436                    if cursor.end(&()).0 == buffer_edit.old.start {
 437                        push_isomorphic(&mut new_transforms, transform.clone());
 438                        cursor.next(&());
 439                    }
 440                }
 441
 442                // Remove all the inlays and transforms contained by the edit.
 443                let old_start =
 444                    cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
 445                cursor.seek(&buffer_edit.old.end, Bias::Right, &());
 446                let old_end =
 447                    cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
 448
 449                // Push the unchanged prefix.
 450                let prefix_start = new_transforms.summary().input.len;
 451                let prefix_end = buffer_edit.new.start;
 452                push_isomorphic(
 453                    &mut new_transforms,
 454                    buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
 455                );
 456                let new_start = InlayOffset(new_transforms.summary().output.len);
 457
 458                let start_ix = match self.inlays.binary_search_by(|probe| {
 459                    probe
 460                        .position
 461                        .to_offset(&buffer_snapshot)
 462                        .cmp(&buffer_edit.new.start)
 463                        .then(std::cmp::Ordering::Greater)
 464                }) {
 465                    Ok(ix) | Err(ix) => ix,
 466                };
 467
 468                for inlay in &self.inlays[start_ix..] {
 469                    let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
 470                    if buffer_offset > buffer_edit.new.end {
 471                        break;
 472                    }
 473
 474                    let prefix_start = new_transforms.summary().input.len;
 475                    let prefix_end = buffer_offset;
 476                    push_isomorphic(
 477                        &mut new_transforms,
 478                        buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
 479                    );
 480
 481                    if inlay.position.is_valid(&buffer_snapshot) {
 482                        new_transforms.push(Transform::Inlay(inlay.clone()), &());
 483                    }
 484                }
 485
 486                // Apply the rest of the edit.
 487                let transform_start = new_transforms.summary().input.len;
 488                push_isomorphic(
 489                    &mut new_transforms,
 490                    buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
 491                );
 492                let new_end = InlayOffset(new_transforms.summary().output.len);
 493                inlay_edits.push(Edit {
 494                    old: old_start..old_end,
 495                    new: new_start..new_end,
 496                });
 497
 498                // If the next edit doesn't intersect the current isomorphic transform, then
 499                // we can push its remainder.
 500                if buffer_edits_iter
 501                    .peek()
 502                    .map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
 503                {
 504                    let transform_start = new_transforms.summary().input.len;
 505                    let transform_end =
 506                        buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
 507                    push_isomorphic(
 508                        &mut new_transforms,
 509                        buffer_snapshot.text_summary_for_range(transform_start..transform_end),
 510                    );
 511                    cursor.next(&());
 512                }
 513            }
 514
 515            new_transforms.append(cursor.suffix(&()), &());
 516            if new_transforms.is_empty() {
 517                new_transforms.push(Transform::Isomorphic(Default::default()), &());
 518            }
 519
 520            drop(cursor);
 521            snapshot.transforms = new_transforms;
 522            snapshot.version += 1;
 523            snapshot.buffer = buffer_snapshot;
 524            snapshot.check_invariants();
 525
 526            (snapshot.clone(), inlay_edits.into_inner())
 527        }
 528    }
 529
 530    pub fn splice(
 531        &mut self,
 532        to_remove: Vec<InlayId>,
 533        to_insert: Vec<Inlay>,
 534    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 535        let snapshot = &mut self.snapshot;
 536        let mut edits = BTreeSet::new();
 537
 538        self.inlays.retain(|inlay| {
 539            let retain = !to_remove.contains(&inlay.id);
 540            if !retain {
 541                let offset = inlay.position.to_offset(&snapshot.buffer);
 542                edits.insert(offset);
 543            }
 544            retain
 545        });
 546
 547        for inlay_to_insert in to_insert {
 548            // Avoid inserting empty inlays.
 549            if inlay_to_insert.text.is_empty() {
 550                continue;
 551            }
 552
 553            let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
 554            match self.inlays.binary_search_by(|probe| {
 555                probe
 556                    .position
 557                    .cmp(&inlay_to_insert.position, &snapshot.buffer)
 558            }) {
 559                Ok(ix) | Err(ix) => {
 560                    self.inlays.insert(ix, inlay_to_insert);
 561                }
 562            }
 563
 564            edits.insert(offset);
 565        }
 566
 567        let buffer_edits = edits
 568            .into_iter()
 569            .map(|offset| Edit {
 570                old: offset..offset,
 571                new: offset..offset,
 572            })
 573            .collect();
 574        let buffer_snapshot = snapshot.buffer.clone();
 575        let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
 576        (snapshot, edits)
 577    }
 578
 579    pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
 580        self.inlays.iter()
 581    }
 582
 583    #[cfg(test)]
 584    pub(crate) fn randomly_mutate(
 585        &mut self,
 586        next_inlay_id: &mut usize,
 587        rng: &mut rand::rngs::StdRng,
 588    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 589        use rand::prelude::*;
 590        use util::post_inc;
 591
 592        let mut to_remove = Vec::new();
 593        let mut to_insert = Vec::new();
 594        let snapshot = &mut self.snapshot;
 595        for i in 0..rng.gen_range(1..=5) {
 596            if self.inlays.is_empty() || rng.gen() {
 597                let position = snapshot.buffer.random_byte_range(0, rng).start;
 598                let bias = if rng.gen() { Bias::Left } else { Bias::Right };
 599                let len = if rng.gen_bool(0.01) {
 600                    0
 601                } else {
 602                    rng.gen_range(1..=5)
 603                };
 604                let text = util::RandomCharIter::new(&mut *rng)
 605                    .filter(|ch| *ch != '\r')
 606                    .take(len)
 607                    .collect::<String>();
 608                log::info!(
 609                    "creating inlay at buffer offset {} with bias {:?} and text {:?}",
 610                    position,
 611                    bias,
 612                    text
 613                );
 614
 615                let inlay_id = if i % 2 == 0 {
 616                    InlayId::Hint(post_inc(next_inlay_id))
 617                } else {
 618                    InlayId::Suggestion(post_inc(next_inlay_id))
 619                };
 620                to_insert.push(Inlay {
 621                    id: inlay_id,
 622                    position: snapshot.buffer.anchor_at(position, bias),
 623                    text: text.into(),
 624                });
 625            } else {
 626                to_remove.push(
 627                    self.inlays
 628                        .iter()
 629                        .choose(rng)
 630                        .map(|inlay| inlay.id)
 631                        .unwrap(),
 632                );
 633            }
 634        }
 635        log::info!("removing inlays: {:?}", to_remove);
 636
 637        let (snapshot, edits) = self.splice(to_remove, to_insert);
 638        (snapshot, edits)
 639    }
 640}
 641
 642impl InlaySnapshot {
 643    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
 644        let mut cursor = self
 645            .transforms
 646            .cursor::<(InlayOffset, (InlayPoint, usize))>();
 647        cursor.seek(&offset, Bias::Right, &());
 648        let overshoot = offset.0 - cursor.start().0 .0;
 649        match cursor.item() {
 650            Some(Transform::Isomorphic(_)) => {
 651                let buffer_offset_start = cursor.start().1 .1;
 652                let buffer_offset_end = buffer_offset_start + overshoot;
 653                let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
 654                let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
 655                InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start))
 656            }
 657            Some(Transform::Inlay(inlay)) => {
 658                let overshoot = inlay.text.offset_to_point(overshoot);
 659                InlayPoint(cursor.start().1 .0 .0 + overshoot)
 660            }
 661            None => self.max_point(),
 662        }
 663    }
 664
 665    pub fn len(&self) -> InlayOffset {
 666        InlayOffset(self.transforms.summary().output.len)
 667    }
 668
 669    pub fn max_point(&self) -> InlayPoint {
 670        InlayPoint(self.transforms.summary().output.lines)
 671    }
 672
 673    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
 674        let mut cursor = self
 675            .transforms
 676            .cursor::<(InlayPoint, (InlayOffset, Point))>();
 677        cursor.seek(&point, Bias::Right, &());
 678        let overshoot = point.0 - cursor.start().0 .0;
 679        match cursor.item() {
 680            Some(Transform::Isomorphic(_)) => {
 681                let buffer_point_start = cursor.start().1 .1;
 682                let buffer_point_end = buffer_point_start + overshoot;
 683                let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
 684                let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
 685                InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start))
 686            }
 687            Some(Transform::Inlay(inlay)) => {
 688                let overshoot = inlay.text.point_to_offset(overshoot);
 689                InlayOffset(cursor.start().1 .0 .0 + overshoot)
 690            }
 691            None => self.len(),
 692        }
 693    }
 694
 695    pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
 696        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
 697        cursor.seek(&point, Bias::Right, &());
 698        match cursor.item() {
 699            Some(Transform::Isomorphic(_)) => {
 700                let overshoot = point.0 - cursor.start().0 .0;
 701                cursor.start().1 + overshoot
 702            }
 703            Some(Transform::Inlay(_)) => cursor.start().1,
 704            None => self.buffer.max_point(),
 705        }
 706    }
 707
 708    pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
 709        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
 710        cursor.seek(&offset, Bias::Right, &());
 711        match cursor.item() {
 712            Some(Transform::Isomorphic(_)) => {
 713                let overshoot = offset - cursor.start().0;
 714                cursor.start().1 + overshoot.0
 715            }
 716            Some(Transform::Inlay(_)) => cursor.start().1,
 717            None => self.buffer.len(),
 718        }
 719    }
 720
 721    pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
 722        let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>();
 723        cursor.seek(&offset, Bias::Left, &());
 724        loop {
 725            match cursor.item() {
 726                Some(Transform::Isomorphic(_)) => {
 727                    if offset == cursor.end(&()).0 {
 728                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 729                            if inlay.position.bias() == Bias::Right {
 730                                break;
 731                            } else {
 732                                cursor.next(&());
 733                            }
 734                        }
 735                        return cursor.end(&()).1;
 736                    } else {
 737                        let overshoot = offset - cursor.start().0;
 738                        return InlayOffset(cursor.start().1 .0 + overshoot);
 739                    }
 740                }
 741                Some(Transform::Inlay(inlay)) => {
 742                    if inlay.position.bias() == Bias::Left {
 743                        cursor.next(&());
 744                    } else {
 745                        return cursor.start().1;
 746                    }
 747                }
 748                None => {
 749                    return self.len();
 750                }
 751            }
 752        }
 753    }
 754
 755    pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
 756        let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
 757        cursor.seek(&point, Bias::Left, &());
 758        loop {
 759            match cursor.item() {
 760                Some(Transform::Isomorphic(_)) => {
 761                    if point == cursor.end(&()).0 {
 762                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 763                            if inlay.position.bias() == Bias::Right {
 764                                break;
 765                            } else {
 766                                cursor.next(&());
 767                            }
 768                        }
 769                        return cursor.end(&()).1;
 770                    } else {
 771                        let overshoot = point - cursor.start().0;
 772                        return InlayPoint(cursor.start().1 .0 + overshoot);
 773                    }
 774                }
 775                Some(Transform::Inlay(inlay)) => {
 776                    if inlay.position.bias() == Bias::Left {
 777                        cursor.next(&());
 778                    } else {
 779                        return cursor.start().1;
 780                    }
 781                }
 782                None => {
 783                    return self.max_point();
 784                }
 785            }
 786        }
 787    }
 788
 789    pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
 790        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
 791        cursor.seek(&point, Bias::Left, &());
 792        loop {
 793            match cursor.item() {
 794                Some(Transform::Isomorphic(transform)) => {
 795                    if cursor.start().0 == point {
 796                        if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
 797                            if inlay.position.bias() == Bias::Left {
 798                                return point;
 799                            } else if bias == Bias::Left {
 800                                cursor.prev(&());
 801                            } else if transform.first_line_chars == 0 {
 802                                point.0 += Point::new(1, 0);
 803                            } else {
 804                                point.0 += Point::new(0, 1);
 805                            }
 806                        } else {
 807                            return point;
 808                        }
 809                    } else if cursor.end(&()).0 == point {
 810                        if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 811                            if inlay.position.bias() == Bias::Right {
 812                                return point;
 813                            } else if bias == Bias::Right {
 814                                cursor.next(&());
 815                            } else if point.0.column == 0 {
 816                                point.0.row -= 1;
 817                                point.0.column = self.line_len(point.0.row);
 818                            } else {
 819                                point.0.column -= 1;
 820                            }
 821                        } else {
 822                            return point;
 823                        }
 824                    } else {
 825                        let overshoot = point.0 - cursor.start().0 .0;
 826                        let buffer_point = cursor.start().1 + overshoot;
 827                        let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
 828                        let clipped_overshoot = clipped_buffer_point - cursor.start().1;
 829                        let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot);
 830                        if clipped_point == point {
 831                            return clipped_point;
 832                        } else {
 833                            point = clipped_point;
 834                        }
 835                    }
 836                }
 837                Some(Transform::Inlay(inlay)) => {
 838                    if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
 839                        match cursor.prev_item() {
 840                            Some(Transform::Inlay(inlay)) => {
 841                                if inlay.position.bias() == Bias::Left {
 842                                    return point;
 843                                }
 844                            }
 845                            _ => return point,
 846                        }
 847                    } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left {
 848                        match cursor.next_item() {
 849                            Some(Transform::Inlay(inlay)) => {
 850                                if inlay.position.bias() == Bias::Right {
 851                                    return point;
 852                                }
 853                            }
 854                            _ => return point,
 855                        }
 856                    }
 857
 858                    if bias == Bias::Left {
 859                        point = cursor.start().0;
 860                        cursor.prev(&());
 861                    } else {
 862                        cursor.next(&());
 863                        point = cursor.start().0;
 864                    }
 865                }
 866                None => {
 867                    bias = bias.invert();
 868                    if bias == Bias::Left {
 869                        point = cursor.start().0;
 870                        cursor.prev(&());
 871                    } else {
 872                        cursor.next(&());
 873                        point = cursor.start().0;
 874                    }
 875                }
 876            }
 877        }
 878    }
 879
 880    pub fn text_summary(&self) -> TextSummary {
 881        self.transforms.summary().output.clone()
 882    }
 883
 884    pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
 885        let mut summary = TextSummary::default();
 886
 887        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
 888        cursor.seek(&range.start, Bias::Right, &());
 889
 890        let overshoot = range.start.0 - cursor.start().0 .0;
 891        match cursor.item() {
 892            Some(Transform::Isomorphic(_)) => {
 893                let buffer_start = cursor.start().1;
 894                let suffix_start = buffer_start + overshoot;
 895                let suffix_end =
 896                    buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
 897                summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
 898                cursor.next(&());
 899            }
 900            Some(Transform::Inlay(inlay)) => {
 901                let suffix_start = overshoot;
 902                let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0;
 903                summary = inlay.text.cursor(suffix_start).summary(suffix_end);
 904                cursor.next(&());
 905            }
 906            None => {}
 907        }
 908
 909        if range.end > cursor.start().0 {
 910            summary += cursor
 911                .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
 912                .output;
 913
 914            let overshoot = range.end.0 - cursor.start().0 .0;
 915            match cursor.item() {
 916                Some(Transform::Isomorphic(_)) => {
 917                    let prefix_start = cursor.start().1;
 918                    let prefix_end = prefix_start + overshoot;
 919                    summary += self
 920                        .buffer
 921                        .text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
 922                }
 923                Some(Transform::Inlay(inlay)) => {
 924                    let prefix_end = overshoot;
 925                    summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
 926                }
 927                None => {}
 928            }
 929        }
 930
 931        summary
 932    }
 933
 934    pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
 935        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
 936        let inlay_point = InlayPoint::new(row, 0);
 937        cursor.seek(&inlay_point, Bias::Left, &());
 938
 939        let max_buffer_row = self.buffer.max_point().row;
 940        let mut buffer_point = cursor.start().1;
 941        let buffer_row = if row == 0 {
 942            0
 943        } else {
 944            match cursor.item() {
 945                Some(Transform::Isomorphic(_)) => {
 946                    buffer_point += inlay_point.0 - cursor.start().0 .0;
 947                    buffer_point.row
 948                }
 949                _ => cmp::min(buffer_point.row + 1, max_buffer_row),
 950            }
 951        };
 952
 953        InlayBufferRows {
 954            transforms: cursor,
 955            inlay_row: inlay_point.row(),
 956            buffer_rows: self.buffer.buffer_rows(buffer_row),
 957            max_buffer_row,
 958        }
 959    }
 960
 961    pub fn line_len(&self, row: u32) -> u32 {
 962        let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
 963        let line_end = if row >= self.max_point().row() {
 964            self.len().0
 965        } else {
 966            self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
 967        };
 968        (line_end - line_start) as u32
 969    }
 970
 971    pub fn chunks<'a>(
 972        &'a self,
 973        range: Range<InlayOffset>,
 974        language_aware: bool,
 975        text_highlights: Option<&'a TextHighlights>,
 976        hint_highlights: Option<HighlightStyle>,
 977        suggestion_highlights: Option<HighlightStyle>,
 978    ) -> InlayChunks<'a> {
 979        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
 980        cursor.seek(&range.start, Bias::Right, &());
 981
 982        let mut highlight_endpoints = Vec::new();
 983        if let Some(text_highlights) = text_highlights {
 984            if !text_highlights.is_empty() {
 985                while cursor.start().0 < range.end {
 986                    if true {
 987                        let transform_start = self.buffer.anchor_after(
 988                            self.to_buffer_offset(cmp::max(range.start, cursor.start().0)),
 989                        );
 990
 991                        let transform_end = {
 992                            let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
 993                            self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
 994                                cursor.end(&()).0,
 995                                cursor.start().0 + overshoot,
 996                            )))
 997                        };
 998
 999                        for (tag, highlights) in text_highlights.iter() {
1000                            let style = highlights.0;
1001                            let ranges = &highlights.1;
1002
1003                            let start_ix = match ranges.binary_search_by(|probe| {
1004                                let cmp = probe.end.cmp(&transform_start, &self.buffer);
1005                                if cmp.is_gt() {
1006                                    cmp::Ordering::Greater
1007                                } else {
1008                                    cmp::Ordering::Less
1009                                }
1010                            }) {
1011                                Ok(i) | Err(i) => i,
1012                            };
1013                            for range in &ranges[start_ix..] {
1014                                if range.start.cmp(&transform_end, &self.buffer).is_ge() {
1015                                    break;
1016                                }
1017
1018                                highlight_endpoints.push(HighlightEndpoint {
1019                                    offset: self
1020                                        .to_inlay_offset(range.start.to_offset(&self.buffer)),
1021                                    is_start: true,
1022                                    tag: *tag,
1023                                    style,
1024                                });
1025                                highlight_endpoints.push(HighlightEndpoint {
1026                                    offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
1027                                    is_start: false,
1028                                    tag: *tag,
1029                                    style,
1030                                });
1031                            }
1032                        }
1033                    }
1034
1035                    cursor.next(&());
1036                }
1037                highlight_endpoints.sort();
1038                cursor.seek(&range.start, Bias::Right, &());
1039            }
1040        }
1041
1042        let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
1043        let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
1044
1045        InlayChunks {
1046            transforms: cursor,
1047            buffer_chunks,
1048            inlay_chunks: None,
1049            buffer_chunk: None,
1050            output_offset: range.start,
1051            max_output_offset: range.end,
1052            hint_highlight_style: hint_highlights,
1053            suggestion_highlight_style: suggestion_highlights,
1054            highlight_endpoints: highlight_endpoints.into_iter().peekable(),
1055            active_highlights: Default::default(),
1056            snapshot: self,
1057        }
1058    }
1059
1060    #[cfg(test)]
1061    pub fn text(&self) -> String {
1062        self.chunks(Default::default()..self.len(), false, None, None, None)
1063            .map(|chunk| chunk.text)
1064            .collect()
1065    }
1066
1067    fn check_invariants(&self) {
1068        #[cfg(any(debug_assertions, feature = "test-support"))]
1069        {
1070            assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1071            let mut transforms = self.transforms.iter().peekable();
1072            while let Some(transform) = transforms.next() {
1073                let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1074                if let Some(next_transform) = transforms.peek() {
1075                    let next_transform_is_isomorphic =
1076                        matches!(next_transform, Transform::Isomorphic(_));
1077                    assert!(
1078                        !transform_is_isomorphic || !next_transform_is_isomorphic,
1079                        "two adjacent isomorphic transforms"
1080                    );
1081                }
1082            }
1083        }
1084    }
1085}
1086
1087fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
1088    if summary.len == 0 {
1089        return;
1090    }
1091
1092    let mut summary = Some(summary);
1093    sum_tree.update_last(
1094        |transform| {
1095            if let Transform::Isomorphic(transform) = transform {
1096                *transform += summary.take().unwrap();
1097            }
1098        },
1099        &(),
1100    );
1101
1102    if let Some(summary) = summary {
1103        sum_tree.push(Transform::Isomorphic(summary), &());
1104    }
1105}
1106
1107#[cfg(test)]
1108mod tests {
1109    use super::*;
1110    use crate::{InlayId, MultiBuffer};
1111    use gpui::AppContext;
1112    use project::{InlayHint, InlayHintLabel};
1113    use rand::prelude::*;
1114    use settings::SettingsStore;
1115    use std::{cmp::Reverse, env, sync::Arc};
1116    use sum_tree::TreeMap;
1117    use text::Patch;
1118    use util::post_inc;
1119
1120    #[test]
1121    fn test_inlay_properties_label_padding() {
1122        assert_eq!(
1123            Inlay::hint(
1124                0,
1125                Anchor::min(),
1126                &InlayHint {
1127                    label: InlayHintLabel::String("a".to_string()),
1128                    buffer_id: 0,
1129                    position: text::Anchor::default(),
1130                    padding_left: false,
1131                    padding_right: false,
1132                    tooltip: None,
1133                    kind: None,
1134                },
1135            )
1136            .text
1137            .to_string(),
1138            "a",
1139            "Should not pad label if not requested"
1140        );
1141
1142        assert_eq!(
1143            Inlay::hint(
1144                0,
1145                Anchor::min(),
1146                &InlayHint {
1147                    label: InlayHintLabel::String("a".to_string()),
1148                    buffer_id: 0,
1149                    position: text::Anchor::default(),
1150                    padding_left: true,
1151                    padding_right: true,
1152                    tooltip: None,
1153                    kind: None,
1154                },
1155            )
1156            .text
1157            .to_string(),
1158            " a ",
1159            "Should pad label for every side requested"
1160        );
1161
1162        assert_eq!(
1163            Inlay::hint(
1164                0,
1165                Anchor::min(),
1166                &InlayHint {
1167                    label: InlayHintLabel::String(" a ".to_string()),
1168                    buffer_id: 0,
1169                    position: text::Anchor::default(),
1170                    padding_left: false,
1171                    padding_right: false,
1172                    tooltip: None,
1173                    kind: None,
1174                },
1175            )
1176            .text
1177            .to_string(),
1178            " a ",
1179            "Should not change already padded label"
1180        );
1181
1182        assert_eq!(
1183            Inlay::hint(
1184                0,
1185                Anchor::min(),
1186                &InlayHint {
1187                    label: InlayHintLabel::String(" a ".to_string()),
1188                    buffer_id: 0,
1189                    position: text::Anchor::default(),
1190                    padding_left: true,
1191                    padding_right: true,
1192                    tooltip: None,
1193                    kind: None,
1194                },
1195            )
1196            .text
1197            .to_string(),
1198            " a ",
1199            "Should not change already padded label"
1200        );
1201    }
1202
1203    #[gpui::test]
1204    fn test_basic_inlays(cx: &mut AppContext) {
1205        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1206        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1207        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1208        assert_eq!(inlay_snapshot.text(), "abcdefghi");
1209        let mut next_inlay_id = 0;
1210
1211        let (inlay_snapshot, _) = inlay_map.splice(
1212            Vec::new(),
1213            vec![Inlay {
1214                id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1215                position: buffer.read(cx).snapshot(cx).anchor_after(3),
1216                text: "|123|".into(),
1217            }],
1218        );
1219        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1220        assert_eq!(
1221            inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1222            InlayPoint::new(0, 0)
1223        );
1224        assert_eq!(
1225            inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1226            InlayPoint::new(0, 1)
1227        );
1228        assert_eq!(
1229            inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1230            InlayPoint::new(0, 2)
1231        );
1232        assert_eq!(
1233            inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1234            InlayPoint::new(0, 3)
1235        );
1236        assert_eq!(
1237            inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1238            InlayPoint::new(0, 9)
1239        );
1240        assert_eq!(
1241            inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1242            InlayPoint::new(0, 10)
1243        );
1244        assert_eq!(
1245            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1246            InlayPoint::new(0, 0)
1247        );
1248        assert_eq!(
1249            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1250            InlayPoint::new(0, 0)
1251        );
1252        assert_eq!(
1253            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1254            InlayPoint::new(0, 3)
1255        );
1256        assert_eq!(
1257            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1258            InlayPoint::new(0, 3)
1259        );
1260        assert_eq!(
1261            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1262            InlayPoint::new(0, 3)
1263        );
1264        assert_eq!(
1265            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1266            InlayPoint::new(0, 9)
1267        );
1268
1269        // Edits before or after the inlay should not affect it.
1270        buffer.update(cx, |buffer, cx| {
1271            buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1272        });
1273        let (inlay_snapshot, _) = inlay_map.sync(
1274            buffer.read(cx).snapshot(cx),
1275            buffer_edits.consume().into_inner(),
1276        );
1277        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1278
1279        // An edit surrounding the inlay should invalidate it.
1280        buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1281        let (inlay_snapshot, _) = inlay_map.sync(
1282            buffer.read(cx).snapshot(cx),
1283            buffer_edits.consume().into_inner(),
1284        );
1285        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1286
1287        let (inlay_snapshot, _) = inlay_map.splice(
1288            Vec::new(),
1289            vec![
1290                Inlay {
1291                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1292                    position: buffer.read(cx).snapshot(cx).anchor_before(3),
1293                    text: "|123|".into(),
1294                },
1295                Inlay {
1296                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1297                    position: buffer.read(cx).snapshot(cx).anchor_after(3),
1298                    text: "|456|".into(),
1299                },
1300            ],
1301        );
1302        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1303
1304        // Edits ending where the inlay starts should not move it if it has a left bias.
1305        buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1306        let (inlay_snapshot, _) = inlay_map.sync(
1307            buffer.read(cx).snapshot(cx),
1308            buffer_edits.consume().into_inner(),
1309        );
1310        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1311
1312        assert_eq!(
1313            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1314            InlayPoint::new(0, 0)
1315        );
1316        assert_eq!(
1317            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1318            InlayPoint::new(0, 0)
1319        );
1320
1321        assert_eq!(
1322            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1323            InlayPoint::new(0, 1)
1324        );
1325        assert_eq!(
1326            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1327            InlayPoint::new(0, 1)
1328        );
1329
1330        assert_eq!(
1331            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1332            InlayPoint::new(0, 2)
1333        );
1334        assert_eq!(
1335            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1336            InlayPoint::new(0, 2)
1337        );
1338
1339        assert_eq!(
1340            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1341            InlayPoint::new(0, 2)
1342        );
1343        assert_eq!(
1344            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1345            InlayPoint::new(0, 8)
1346        );
1347
1348        assert_eq!(
1349            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1350            InlayPoint::new(0, 2)
1351        );
1352        assert_eq!(
1353            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1354            InlayPoint::new(0, 8)
1355        );
1356
1357        assert_eq!(
1358            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1359            InlayPoint::new(0, 2)
1360        );
1361        assert_eq!(
1362            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1363            InlayPoint::new(0, 8)
1364        );
1365
1366        assert_eq!(
1367            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1368            InlayPoint::new(0, 2)
1369        );
1370        assert_eq!(
1371            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1372            InlayPoint::new(0, 8)
1373        );
1374
1375        assert_eq!(
1376            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1377            InlayPoint::new(0, 2)
1378        );
1379        assert_eq!(
1380            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1381            InlayPoint::new(0, 8)
1382        );
1383
1384        assert_eq!(
1385            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1386            InlayPoint::new(0, 8)
1387        );
1388        assert_eq!(
1389            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1390            InlayPoint::new(0, 8)
1391        );
1392
1393        assert_eq!(
1394            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1395            InlayPoint::new(0, 9)
1396        );
1397        assert_eq!(
1398            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1399            InlayPoint::new(0, 9)
1400        );
1401
1402        assert_eq!(
1403            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1404            InlayPoint::new(0, 10)
1405        );
1406        assert_eq!(
1407            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1408            InlayPoint::new(0, 10)
1409        );
1410
1411        assert_eq!(
1412            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1413            InlayPoint::new(0, 11)
1414        );
1415        assert_eq!(
1416            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1417            InlayPoint::new(0, 11)
1418        );
1419
1420        assert_eq!(
1421            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1422            InlayPoint::new(0, 11)
1423        );
1424        assert_eq!(
1425            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1426            InlayPoint::new(0, 17)
1427        );
1428
1429        assert_eq!(
1430            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1431            InlayPoint::new(0, 11)
1432        );
1433        assert_eq!(
1434            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1435            InlayPoint::new(0, 17)
1436        );
1437
1438        assert_eq!(
1439            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1440            InlayPoint::new(0, 11)
1441        );
1442        assert_eq!(
1443            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1444            InlayPoint::new(0, 17)
1445        );
1446
1447        assert_eq!(
1448            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1449            InlayPoint::new(0, 11)
1450        );
1451        assert_eq!(
1452            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1453            InlayPoint::new(0, 17)
1454        );
1455
1456        assert_eq!(
1457            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1458            InlayPoint::new(0, 11)
1459        );
1460        assert_eq!(
1461            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1462            InlayPoint::new(0, 17)
1463        );
1464
1465        assert_eq!(
1466            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1467            InlayPoint::new(0, 17)
1468        );
1469        assert_eq!(
1470            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1471            InlayPoint::new(0, 17)
1472        );
1473
1474        assert_eq!(
1475            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1476            InlayPoint::new(0, 18)
1477        );
1478        assert_eq!(
1479            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1480            InlayPoint::new(0, 18)
1481        );
1482
1483        // The inlays can be manually removed.
1484        let (inlay_snapshot, _) = inlay_map.splice(
1485            inlay_map.inlays.iter().map(|inlay| inlay.id).collect(),
1486            Vec::new(),
1487        );
1488        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1489    }
1490
1491    #[gpui::test]
1492    fn test_inlay_buffer_rows(cx: &mut AppContext) {
1493        let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1494        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1495        assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1496        let mut next_inlay_id = 0;
1497
1498        let (inlay_snapshot, _) = inlay_map.splice(
1499            Vec::new(),
1500            vec![
1501                Inlay {
1502                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1503                    position: buffer.read(cx).snapshot(cx).anchor_before(0),
1504                    text: "|123|\n".into(),
1505                },
1506                Inlay {
1507                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1508                    position: buffer.read(cx).snapshot(cx).anchor_before(4),
1509                    text: "|456|".into(),
1510                },
1511                Inlay {
1512                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1513                    position: buffer.read(cx).snapshot(cx).anchor_before(7),
1514                    text: "\n|567|\n".into(),
1515                },
1516            ],
1517        );
1518        assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1519        assert_eq!(
1520            inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1521            vec![Some(0), None, Some(1), None, None, Some(2)]
1522        );
1523    }
1524
1525    #[gpui::test(iterations = 100)]
1526    fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1527        init_test(cx);
1528
1529        let operations = env::var("OPERATIONS")
1530            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1531            .unwrap_or(10);
1532
1533        let len = rng.gen_range(0..30);
1534        let buffer = if rng.gen() {
1535            let text = util::RandomCharIter::new(&mut rng)
1536                .take(len)
1537                .collect::<String>();
1538            MultiBuffer::build_simple(&text, cx)
1539        } else {
1540            MultiBuffer::build_random(&mut rng, cx)
1541        };
1542        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1543        let mut next_inlay_id = 0;
1544        log::info!("buffer text: {:?}", buffer_snapshot.text());
1545
1546        let mut highlights = TreeMap::default();
1547        let highlight_count = rng.gen_range(0_usize..10);
1548        let mut highlight_ranges = (0..highlight_count)
1549            .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
1550            .collect::<Vec<_>>();
1551        highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1552        log::info!("highlighting ranges {:?}", highlight_ranges);
1553        let highlight_ranges = highlight_ranges
1554            .into_iter()
1555            .map(|range| {
1556                buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end)
1557            })
1558            .collect::<Vec<_>>();
1559
1560        highlights.insert(
1561            Some(TypeId::of::<()>()),
1562            Arc::new((HighlightStyle::default(), highlight_ranges)),
1563        );
1564
1565        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1566        for _ in 0..operations {
1567            let mut inlay_edits = Patch::default();
1568
1569            let mut prev_inlay_text = inlay_snapshot.text();
1570            let mut buffer_edits = Vec::new();
1571            match rng.gen_range(0..=100) {
1572                0..=50 => {
1573                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1574                    log::info!("mutated text: {:?}", snapshot.text());
1575                    inlay_edits = Patch::new(edits);
1576                }
1577                _ => buffer.update(cx, |buffer, cx| {
1578                    let subscription = buffer.subscribe();
1579                    let edit_count = rng.gen_range(1..=5);
1580                    buffer.randomly_mutate(&mut rng, edit_count, cx);
1581                    buffer_snapshot = buffer.snapshot(cx);
1582                    let edits = subscription.consume().into_inner();
1583                    log::info!("editing {:?}", edits);
1584                    buffer_edits.extend(edits);
1585                }),
1586            };
1587
1588            let (new_inlay_snapshot, new_inlay_edits) =
1589                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1590            inlay_snapshot = new_inlay_snapshot;
1591            inlay_edits = inlay_edits.compose(new_inlay_edits);
1592
1593            log::info!("buffer text: {:?}", buffer_snapshot.text());
1594            log::info!("inlay text: {:?}", inlay_snapshot.text());
1595
1596            let inlays = inlay_map
1597                .inlays
1598                .iter()
1599                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1600                .map(|inlay| {
1601                    let offset = inlay.position.to_offset(&buffer_snapshot);
1602                    (offset, inlay.clone())
1603                })
1604                .collect::<Vec<_>>();
1605            let mut expected_text = Rope::from(buffer_snapshot.text());
1606            for (offset, inlay) in inlays.into_iter().rev() {
1607                expected_text.replace(offset..offset, &inlay.text.to_string());
1608            }
1609            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1610
1611            let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1612            assert_eq!(
1613                expected_buffer_rows.len() as u32,
1614                expected_text.max_point().row + 1
1615            );
1616            for row_start in 0..expected_buffer_rows.len() {
1617                assert_eq!(
1618                    inlay_snapshot
1619                        .buffer_rows(row_start as u32)
1620                        .collect::<Vec<_>>(),
1621                    &expected_buffer_rows[row_start..],
1622                    "incorrect buffer rows starting at {}",
1623                    row_start
1624                );
1625            }
1626
1627            for _ in 0..5 {
1628                let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1629                end = expected_text.clip_offset(end, Bias::Right);
1630                let mut start = rng.gen_range(0..=end);
1631                start = expected_text.clip_offset(start, Bias::Right);
1632
1633                let actual_text = inlay_snapshot
1634                    .chunks(
1635                        InlayOffset(start)..InlayOffset(end),
1636                        false,
1637                        Some(&highlights),
1638                        None,
1639                        None,
1640                    )
1641                    .map(|chunk| chunk.text)
1642                    .collect::<String>();
1643                assert_eq!(
1644                    actual_text,
1645                    expected_text.slice(start..end).to_string(),
1646                    "incorrect text in range {:?}",
1647                    start..end
1648                );
1649
1650                assert_eq!(
1651                    inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1652                    expected_text.slice(start..end).summary()
1653                );
1654            }
1655
1656            for edit in inlay_edits {
1657                prev_inlay_text.replace_range(
1658                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1659                    &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1660                );
1661            }
1662            assert_eq!(prev_inlay_text, inlay_snapshot.text());
1663
1664            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1665            assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1666
1667            let mut buffer_point = Point::default();
1668            let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1669            let mut buffer_chars = buffer_snapshot.chars_at(0);
1670            loop {
1671                // Ensure conversion from buffer coordinates to inlay coordinates
1672                // is consistent.
1673                let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
1674                assert_eq!(
1675                    inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
1676                    inlay_point
1677                );
1678
1679                // No matter which bias we clip an inlay point with, it doesn't move
1680                // because it was constructed from a buffer point.
1681                assert_eq!(
1682                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
1683                    inlay_point,
1684                    "invalid inlay point for buffer point {:?} when clipped left",
1685                    buffer_point
1686                );
1687                assert_eq!(
1688                    inlay_snapshot.clip_point(inlay_point, Bias::Right),
1689                    inlay_point,
1690                    "invalid inlay point for buffer point {:?} when clipped right",
1691                    buffer_point
1692                );
1693
1694                if let Some(ch) = buffer_chars.next() {
1695                    if ch == '\n' {
1696                        buffer_point += Point::new(1, 0);
1697                    } else {
1698                        buffer_point += Point::new(0, ch.len_utf8() as u32);
1699                    }
1700
1701                    // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1702                    let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1703                    assert!(new_inlay_point > inlay_point);
1704                    inlay_point = new_inlay_point;
1705                } else {
1706                    break;
1707                }
1708            }
1709
1710            let mut inlay_point = InlayPoint::default();
1711            let mut inlay_offset = InlayOffset::default();
1712            for ch in expected_text.chars() {
1713                assert_eq!(
1714                    inlay_snapshot.to_offset(inlay_point),
1715                    inlay_offset,
1716                    "invalid to_offset({:?})",
1717                    inlay_point
1718                );
1719                assert_eq!(
1720                    inlay_snapshot.to_point(inlay_offset),
1721                    inlay_point,
1722                    "invalid to_point({:?})",
1723                    inlay_offset
1724                );
1725
1726                let mut bytes = [0; 4];
1727                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1728                    inlay_offset.0 += 1;
1729                    if *byte == b'\n' {
1730                        inlay_point.0 += Point::new(1, 0);
1731                    } else {
1732                        inlay_point.0 += Point::new(0, 1);
1733                    }
1734
1735                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1736                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1737                    assert!(
1738                        clipped_left_point <= clipped_right_point,
1739                        "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1740                        inlay_point,
1741                        clipped_left_point,
1742                        clipped_right_point
1743                    );
1744
1745                    // Ensure the clipped points are at valid text locations.
1746                    assert_eq!(
1747                        clipped_left_point.0,
1748                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
1749                    );
1750                    assert_eq!(
1751                        clipped_right_point.0,
1752                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
1753                    );
1754
1755                    // Ensure the clipped points never overshoot the end of the map.
1756                    assert!(clipped_left_point <= inlay_snapshot.max_point());
1757                    assert!(clipped_right_point <= inlay_snapshot.max_point());
1758
1759                    // Ensure the clipped points are at valid buffer locations.
1760                    assert_eq!(
1761                        inlay_snapshot
1762                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1763                        clipped_left_point,
1764                        "to_buffer_point({:?}) = {:?}",
1765                        clipped_left_point,
1766                        inlay_snapshot.to_buffer_point(clipped_left_point),
1767                    );
1768                    assert_eq!(
1769                        inlay_snapshot
1770                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1771                        clipped_right_point,
1772                        "to_buffer_point({:?}) = {:?}",
1773                        clipped_right_point,
1774                        inlay_snapshot.to_buffer_point(clipped_right_point),
1775                    );
1776                }
1777            }
1778        }
1779    }
1780
1781    fn init_test(cx: &mut AppContext) {
1782        cx.set_global(SettingsStore::test(cx));
1783        theme::init((), cx);
1784    }
1785}