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 mut 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        drop(snapshot);
 576        let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
 577        (snapshot, edits)
 578    }
 579
 580    pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
 581        self.inlays.iter()
 582    }
 583
 584    #[cfg(test)]
 585    pub(crate) fn randomly_mutate(
 586        &mut self,
 587        next_inlay_id: &mut usize,
 588        rng: &mut rand::rngs::StdRng,
 589    ) -> (InlaySnapshot, Vec<InlayEdit>) {
 590        use rand::prelude::*;
 591        use util::post_inc;
 592
 593        let mut to_remove = Vec::new();
 594        let mut to_insert = Vec::new();
 595        let snapshot = &mut self.snapshot;
 596        for i in 0..rng.gen_range(1..=5) {
 597            if self.inlays.is_empty() || rng.gen() {
 598                let position = snapshot.buffer.random_byte_range(0, rng).start;
 599                let bias = if rng.gen() { Bias::Left } else { Bias::Right };
 600                let len = if rng.gen_bool(0.01) {
 601                    0
 602                } else {
 603                    rng.gen_range(1..=5)
 604                };
 605                let text = util::RandomCharIter::new(&mut *rng)
 606                    .filter(|ch| *ch != '\r')
 607                    .take(len)
 608                    .collect::<String>();
 609                log::info!(
 610                    "creating inlay at buffer offset {} with bias {:?} and text {:?}",
 611                    position,
 612                    bias,
 613                    text
 614                );
 615
 616                let inlay_id = if i % 2 == 0 {
 617                    InlayId::Hint(post_inc(next_inlay_id))
 618                } else {
 619                    InlayId::Suggestion(post_inc(next_inlay_id))
 620                };
 621                to_insert.push(Inlay {
 622                    id: inlay_id,
 623                    position: snapshot.buffer.anchor_at(position, bias),
 624                    text: text.into(),
 625                });
 626            } else {
 627                to_remove.push(
 628                    self.inlays
 629                        .iter()
 630                        .choose(rng)
 631                        .map(|inlay| inlay.id)
 632                        .unwrap(),
 633                );
 634            }
 635        }
 636        log::info!("removing inlays: {:?}", to_remove);
 637
 638        drop(snapshot);
 639        let (snapshot, edits) = self.splice(to_remove, to_insert);
 640        (snapshot, edits)
 641    }
 642}
 643
 644impl InlaySnapshot {
 645    pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
 646        let mut cursor = self
 647            .transforms
 648            .cursor::<(InlayOffset, (InlayPoint, usize))>();
 649        cursor.seek(&offset, Bias::Right, &());
 650        let overshoot = offset.0 - cursor.start().0 .0;
 651        match cursor.item() {
 652            Some(Transform::Isomorphic(_)) => {
 653                let buffer_offset_start = cursor.start().1 .1;
 654                let buffer_offset_end = buffer_offset_start + overshoot;
 655                let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
 656                let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
 657                InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start))
 658            }
 659            Some(Transform::Inlay(inlay)) => {
 660                let overshoot = inlay.text.offset_to_point(overshoot);
 661                InlayPoint(cursor.start().1 .0 .0 + overshoot)
 662            }
 663            None => self.max_point(),
 664        }
 665    }
 666
 667    pub fn len(&self) -> InlayOffset {
 668        InlayOffset(self.transforms.summary().output.len)
 669    }
 670
 671    pub fn max_point(&self) -> InlayPoint {
 672        InlayPoint(self.transforms.summary().output.lines)
 673    }
 674
 675    pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
 676        let mut cursor = self
 677            .transforms
 678            .cursor::<(InlayPoint, (InlayOffset, Point))>();
 679        cursor.seek(&point, Bias::Right, &());
 680        let overshoot = point.0 - cursor.start().0 .0;
 681        match cursor.item() {
 682            Some(Transform::Isomorphic(_)) => {
 683                let buffer_point_start = cursor.start().1 .1;
 684                let buffer_point_end = buffer_point_start + overshoot;
 685                let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
 686                let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
 687                InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start))
 688            }
 689            Some(Transform::Inlay(inlay)) => {
 690                let overshoot = inlay.text.point_to_offset(overshoot);
 691                InlayOffset(cursor.start().1 .0 .0 + overshoot)
 692            }
 693            None => self.len(),
 694        }
 695    }
 696
 697    pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
 698        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
 699        cursor.seek(&point, Bias::Right, &());
 700        match cursor.item() {
 701            Some(Transform::Isomorphic(_)) => {
 702                let overshoot = point.0 - cursor.start().0 .0;
 703                cursor.start().1 + overshoot
 704            }
 705            Some(Transform::Inlay(_)) => cursor.start().1,
 706            None => self.buffer.max_point(),
 707        }
 708    }
 709
 710    pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
 711        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
 712        cursor.seek(&offset, Bias::Right, &());
 713        match cursor.item() {
 714            Some(Transform::Isomorphic(_)) => {
 715                let overshoot = offset - cursor.start().0;
 716                cursor.start().1 + overshoot.0
 717            }
 718            Some(Transform::Inlay(_)) => cursor.start().1,
 719            None => self.buffer.len(),
 720        }
 721    }
 722
 723    pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
 724        let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>();
 725        cursor.seek(&offset, Bias::Left, &());
 726        loop {
 727            match cursor.item() {
 728                Some(Transform::Isomorphic(_)) => {
 729                    if offset == cursor.end(&()).0 {
 730                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 731                            if inlay.position.bias() == Bias::Right {
 732                                break;
 733                            } else {
 734                                cursor.next(&());
 735                            }
 736                        }
 737                        return cursor.end(&()).1;
 738                    } else {
 739                        let overshoot = offset - cursor.start().0;
 740                        return InlayOffset(cursor.start().1 .0 + overshoot);
 741                    }
 742                }
 743                Some(Transform::Inlay(inlay)) => {
 744                    if inlay.position.bias() == Bias::Left {
 745                        cursor.next(&());
 746                    } else {
 747                        return cursor.start().1;
 748                    }
 749                }
 750                None => {
 751                    return self.len();
 752                }
 753            }
 754        }
 755    }
 756
 757    pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
 758        let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
 759        cursor.seek(&point, Bias::Left, &());
 760        loop {
 761            match cursor.item() {
 762                Some(Transform::Isomorphic(_)) => {
 763                    if point == cursor.end(&()).0 {
 764                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 765                            if inlay.position.bias() == Bias::Right {
 766                                break;
 767                            } else {
 768                                cursor.next(&());
 769                            }
 770                        }
 771                        return cursor.end(&()).1;
 772                    } else {
 773                        let overshoot = point - cursor.start().0;
 774                        return InlayPoint(cursor.start().1 .0 + overshoot);
 775                    }
 776                }
 777                Some(Transform::Inlay(inlay)) => {
 778                    if inlay.position.bias() == Bias::Left {
 779                        cursor.next(&());
 780                    } else {
 781                        return cursor.start().1;
 782                    }
 783                }
 784                None => {
 785                    return self.max_point();
 786                }
 787            }
 788        }
 789    }
 790
 791    pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
 792        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
 793        cursor.seek(&point, Bias::Left, &());
 794        loop {
 795            match cursor.item() {
 796                Some(Transform::Isomorphic(transform)) => {
 797                    if cursor.start().0 == point {
 798                        if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
 799                            if inlay.position.bias() == Bias::Left {
 800                                return point;
 801                            } else if bias == Bias::Left {
 802                                cursor.prev(&());
 803                            } else if transform.first_line_chars == 0 {
 804                                point.0 += Point::new(1, 0);
 805                            } else {
 806                                point.0 += Point::new(0, 1);
 807                            }
 808                        } else {
 809                            return point;
 810                        }
 811                    } else if cursor.end(&()).0 == point {
 812                        if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
 813                            if inlay.position.bias() == Bias::Right {
 814                                return point;
 815                            } else if bias == Bias::Right {
 816                                cursor.next(&());
 817                            } else if point.0.column == 0 {
 818                                point.0.row -= 1;
 819                                point.0.column = self.line_len(point.0.row);
 820                            } else {
 821                                point.0.column -= 1;
 822                            }
 823                        } else {
 824                            return point;
 825                        }
 826                    } else {
 827                        let overshoot = point.0 - cursor.start().0 .0;
 828                        let buffer_point = cursor.start().1 + overshoot;
 829                        let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
 830                        let clipped_overshoot = clipped_buffer_point - cursor.start().1;
 831                        let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot);
 832                        if clipped_point == point {
 833                            return clipped_point;
 834                        } else {
 835                            point = clipped_point;
 836                        }
 837                    }
 838                }
 839                Some(Transform::Inlay(inlay)) => {
 840                    if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
 841                        match cursor.prev_item() {
 842                            Some(Transform::Inlay(inlay)) => {
 843                                if inlay.position.bias() == Bias::Left {
 844                                    return point;
 845                                }
 846                            }
 847                            _ => return point,
 848                        }
 849                    } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left {
 850                        match cursor.next_item() {
 851                            Some(Transform::Inlay(inlay)) => {
 852                                if inlay.position.bias() == Bias::Right {
 853                                    return point;
 854                                }
 855                            }
 856                            _ => return point,
 857                        }
 858                    }
 859
 860                    if bias == Bias::Left {
 861                        point = cursor.start().0;
 862                        cursor.prev(&());
 863                    } else {
 864                        cursor.next(&());
 865                        point = cursor.start().0;
 866                    }
 867                }
 868                None => {
 869                    bias = bias.invert();
 870                    if bias == Bias::Left {
 871                        point = cursor.start().0;
 872                        cursor.prev(&());
 873                    } else {
 874                        cursor.next(&());
 875                        point = cursor.start().0;
 876                    }
 877                }
 878            }
 879        }
 880    }
 881
 882    pub fn text_summary(&self) -> TextSummary {
 883        self.transforms.summary().output.clone()
 884    }
 885
 886    pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
 887        let mut summary = TextSummary::default();
 888
 889        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
 890        cursor.seek(&range.start, Bias::Right, &());
 891
 892        let overshoot = range.start.0 - cursor.start().0 .0;
 893        match cursor.item() {
 894            Some(Transform::Isomorphic(_)) => {
 895                let buffer_start = cursor.start().1;
 896                let suffix_start = buffer_start + overshoot;
 897                let suffix_end =
 898                    buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
 899                summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
 900                cursor.next(&());
 901            }
 902            Some(Transform::Inlay(inlay)) => {
 903                let suffix_start = overshoot;
 904                let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0;
 905                summary = inlay.text.cursor(suffix_start).summary(suffix_end);
 906                cursor.next(&());
 907            }
 908            None => {}
 909        }
 910
 911        if range.end > cursor.start().0 {
 912            summary += cursor
 913                .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
 914                .output;
 915
 916            let overshoot = range.end.0 - cursor.start().0 .0;
 917            match cursor.item() {
 918                Some(Transform::Isomorphic(_)) => {
 919                    let prefix_start = cursor.start().1;
 920                    let prefix_end = prefix_start + overshoot;
 921                    summary += self
 922                        .buffer
 923                        .text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
 924                }
 925                Some(Transform::Inlay(inlay)) => {
 926                    let prefix_end = overshoot;
 927                    summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
 928                }
 929                None => {}
 930            }
 931        }
 932
 933        summary
 934    }
 935
 936    pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> {
 937        let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
 938        let inlay_point = InlayPoint::new(row, 0);
 939        cursor.seek(&inlay_point, Bias::Left, &());
 940
 941        let max_buffer_row = self.buffer.max_point().row;
 942        let mut buffer_point = cursor.start().1;
 943        let buffer_row = if row == 0 {
 944            0
 945        } else {
 946            match cursor.item() {
 947                Some(Transform::Isomorphic(_)) => {
 948                    buffer_point += inlay_point.0 - cursor.start().0 .0;
 949                    buffer_point.row
 950                }
 951                _ => cmp::min(buffer_point.row + 1, max_buffer_row),
 952            }
 953        };
 954
 955        InlayBufferRows {
 956            transforms: cursor,
 957            inlay_row: inlay_point.row(),
 958            buffer_rows: self.buffer.buffer_rows(buffer_row),
 959            max_buffer_row,
 960        }
 961    }
 962
 963    pub fn line_len(&self, row: u32) -> u32 {
 964        let line_start = self.to_offset(InlayPoint::new(row, 0)).0;
 965        let line_end = if row >= self.max_point().row() {
 966            self.len().0
 967        } else {
 968            self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1
 969        };
 970        (line_end - line_start) as u32
 971    }
 972
 973    pub fn chunks<'a>(
 974        &'a self,
 975        range: Range<InlayOffset>,
 976        language_aware: bool,
 977        text_highlights: Option<&'a TextHighlights>,
 978        hint_highlights: Option<HighlightStyle>,
 979        suggestion_highlights: Option<HighlightStyle>,
 980    ) -> InlayChunks<'a> {
 981        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
 982        cursor.seek(&range.start, Bias::Right, &());
 983
 984        let mut highlight_endpoints = Vec::new();
 985        if let Some(text_highlights) = text_highlights {
 986            if !text_highlights.is_empty() {
 987                while cursor.start().0 < range.end {
 988                    if true {
 989                        let transform_start = self.buffer.anchor_after(
 990                            self.to_buffer_offset(cmp::max(range.start, cursor.start().0)),
 991                        );
 992
 993                        let transform_end = {
 994                            let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
 995                            self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
 996                                cursor.end(&()).0,
 997                                cursor.start().0 + overshoot,
 998                            )))
 999                        };
1000
1001                        for (tag, highlights) in text_highlights.iter() {
1002                            let style = highlights.0;
1003                            let ranges = &highlights.1;
1004
1005                            let start_ix = match ranges.binary_search_by(|probe| {
1006                                let cmp = probe.end.cmp(&transform_start, &self.buffer);
1007                                if cmp.is_gt() {
1008                                    cmp::Ordering::Greater
1009                                } else {
1010                                    cmp::Ordering::Less
1011                                }
1012                            }) {
1013                                Ok(i) | Err(i) => i,
1014                            };
1015                            for range in &ranges[start_ix..] {
1016                                if range.start.cmp(&transform_end, &self.buffer).is_ge() {
1017                                    break;
1018                                }
1019
1020                                highlight_endpoints.push(HighlightEndpoint {
1021                                    offset: self
1022                                        .to_inlay_offset(range.start.to_offset(&self.buffer)),
1023                                    is_start: true,
1024                                    tag: *tag,
1025                                    style,
1026                                });
1027                                highlight_endpoints.push(HighlightEndpoint {
1028                                    offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
1029                                    is_start: false,
1030                                    tag: *tag,
1031                                    style,
1032                                });
1033                            }
1034                        }
1035                    }
1036
1037                    cursor.next(&());
1038                }
1039                highlight_endpoints.sort();
1040                cursor.seek(&range.start, Bias::Right, &());
1041            }
1042        }
1043
1044        let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
1045        let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
1046
1047        InlayChunks {
1048            transforms: cursor,
1049            buffer_chunks,
1050            inlay_chunks: None,
1051            buffer_chunk: None,
1052            output_offset: range.start,
1053            max_output_offset: range.end,
1054            hint_highlight_style: hint_highlights,
1055            suggestion_highlight_style: suggestion_highlights,
1056            highlight_endpoints: highlight_endpoints.into_iter().peekable(),
1057            active_highlights: Default::default(),
1058            snapshot: self,
1059        }
1060    }
1061
1062    #[cfg(test)]
1063    pub fn text(&self) -> String {
1064        self.chunks(Default::default()..self.len(), false, None, None, None)
1065            .map(|chunk| chunk.text)
1066            .collect()
1067    }
1068
1069    fn check_invariants(&self) {
1070        #[cfg(any(debug_assertions, feature = "test-support"))]
1071        {
1072            assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1073            let mut transforms = self.transforms.iter().peekable();
1074            while let Some(transform) = transforms.next() {
1075                let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1076                if let Some(next_transform) = transforms.peek() {
1077                    let next_transform_is_isomorphic =
1078                        matches!(next_transform, Transform::Isomorphic(_));
1079                    assert!(
1080                        !transform_is_isomorphic || !next_transform_is_isomorphic,
1081                        "two adjacent isomorphic transforms"
1082                    );
1083                }
1084            }
1085        }
1086    }
1087}
1088
1089fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
1090    if summary.len == 0 {
1091        return;
1092    }
1093
1094    let mut summary = Some(summary);
1095    sum_tree.update_last(
1096        |transform| {
1097            if let Transform::Isomorphic(transform) = transform {
1098                *transform += summary.take().unwrap();
1099            }
1100        },
1101        &(),
1102    );
1103
1104    if let Some(summary) = summary {
1105        sum_tree.push(Transform::Isomorphic(summary), &());
1106    }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    use super::*;
1112    use crate::{InlayId, MultiBuffer};
1113    use gpui::AppContext;
1114    use project::{InlayHint, InlayHintLabel};
1115    use rand::prelude::*;
1116    use settings::SettingsStore;
1117    use std::{cmp::Reverse, env, sync::Arc};
1118    use sum_tree::TreeMap;
1119    use text::Patch;
1120    use util::post_inc;
1121
1122    #[test]
1123    fn test_inlay_properties_label_padding() {
1124        assert_eq!(
1125            Inlay::hint(
1126                0,
1127                Anchor::min(),
1128                &InlayHint {
1129                    label: InlayHintLabel::String("a".to_string()),
1130                    buffer_id: 0,
1131                    position: text::Anchor::default(),
1132                    padding_left: false,
1133                    padding_right: false,
1134                    tooltip: None,
1135                    kind: None,
1136                },
1137            )
1138            .text
1139            .to_string(),
1140            "a",
1141            "Should not pad label if not requested"
1142        );
1143
1144        assert_eq!(
1145            Inlay::hint(
1146                0,
1147                Anchor::min(),
1148                &InlayHint {
1149                    label: InlayHintLabel::String("a".to_string()),
1150                    buffer_id: 0,
1151                    position: text::Anchor::default(),
1152                    padding_left: true,
1153                    padding_right: true,
1154                    tooltip: None,
1155                    kind: None,
1156                },
1157            )
1158            .text
1159            .to_string(),
1160            " a ",
1161            "Should pad label for every side requested"
1162        );
1163
1164        assert_eq!(
1165            Inlay::hint(
1166                0,
1167                Anchor::min(),
1168                &InlayHint {
1169                    label: InlayHintLabel::String(" a ".to_string()),
1170                    buffer_id: 0,
1171                    position: text::Anchor::default(),
1172                    padding_left: false,
1173                    padding_right: false,
1174                    tooltip: None,
1175                    kind: None,
1176                },
1177            )
1178            .text
1179            .to_string(),
1180            " a ",
1181            "Should not change already padded label"
1182        );
1183
1184        assert_eq!(
1185            Inlay::hint(
1186                0,
1187                Anchor::min(),
1188                &InlayHint {
1189                    label: InlayHintLabel::String(" a ".to_string()),
1190                    buffer_id: 0,
1191                    position: text::Anchor::default(),
1192                    padding_left: true,
1193                    padding_right: true,
1194                    tooltip: None,
1195                    kind: None,
1196                },
1197            )
1198            .text
1199            .to_string(),
1200            " a ",
1201            "Should not change already padded label"
1202        );
1203    }
1204
1205    #[gpui::test]
1206    fn test_basic_inlays(cx: &mut AppContext) {
1207        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1208        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1209        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1210        assert_eq!(inlay_snapshot.text(), "abcdefghi");
1211        let mut next_inlay_id = 0;
1212
1213        let (inlay_snapshot, _) = inlay_map.splice(
1214            Vec::new(),
1215            vec![Inlay {
1216                id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1217                position: buffer.read(cx).snapshot(cx).anchor_after(3),
1218                text: "|123|".into(),
1219            }],
1220        );
1221        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1222        assert_eq!(
1223            inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1224            InlayPoint::new(0, 0)
1225        );
1226        assert_eq!(
1227            inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1228            InlayPoint::new(0, 1)
1229        );
1230        assert_eq!(
1231            inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1232            InlayPoint::new(0, 2)
1233        );
1234        assert_eq!(
1235            inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1236            InlayPoint::new(0, 3)
1237        );
1238        assert_eq!(
1239            inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1240            InlayPoint::new(0, 9)
1241        );
1242        assert_eq!(
1243            inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1244            InlayPoint::new(0, 10)
1245        );
1246        assert_eq!(
1247            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1248            InlayPoint::new(0, 0)
1249        );
1250        assert_eq!(
1251            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1252            InlayPoint::new(0, 0)
1253        );
1254        assert_eq!(
1255            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1256            InlayPoint::new(0, 3)
1257        );
1258        assert_eq!(
1259            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1260            InlayPoint::new(0, 3)
1261        );
1262        assert_eq!(
1263            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1264            InlayPoint::new(0, 3)
1265        );
1266        assert_eq!(
1267            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1268            InlayPoint::new(0, 9)
1269        );
1270
1271        // Edits before or after the inlay should not affect it.
1272        buffer.update(cx, |buffer, cx| {
1273            buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1274        });
1275        let (inlay_snapshot, _) = inlay_map.sync(
1276            buffer.read(cx).snapshot(cx),
1277            buffer_edits.consume().into_inner(),
1278        );
1279        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1280
1281        // An edit surrounding the inlay should invalidate it.
1282        buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1283        let (inlay_snapshot, _) = inlay_map.sync(
1284            buffer.read(cx).snapshot(cx),
1285            buffer_edits.consume().into_inner(),
1286        );
1287        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1288
1289        let (inlay_snapshot, _) = inlay_map.splice(
1290            Vec::new(),
1291            vec![
1292                Inlay {
1293                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1294                    position: buffer.read(cx).snapshot(cx).anchor_before(3),
1295                    text: "|123|".into(),
1296                },
1297                Inlay {
1298                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1299                    position: buffer.read(cx).snapshot(cx).anchor_after(3),
1300                    text: "|456|".into(),
1301                },
1302            ],
1303        );
1304        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1305
1306        // Edits ending where the inlay starts should not move it if it has a left bias.
1307        buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1308        let (inlay_snapshot, _) = inlay_map.sync(
1309            buffer.read(cx).snapshot(cx),
1310            buffer_edits.consume().into_inner(),
1311        );
1312        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1313
1314        assert_eq!(
1315            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1316            InlayPoint::new(0, 0)
1317        );
1318        assert_eq!(
1319            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1320            InlayPoint::new(0, 0)
1321        );
1322
1323        assert_eq!(
1324            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1325            InlayPoint::new(0, 1)
1326        );
1327        assert_eq!(
1328            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1329            InlayPoint::new(0, 1)
1330        );
1331
1332        assert_eq!(
1333            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1334            InlayPoint::new(0, 2)
1335        );
1336        assert_eq!(
1337            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1338            InlayPoint::new(0, 2)
1339        );
1340
1341        assert_eq!(
1342            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1343            InlayPoint::new(0, 2)
1344        );
1345        assert_eq!(
1346            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1347            InlayPoint::new(0, 8)
1348        );
1349
1350        assert_eq!(
1351            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1352            InlayPoint::new(0, 2)
1353        );
1354        assert_eq!(
1355            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1356            InlayPoint::new(0, 8)
1357        );
1358
1359        assert_eq!(
1360            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1361            InlayPoint::new(0, 2)
1362        );
1363        assert_eq!(
1364            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1365            InlayPoint::new(0, 8)
1366        );
1367
1368        assert_eq!(
1369            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1370            InlayPoint::new(0, 2)
1371        );
1372        assert_eq!(
1373            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1374            InlayPoint::new(0, 8)
1375        );
1376
1377        assert_eq!(
1378            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1379            InlayPoint::new(0, 2)
1380        );
1381        assert_eq!(
1382            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1383            InlayPoint::new(0, 8)
1384        );
1385
1386        assert_eq!(
1387            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1388            InlayPoint::new(0, 8)
1389        );
1390        assert_eq!(
1391            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1392            InlayPoint::new(0, 8)
1393        );
1394
1395        assert_eq!(
1396            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1397            InlayPoint::new(0, 9)
1398        );
1399        assert_eq!(
1400            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1401            InlayPoint::new(0, 9)
1402        );
1403
1404        assert_eq!(
1405            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1406            InlayPoint::new(0, 10)
1407        );
1408        assert_eq!(
1409            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1410            InlayPoint::new(0, 10)
1411        );
1412
1413        assert_eq!(
1414            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1415            InlayPoint::new(0, 11)
1416        );
1417        assert_eq!(
1418            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1419            InlayPoint::new(0, 11)
1420        );
1421
1422        assert_eq!(
1423            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1424            InlayPoint::new(0, 11)
1425        );
1426        assert_eq!(
1427            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1428            InlayPoint::new(0, 17)
1429        );
1430
1431        assert_eq!(
1432            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1433            InlayPoint::new(0, 11)
1434        );
1435        assert_eq!(
1436            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1437            InlayPoint::new(0, 17)
1438        );
1439
1440        assert_eq!(
1441            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1442            InlayPoint::new(0, 11)
1443        );
1444        assert_eq!(
1445            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1446            InlayPoint::new(0, 17)
1447        );
1448
1449        assert_eq!(
1450            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1451            InlayPoint::new(0, 11)
1452        );
1453        assert_eq!(
1454            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1455            InlayPoint::new(0, 17)
1456        );
1457
1458        assert_eq!(
1459            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1460            InlayPoint::new(0, 11)
1461        );
1462        assert_eq!(
1463            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1464            InlayPoint::new(0, 17)
1465        );
1466
1467        assert_eq!(
1468            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1469            InlayPoint::new(0, 17)
1470        );
1471        assert_eq!(
1472            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1473            InlayPoint::new(0, 17)
1474        );
1475
1476        assert_eq!(
1477            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1478            InlayPoint::new(0, 18)
1479        );
1480        assert_eq!(
1481            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1482            InlayPoint::new(0, 18)
1483        );
1484
1485        // The inlays can be manually removed.
1486        let (inlay_snapshot, _) = inlay_map.splice(
1487            inlay_map.inlays.iter().map(|inlay| inlay.id).collect(),
1488            Vec::new(),
1489        );
1490        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1491    }
1492
1493    #[gpui::test]
1494    fn test_inlay_buffer_rows(cx: &mut AppContext) {
1495        let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1496        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1497        assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1498        let mut next_inlay_id = 0;
1499
1500        let (inlay_snapshot, _) = inlay_map.splice(
1501            Vec::new(),
1502            vec![
1503                Inlay {
1504                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1505                    position: buffer.read(cx).snapshot(cx).anchor_before(0),
1506                    text: "|123|\n".into(),
1507                },
1508                Inlay {
1509                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1510                    position: buffer.read(cx).snapshot(cx).anchor_before(4),
1511                    text: "|456|".into(),
1512                },
1513                Inlay {
1514                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1515                    position: buffer.read(cx).snapshot(cx).anchor_before(7),
1516                    text: "\n|567|\n".into(),
1517                },
1518            ],
1519        );
1520        assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1521        assert_eq!(
1522            inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1523            vec![Some(0), None, Some(1), None, None, Some(2)]
1524        );
1525    }
1526
1527    #[gpui::test(iterations = 100)]
1528    fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1529        init_test(cx);
1530
1531        let operations = env::var("OPERATIONS")
1532            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1533            .unwrap_or(10);
1534
1535        let len = rng.gen_range(0..30);
1536        let buffer = if rng.gen() {
1537            let text = util::RandomCharIter::new(&mut rng)
1538                .take(len)
1539                .collect::<String>();
1540            MultiBuffer::build_simple(&text, cx)
1541        } else {
1542            MultiBuffer::build_random(&mut rng, cx)
1543        };
1544        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1545        let mut next_inlay_id = 0;
1546        log::info!("buffer text: {:?}", buffer_snapshot.text());
1547
1548        let mut highlights = TreeMap::default();
1549        let highlight_count = rng.gen_range(0_usize..10);
1550        let mut highlight_ranges = (0..highlight_count)
1551            .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
1552            .collect::<Vec<_>>();
1553        highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1554        log::info!("highlighting ranges {:?}", highlight_ranges);
1555        let highlight_ranges = highlight_ranges
1556            .into_iter()
1557            .map(|range| {
1558                buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end)
1559            })
1560            .collect::<Vec<_>>();
1561
1562        highlights.insert(
1563            Some(TypeId::of::<()>()),
1564            Arc::new((HighlightStyle::default(), highlight_ranges)),
1565        );
1566
1567        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1568        for _ in 0..operations {
1569            let mut inlay_edits = Patch::default();
1570
1571            let mut prev_inlay_text = inlay_snapshot.text();
1572            let mut buffer_edits = Vec::new();
1573            match rng.gen_range(0..=100) {
1574                0..=50 => {
1575                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1576                    log::info!("mutated text: {:?}", snapshot.text());
1577                    inlay_edits = Patch::new(edits);
1578                }
1579                _ => buffer.update(cx, |buffer, cx| {
1580                    let subscription = buffer.subscribe();
1581                    let edit_count = rng.gen_range(1..=5);
1582                    buffer.randomly_mutate(&mut rng, edit_count, cx);
1583                    buffer_snapshot = buffer.snapshot(cx);
1584                    let edits = subscription.consume().into_inner();
1585                    log::info!("editing {:?}", edits);
1586                    buffer_edits.extend(edits);
1587                }),
1588            };
1589
1590            let (new_inlay_snapshot, new_inlay_edits) =
1591                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1592            inlay_snapshot = new_inlay_snapshot;
1593            inlay_edits = inlay_edits.compose(new_inlay_edits);
1594
1595            log::info!("buffer text: {:?}", buffer_snapshot.text());
1596            log::info!("inlay text: {:?}", inlay_snapshot.text());
1597
1598            let inlays = inlay_map
1599                .inlays
1600                .iter()
1601                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1602                .map(|inlay| {
1603                    let offset = inlay.position.to_offset(&buffer_snapshot);
1604                    (offset, inlay.clone())
1605                })
1606                .collect::<Vec<_>>();
1607            let mut expected_text = Rope::from(buffer_snapshot.text());
1608            for (offset, inlay) in inlays.into_iter().rev() {
1609                expected_text.replace(offset..offset, &inlay.text.to_string());
1610            }
1611            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1612
1613            let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1614            assert_eq!(
1615                expected_buffer_rows.len() as u32,
1616                expected_text.max_point().row + 1
1617            );
1618            for row_start in 0..expected_buffer_rows.len() {
1619                assert_eq!(
1620                    inlay_snapshot
1621                        .buffer_rows(row_start as u32)
1622                        .collect::<Vec<_>>(),
1623                    &expected_buffer_rows[row_start..],
1624                    "incorrect buffer rows starting at {}",
1625                    row_start
1626                );
1627            }
1628
1629            for _ in 0..5 {
1630                let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1631                end = expected_text.clip_offset(end, Bias::Right);
1632                let mut start = rng.gen_range(0..=end);
1633                start = expected_text.clip_offset(start, Bias::Right);
1634
1635                let actual_text = inlay_snapshot
1636                    .chunks(
1637                        InlayOffset(start)..InlayOffset(end),
1638                        false,
1639                        Some(&highlights),
1640                        None,
1641                        None,
1642                    )
1643                    .map(|chunk| chunk.text)
1644                    .collect::<String>();
1645                assert_eq!(
1646                    actual_text,
1647                    expected_text.slice(start..end).to_string(),
1648                    "incorrect text in range {:?}",
1649                    start..end
1650                );
1651
1652                assert_eq!(
1653                    inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1654                    expected_text.slice(start..end).summary()
1655                );
1656            }
1657
1658            for edit in inlay_edits {
1659                prev_inlay_text.replace_range(
1660                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1661                    &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1662                );
1663            }
1664            assert_eq!(prev_inlay_text, inlay_snapshot.text());
1665
1666            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1667            assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1668
1669            let mut buffer_point = Point::default();
1670            let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1671            let mut buffer_chars = buffer_snapshot.chars_at(0);
1672            loop {
1673                // Ensure conversion from buffer coordinates to inlay coordinates
1674                // is consistent.
1675                let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
1676                assert_eq!(
1677                    inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
1678                    inlay_point
1679                );
1680
1681                // No matter which bias we clip an inlay point with, it doesn't move
1682                // because it was constructed from a buffer point.
1683                assert_eq!(
1684                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
1685                    inlay_point,
1686                    "invalid inlay point for buffer point {:?} when clipped left",
1687                    buffer_point
1688                );
1689                assert_eq!(
1690                    inlay_snapshot.clip_point(inlay_point, Bias::Right),
1691                    inlay_point,
1692                    "invalid inlay point for buffer point {:?} when clipped right",
1693                    buffer_point
1694                );
1695
1696                if let Some(ch) = buffer_chars.next() {
1697                    if ch == '\n' {
1698                        buffer_point += Point::new(1, 0);
1699                    } else {
1700                        buffer_point += Point::new(0, ch.len_utf8() as u32);
1701                    }
1702
1703                    // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1704                    let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1705                    assert!(new_inlay_point > inlay_point);
1706                    inlay_point = new_inlay_point;
1707                } else {
1708                    break;
1709                }
1710            }
1711
1712            let mut inlay_point = InlayPoint::default();
1713            let mut inlay_offset = InlayOffset::default();
1714            for ch in expected_text.chars() {
1715                assert_eq!(
1716                    inlay_snapshot.to_offset(inlay_point),
1717                    inlay_offset,
1718                    "invalid to_offset({:?})",
1719                    inlay_point
1720                );
1721                assert_eq!(
1722                    inlay_snapshot.to_point(inlay_offset),
1723                    inlay_point,
1724                    "invalid to_point({:?})",
1725                    inlay_offset
1726                );
1727
1728                let mut bytes = [0; 4];
1729                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1730                    inlay_offset.0 += 1;
1731                    if *byte == b'\n' {
1732                        inlay_point.0 += Point::new(1, 0);
1733                    } else {
1734                        inlay_point.0 += Point::new(0, 1);
1735                    }
1736
1737                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1738                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1739                    assert!(
1740                        clipped_left_point <= clipped_right_point,
1741                        "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1742                        inlay_point,
1743                        clipped_left_point,
1744                        clipped_right_point
1745                    );
1746
1747                    // Ensure the clipped points are at valid text locations.
1748                    assert_eq!(
1749                        clipped_left_point.0,
1750                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
1751                    );
1752                    assert_eq!(
1753                        clipped_right_point.0,
1754                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
1755                    );
1756
1757                    // Ensure the clipped points never overshoot the end of the map.
1758                    assert!(clipped_left_point <= inlay_snapshot.max_point());
1759                    assert!(clipped_right_point <= inlay_snapshot.max_point());
1760
1761                    // Ensure the clipped points are at valid buffer locations.
1762                    assert_eq!(
1763                        inlay_snapshot
1764                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1765                        clipped_left_point,
1766                        "to_buffer_point({:?}) = {:?}",
1767                        clipped_left_point,
1768                        inlay_snapshot.to_buffer_point(clipped_left_point),
1769                    );
1770                    assert_eq!(
1771                        inlay_snapshot
1772                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1773                        clipped_right_point,
1774                        "to_buffer_point({:?}) = {:?}",
1775                        clipped_right_point,
1776                        inlay_snapshot.to_buffer_point(clipped_right_point),
1777                    );
1778                }
1779            }
1780        }
1781    }
1782
1783    fn init_test(cx: &mut AppContext) {
1784        cx.set_global(SettingsStore::test(cx));
1785        theme::init((), cx);
1786    }
1787}