inlay_map.rs

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