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