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                            // TODO kb add a way to highlight inlay hints through here.
1014                            for range in &ranges[start_ix..] {
1015                                if range.start.cmp(&transform_end, &self.buffer).is_ge() {
1016                                    break;
1017                                }
1018
1019                                highlight_endpoints.push(HighlightEndpoint {
1020                                    offset: self
1021                                        .to_inlay_offset(range.start.to_offset(&self.buffer)),
1022                                    is_start: true,
1023                                    tag: *tag,
1024                                    style,
1025                                });
1026                                highlight_endpoints.push(HighlightEndpoint {
1027                                    offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
1028                                    is_start: false,
1029                                    tag: *tag,
1030                                    style,
1031                                });
1032                            }
1033                        }
1034                    }
1035
1036                    cursor.next(&());
1037                }
1038                highlight_endpoints.sort();
1039                cursor.seek(&range.start, Bias::Right, &());
1040            }
1041        }
1042
1043        let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
1044        let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
1045
1046        InlayChunks {
1047            transforms: cursor,
1048            buffer_chunks,
1049            inlay_chunks: None,
1050            buffer_chunk: None,
1051            output_offset: range.start,
1052            max_output_offset: range.end,
1053            hint_highlight_style: hint_highlights,
1054            suggestion_highlight_style: suggestion_highlights,
1055            highlight_endpoints: highlight_endpoints.into_iter().peekable(),
1056            active_highlights: Default::default(),
1057            snapshot: self,
1058        }
1059    }
1060
1061    #[cfg(test)]
1062    pub fn text(&self) -> String {
1063        self.chunks(Default::default()..self.len(), false, None, None, None)
1064            .map(|chunk| chunk.text)
1065            .collect()
1066    }
1067
1068    fn check_invariants(&self) {
1069        #[cfg(any(debug_assertions, feature = "test-support"))]
1070        {
1071            assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1072            let mut transforms = self.transforms.iter().peekable();
1073            while let Some(transform) = transforms.next() {
1074                let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1075                if let Some(next_transform) = transforms.peek() {
1076                    let next_transform_is_isomorphic =
1077                        matches!(next_transform, Transform::Isomorphic(_));
1078                    assert!(
1079                        !transform_is_isomorphic || !next_transform_is_isomorphic,
1080                        "two adjacent isomorphic transforms"
1081                    );
1082                }
1083            }
1084        }
1085    }
1086}
1087
1088fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
1089    if summary.len == 0 {
1090        return;
1091    }
1092
1093    let mut summary = Some(summary);
1094    sum_tree.update_last(
1095        |transform| {
1096            if let Transform::Isomorphic(transform) = transform {
1097                *transform += summary.take().unwrap();
1098            }
1099        },
1100        &(),
1101    );
1102
1103    if let Some(summary) = summary {
1104        sum_tree.push(Transform::Isomorphic(summary), &());
1105    }
1106}
1107
1108#[cfg(test)]
1109mod tests {
1110    use super::*;
1111    use crate::{InlayId, MultiBuffer};
1112    use gpui::AppContext;
1113    use project::{InlayHint, InlayHintLabel, ResolveState};
1114    use rand::prelude::*;
1115    use settings::SettingsStore;
1116    use std::{cmp::Reverse, env, sync::Arc};
1117    use sum_tree::TreeMap;
1118    use text::Patch;
1119    use util::post_inc;
1120
1121    #[test]
1122    fn test_inlay_properties_label_padding() {
1123        assert_eq!(
1124            Inlay::hint(
1125                0,
1126                Anchor::min(),
1127                &InlayHint {
1128                    label: InlayHintLabel::String("a".to_string()),
1129                    position: text::Anchor::default(),
1130                    padding_left: false,
1131                    padding_right: false,
1132                    tooltip: None,
1133                    kind: None,
1134                    resolve_state: ResolveState::Resolved,
1135                },
1136            )
1137            .text
1138            .to_string(),
1139            "a",
1140            "Should not pad label if not requested"
1141        );
1142
1143        assert_eq!(
1144            Inlay::hint(
1145                0,
1146                Anchor::min(),
1147                &InlayHint {
1148                    label: InlayHintLabel::String("a".to_string()),
1149                    position: text::Anchor::default(),
1150                    padding_left: true,
1151                    padding_right: true,
1152                    tooltip: None,
1153                    kind: None,
1154                    resolve_state: ResolveState::Resolved,
1155                },
1156            )
1157            .text
1158            .to_string(),
1159            " a ",
1160            "Should pad label for every side requested"
1161        );
1162
1163        assert_eq!(
1164            Inlay::hint(
1165                0,
1166                Anchor::min(),
1167                &InlayHint {
1168                    label: InlayHintLabel::String(" a ".to_string()),
1169                    position: text::Anchor::default(),
1170                    padding_left: false,
1171                    padding_right: false,
1172                    tooltip: None,
1173                    kind: None,
1174                    resolve_state: ResolveState::Resolved,
1175                },
1176            )
1177            .text
1178            .to_string(),
1179            " a ",
1180            "Should not change already padded label"
1181        );
1182
1183        assert_eq!(
1184            Inlay::hint(
1185                0,
1186                Anchor::min(),
1187                &InlayHint {
1188                    label: InlayHintLabel::String(" a ".to_string()),
1189                    position: text::Anchor::default(),
1190                    padding_left: true,
1191                    padding_right: true,
1192                    tooltip: None,
1193                    kind: None,
1194                    resolve_state: ResolveState::Resolved,
1195                },
1196            )
1197            .text
1198            .to_string(),
1199            " a ",
1200            "Should not change already padded label"
1201        );
1202    }
1203
1204    #[gpui::test]
1205    fn test_basic_inlays(cx: &mut AppContext) {
1206        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1207        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1208        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1209        assert_eq!(inlay_snapshot.text(), "abcdefghi");
1210        let mut next_inlay_id = 0;
1211
1212        let (inlay_snapshot, _) = inlay_map.splice(
1213            Vec::new(),
1214            vec![Inlay {
1215                id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1216                position: buffer.read(cx).snapshot(cx).anchor_after(3),
1217                text: "|123|".into(),
1218            }],
1219        );
1220        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1221        assert_eq!(
1222            inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1223            InlayPoint::new(0, 0)
1224        );
1225        assert_eq!(
1226            inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1227            InlayPoint::new(0, 1)
1228        );
1229        assert_eq!(
1230            inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1231            InlayPoint::new(0, 2)
1232        );
1233        assert_eq!(
1234            inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1235            InlayPoint::new(0, 3)
1236        );
1237        assert_eq!(
1238            inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1239            InlayPoint::new(0, 9)
1240        );
1241        assert_eq!(
1242            inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1243            InlayPoint::new(0, 10)
1244        );
1245        assert_eq!(
1246            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1247            InlayPoint::new(0, 0)
1248        );
1249        assert_eq!(
1250            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1251            InlayPoint::new(0, 0)
1252        );
1253        assert_eq!(
1254            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1255            InlayPoint::new(0, 3)
1256        );
1257        assert_eq!(
1258            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1259            InlayPoint::new(0, 3)
1260        );
1261        assert_eq!(
1262            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1263            InlayPoint::new(0, 3)
1264        );
1265        assert_eq!(
1266            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1267            InlayPoint::new(0, 9)
1268        );
1269
1270        // Edits before or after the inlay should not affect it.
1271        buffer.update(cx, |buffer, cx| {
1272            buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1273        });
1274        let (inlay_snapshot, _) = inlay_map.sync(
1275            buffer.read(cx).snapshot(cx),
1276            buffer_edits.consume().into_inner(),
1277        );
1278        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1279
1280        // An edit surrounding the inlay should invalidate it.
1281        buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1282        let (inlay_snapshot, _) = inlay_map.sync(
1283            buffer.read(cx).snapshot(cx),
1284            buffer_edits.consume().into_inner(),
1285        );
1286        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1287
1288        let (inlay_snapshot, _) = inlay_map.splice(
1289            Vec::new(),
1290            vec![
1291                Inlay {
1292                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1293                    position: buffer.read(cx).snapshot(cx).anchor_before(3),
1294                    text: "|123|".into(),
1295                },
1296                Inlay {
1297                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1298                    position: buffer.read(cx).snapshot(cx).anchor_after(3),
1299                    text: "|456|".into(),
1300                },
1301            ],
1302        );
1303        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1304
1305        // Edits ending where the inlay starts should not move it if it has a left bias.
1306        buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1307        let (inlay_snapshot, _) = inlay_map.sync(
1308            buffer.read(cx).snapshot(cx),
1309            buffer_edits.consume().into_inner(),
1310        );
1311        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1312
1313        assert_eq!(
1314            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1315            InlayPoint::new(0, 0)
1316        );
1317        assert_eq!(
1318            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1319            InlayPoint::new(0, 0)
1320        );
1321
1322        assert_eq!(
1323            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1324            InlayPoint::new(0, 1)
1325        );
1326        assert_eq!(
1327            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1328            InlayPoint::new(0, 1)
1329        );
1330
1331        assert_eq!(
1332            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1333            InlayPoint::new(0, 2)
1334        );
1335        assert_eq!(
1336            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1337            InlayPoint::new(0, 2)
1338        );
1339
1340        assert_eq!(
1341            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1342            InlayPoint::new(0, 2)
1343        );
1344        assert_eq!(
1345            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1346            InlayPoint::new(0, 8)
1347        );
1348
1349        assert_eq!(
1350            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1351            InlayPoint::new(0, 2)
1352        );
1353        assert_eq!(
1354            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1355            InlayPoint::new(0, 8)
1356        );
1357
1358        assert_eq!(
1359            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1360            InlayPoint::new(0, 2)
1361        );
1362        assert_eq!(
1363            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1364            InlayPoint::new(0, 8)
1365        );
1366
1367        assert_eq!(
1368            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1369            InlayPoint::new(0, 2)
1370        );
1371        assert_eq!(
1372            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1373            InlayPoint::new(0, 8)
1374        );
1375
1376        assert_eq!(
1377            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1378            InlayPoint::new(0, 2)
1379        );
1380        assert_eq!(
1381            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1382            InlayPoint::new(0, 8)
1383        );
1384
1385        assert_eq!(
1386            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1387            InlayPoint::new(0, 8)
1388        );
1389        assert_eq!(
1390            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1391            InlayPoint::new(0, 8)
1392        );
1393
1394        assert_eq!(
1395            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1396            InlayPoint::new(0, 9)
1397        );
1398        assert_eq!(
1399            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1400            InlayPoint::new(0, 9)
1401        );
1402
1403        assert_eq!(
1404            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1405            InlayPoint::new(0, 10)
1406        );
1407        assert_eq!(
1408            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1409            InlayPoint::new(0, 10)
1410        );
1411
1412        assert_eq!(
1413            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1414            InlayPoint::new(0, 11)
1415        );
1416        assert_eq!(
1417            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1418            InlayPoint::new(0, 11)
1419        );
1420
1421        assert_eq!(
1422            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1423            InlayPoint::new(0, 11)
1424        );
1425        assert_eq!(
1426            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1427            InlayPoint::new(0, 17)
1428        );
1429
1430        assert_eq!(
1431            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1432            InlayPoint::new(0, 11)
1433        );
1434        assert_eq!(
1435            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1436            InlayPoint::new(0, 17)
1437        );
1438
1439        assert_eq!(
1440            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1441            InlayPoint::new(0, 11)
1442        );
1443        assert_eq!(
1444            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1445            InlayPoint::new(0, 17)
1446        );
1447
1448        assert_eq!(
1449            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1450            InlayPoint::new(0, 11)
1451        );
1452        assert_eq!(
1453            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1454            InlayPoint::new(0, 17)
1455        );
1456
1457        assert_eq!(
1458            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1459            InlayPoint::new(0, 11)
1460        );
1461        assert_eq!(
1462            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1463            InlayPoint::new(0, 17)
1464        );
1465
1466        assert_eq!(
1467            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1468            InlayPoint::new(0, 17)
1469        );
1470        assert_eq!(
1471            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1472            InlayPoint::new(0, 17)
1473        );
1474
1475        assert_eq!(
1476            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1477            InlayPoint::new(0, 18)
1478        );
1479        assert_eq!(
1480            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1481            InlayPoint::new(0, 18)
1482        );
1483
1484        // The inlays can be manually removed.
1485        let (inlay_snapshot, _) = inlay_map.splice(
1486            inlay_map.inlays.iter().map(|inlay| inlay.id).collect(),
1487            Vec::new(),
1488        );
1489        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1490    }
1491
1492    #[gpui::test]
1493    fn test_inlay_buffer_rows(cx: &mut AppContext) {
1494        let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1495        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1496        assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1497        let mut next_inlay_id = 0;
1498
1499        let (inlay_snapshot, _) = inlay_map.splice(
1500            Vec::new(),
1501            vec![
1502                Inlay {
1503                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1504                    position: buffer.read(cx).snapshot(cx).anchor_before(0),
1505                    text: "|123|\n".into(),
1506                },
1507                Inlay {
1508                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1509                    position: buffer.read(cx).snapshot(cx).anchor_before(4),
1510                    text: "|456|".into(),
1511                },
1512                Inlay {
1513                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1514                    position: buffer.read(cx).snapshot(cx).anchor_before(7),
1515                    text: "\n|567|\n".into(),
1516                },
1517            ],
1518        );
1519        assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1520        assert_eq!(
1521            inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1522            vec![Some(0), None, Some(1), None, None, Some(2)]
1523        );
1524    }
1525
1526    #[gpui::test(iterations = 100)]
1527    fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1528        init_test(cx);
1529
1530        let operations = env::var("OPERATIONS")
1531            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1532            .unwrap_or(10);
1533
1534        let len = rng.gen_range(0..30);
1535        let buffer = if rng.gen() {
1536            let text = util::RandomCharIter::new(&mut rng)
1537                .take(len)
1538                .collect::<String>();
1539            MultiBuffer::build_simple(&text, cx)
1540        } else {
1541            MultiBuffer::build_random(&mut rng, cx)
1542        };
1543        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1544        let mut next_inlay_id = 0;
1545        log::info!("buffer text: {:?}", buffer_snapshot.text());
1546
1547        let mut highlights = TreeMap::default();
1548        let highlight_count = rng.gen_range(0_usize..10);
1549        let mut highlight_ranges = (0..highlight_count)
1550            .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
1551            .collect::<Vec<_>>();
1552        highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1553        log::info!("highlighting ranges {:?}", highlight_ranges);
1554        let highlight_ranges = highlight_ranges
1555            .into_iter()
1556            .map(|range| {
1557                buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end)
1558            })
1559            .collect::<Vec<_>>();
1560
1561        highlights.insert(
1562            Some(TypeId::of::<()>()),
1563            Arc::new((HighlightStyle::default(), highlight_ranges)),
1564        );
1565
1566        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1567        for _ in 0..operations {
1568            let mut inlay_edits = Patch::default();
1569
1570            let mut prev_inlay_text = inlay_snapshot.text();
1571            let mut buffer_edits = Vec::new();
1572            match rng.gen_range(0..=100) {
1573                0..=50 => {
1574                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1575                    log::info!("mutated text: {:?}", snapshot.text());
1576                    inlay_edits = Patch::new(edits);
1577                }
1578                _ => buffer.update(cx, |buffer, cx| {
1579                    let subscription = buffer.subscribe();
1580                    let edit_count = rng.gen_range(1..=5);
1581                    buffer.randomly_mutate(&mut rng, edit_count, cx);
1582                    buffer_snapshot = buffer.snapshot(cx);
1583                    let edits = subscription.consume().into_inner();
1584                    log::info!("editing {:?}", edits);
1585                    buffer_edits.extend(edits);
1586                }),
1587            };
1588
1589            let (new_inlay_snapshot, new_inlay_edits) =
1590                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1591            inlay_snapshot = new_inlay_snapshot;
1592            inlay_edits = inlay_edits.compose(new_inlay_edits);
1593
1594            log::info!("buffer text: {:?}", buffer_snapshot.text());
1595            log::info!("inlay text: {:?}", inlay_snapshot.text());
1596
1597            let inlays = inlay_map
1598                .inlays
1599                .iter()
1600                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1601                .map(|inlay| {
1602                    let offset = inlay.position.to_offset(&buffer_snapshot);
1603                    (offset, inlay.clone())
1604                })
1605                .collect::<Vec<_>>();
1606            let mut expected_text = Rope::from(buffer_snapshot.text());
1607            for (offset, inlay) in inlays.into_iter().rev() {
1608                expected_text.replace(offset..offset, &inlay.text.to_string());
1609            }
1610            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1611
1612            let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1613            assert_eq!(
1614                expected_buffer_rows.len() as u32,
1615                expected_text.max_point().row + 1
1616            );
1617            for row_start in 0..expected_buffer_rows.len() {
1618                assert_eq!(
1619                    inlay_snapshot
1620                        .buffer_rows(row_start as u32)
1621                        .collect::<Vec<_>>(),
1622                    &expected_buffer_rows[row_start..],
1623                    "incorrect buffer rows starting at {}",
1624                    row_start
1625                );
1626            }
1627
1628            for _ in 0..5 {
1629                let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1630                end = expected_text.clip_offset(end, Bias::Right);
1631                let mut start = rng.gen_range(0..=end);
1632                start = expected_text.clip_offset(start, Bias::Right);
1633
1634                let actual_text = inlay_snapshot
1635                    .chunks(
1636                        InlayOffset(start)..InlayOffset(end),
1637                        false,
1638                        Some(&highlights),
1639                        None,
1640                        None,
1641                    )
1642                    .map(|chunk| chunk.text)
1643                    .collect::<String>();
1644                assert_eq!(
1645                    actual_text,
1646                    expected_text.slice(start..end).to_string(),
1647                    "incorrect text in range {:?}",
1648                    start..end
1649                );
1650
1651                assert_eq!(
1652                    inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1653                    expected_text.slice(start..end).summary()
1654                );
1655            }
1656
1657            for edit in inlay_edits {
1658                prev_inlay_text.replace_range(
1659                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1660                    &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1661                );
1662            }
1663            assert_eq!(prev_inlay_text, inlay_snapshot.text());
1664
1665            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1666            assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1667
1668            let mut buffer_point = Point::default();
1669            let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1670            let mut buffer_chars = buffer_snapshot.chars_at(0);
1671            loop {
1672                // Ensure conversion from buffer coordinates to inlay coordinates
1673                // is consistent.
1674                let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
1675                assert_eq!(
1676                    inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
1677                    inlay_point
1678                );
1679
1680                // No matter which bias we clip an inlay point with, it doesn't move
1681                // because it was constructed from a buffer point.
1682                assert_eq!(
1683                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
1684                    inlay_point,
1685                    "invalid inlay point for buffer point {:?} when clipped left",
1686                    buffer_point
1687                );
1688                assert_eq!(
1689                    inlay_snapshot.clip_point(inlay_point, Bias::Right),
1690                    inlay_point,
1691                    "invalid inlay point for buffer point {:?} when clipped right",
1692                    buffer_point
1693                );
1694
1695                if let Some(ch) = buffer_chars.next() {
1696                    if ch == '\n' {
1697                        buffer_point += Point::new(1, 0);
1698                    } else {
1699                        buffer_point += Point::new(0, ch.len_utf8() as u32);
1700                    }
1701
1702                    // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1703                    let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1704                    assert!(new_inlay_point > inlay_point);
1705                    inlay_point = new_inlay_point;
1706                } else {
1707                    break;
1708                }
1709            }
1710
1711            let mut inlay_point = InlayPoint::default();
1712            let mut inlay_offset = InlayOffset::default();
1713            for ch in expected_text.chars() {
1714                assert_eq!(
1715                    inlay_snapshot.to_offset(inlay_point),
1716                    inlay_offset,
1717                    "invalid to_offset({:?})",
1718                    inlay_point
1719                );
1720                assert_eq!(
1721                    inlay_snapshot.to_point(inlay_offset),
1722                    inlay_point,
1723                    "invalid to_point({:?})",
1724                    inlay_offset
1725                );
1726
1727                let mut bytes = [0; 4];
1728                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1729                    inlay_offset.0 += 1;
1730                    if *byte == b'\n' {
1731                        inlay_point.0 += Point::new(1, 0);
1732                    } else {
1733                        inlay_point.0 += Point::new(0, 1);
1734                    }
1735
1736                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1737                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1738                    assert!(
1739                        clipped_left_point <= clipped_right_point,
1740                        "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1741                        inlay_point,
1742                        clipped_left_point,
1743                        clipped_right_point
1744                    );
1745
1746                    // Ensure the clipped points are at valid text locations.
1747                    assert_eq!(
1748                        clipped_left_point.0,
1749                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
1750                    );
1751                    assert_eq!(
1752                        clipped_right_point.0,
1753                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
1754                    );
1755
1756                    // Ensure the clipped points never overshoot the end of the map.
1757                    assert!(clipped_left_point <= inlay_snapshot.max_point());
1758                    assert!(clipped_right_point <= inlay_snapshot.max_point());
1759
1760                    // Ensure the clipped points are at valid buffer locations.
1761                    assert_eq!(
1762                        inlay_snapshot
1763                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1764                        clipped_left_point,
1765                        "to_buffer_point({:?}) = {:?}",
1766                        clipped_left_point,
1767                        inlay_snapshot.to_buffer_point(clipped_left_point),
1768                    );
1769                    assert_eq!(
1770                        inlay_snapshot
1771                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1772                        clipped_right_point,
1773                        "to_buffer_point({:?}) = {:?}",
1774                        clipped_right_point,
1775                        inlay_snapshot.to_buffer_point(clipped_right_point),
1776                    );
1777                }
1778            }
1779        }
1780    }
1781
1782    fn init_test(cx: &mut AppContext) {
1783        cx.set_global(SettingsStore::test(cx));
1784        theme::init((), cx);
1785    }
1786}