inlay_map.rs

   1use crate::{
   2    multi_buffer::{MultiBufferChunks, MultiBufferRows},
   3    Anchor, InlayId, MultiBufferSnapshot, ToOffset,
   4};
   5use collections::{BTreeMap, BTreeSet, HashSet};
   6use gpui::fonts::HighlightStyle;
   7use language::{Chunk, Edit, Point, TextSummary};
   8use std::{
   9    any::TypeId,
  10    cmp,
  11    iter::Peekable,
  12    ops::{Add, AddAssign, Range, Sub, SubAssign},
  13    vec,
  14};
  15use sum_tree::{Bias, Cursor, SumTree};
  16use text::{Patch, Rope};
  17
  18use super::{InlayHighlights, TextHighlights};
  19
  20pub struct InlayMap {
  21    snapshot: InlaySnapshot,
  22    inlays: Vec<Inlay>,
  23}
  24
  25#[derive(Clone)]
  26pub struct InlaySnapshot {
  27    pub buffer: MultiBufferSnapshot,
  28    transforms: SumTree<Transform>,
  29    pub version: usize,
  30}
  31
  32#[derive(Clone, Debug)]
  33enum Transform {
  34    Isomorphic(TextSummary),
  35    Inlay(Inlay),
  36}
  37
  38#[derive(Debug, Clone)]
  39pub struct Inlay {
  40    pub id: InlayId,
  41    pub position: Anchor,
  42    pub text: text::Rope,
  43}
  44
  45impl Inlay {
  46    pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self {
  47        let mut text = hint.text();
  48        if hint.padding_right && !text.ends_with(' ') {
  49            text.push(' ');
  50        }
  51        if hint.padding_left && !text.starts_with(' ') {
  52            text.insert(0, ' ');
  53        }
  54        Self {
  55            id: InlayId::Hint(id),
  56            position,
  57            text: text.into(),
  58        }
  59    }
  60
  61    pub fn suggestion<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
  62        Self {
  63            id: InlayId::Suggestion(id),
  64            position,
  65            text: text.into(),
  66        }
  67    }
  68}
  69
  70impl sum_tree::Item for Transform {
  71    type Summary = TransformSummary;
  72
  73    fn summary(&self) -> Self::Summary {
  74        match self {
  75            Transform::Isomorphic(summary) => TransformSummary {
  76                input: summary.clone(),
  77                output: summary.clone(),
  78            },
  79            Transform::Inlay(inlay) => TransformSummary {
  80                input: TextSummary::default(),
  81                output: inlay.text.summary(),
  82            },
  83        }
  84    }
  85}
  86
  87#[derive(Clone, Debug, Default)]
  88struct TransformSummary {
  89    input: TextSummary,
  90    output: TextSummary,
  91}
  92
  93impl sum_tree::Summary for TransformSummary {
  94    type Context = ();
  95
  96    fn add_summary(&mut self, other: &Self, _: &()) {
  97        self.input += &other.input;
  98        self.output += &other.output;
  99    }
 100}
 101
 102pub type InlayEdit = Edit<InlayOffset>;
 103
 104#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 105pub struct InlayOffset(pub usize);
 106
 107impl Add for InlayOffset {
 108    type Output = Self;
 109
 110    fn add(self, rhs: Self) -> Self::Output {
 111        Self(self.0 + rhs.0)
 112    }
 113}
 114
 115impl Sub for InlayOffset {
 116    type Output = Self;
 117
 118    fn sub(self, rhs: Self) -> Self::Output {
 119        Self(self.0 - rhs.0)
 120    }
 121}
 122
 123impl AddAssign for InlayOffset {
 124    fn add_assign(&mut self, rhs: Self) {
 125        self.0 += rhs.0;
 126    }
 127}
 128
 129impl SubAssign for InlayOffset {
 130    fn sub_assign(&mut self, rhs: Self) {
 131        self.0 -= rhs.0;
 132    }
 133}
 134
 135impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
 136    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 137        self.0 += &summary.output.len;
 138    }
 139}
 140
 141#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 142pub struct InlayPoint(pub Point);
 143
 144impl Add for InlayPoint {
 145    type Output = Self;
 146
 147    fn add(self, rhs: Self) -> Self::Output {
 148        Self(self.0 + rhs.0)
 149    }
 150}
 151
 152impl Sub for InlayPoint {
 153    type Output = Self;
 154
 155    fn sub(self, rhs: Self) -> Self::Output {
 156        Self(self.0 - rhs.0)
 157    }
 158}
 159
 160impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
 161    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 162        self.0 += &summary.output.lines;
 163    }
 164}
 165
 166impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
 167    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 168        *self += &summary.input.len;
 169    }
 170}
 171
 172impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
 173    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 174        *self += &summary.input.lines;
 175    }
 176}
 177
 178#[derive(Clone)]
 179pub struct InlayBufferRows<'a> {
 180    transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
 181    buffer_rows: MultiBufferRows<'a>,
 182    inlay_row: u32,
 183    max_buffer_row: u32,
 184}
 185
 186#[derive(Debug, Copy, Clone, Eq, PartialEq)]
 187struct HighlightEndpoint {
 188    offset: InlayOffset,
 189    is_start: bool,
 190    tag: Option<TypeId>,
 191    style: HighlightStyle,
 192}
 193
 194impl PartialOrd for HighlightEndpoint {
 195    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
 196        Some(self.cmp(other))
 197    }
 198}
 199
 200impl Ord for HighlightEndpoint {
 201    fn cmp(&self, other: &Self) -> cmp::Ordering {
 202        self.offset
 203            .cmp(&other.offset)
 204            .then_with(|| other.is_start.cmp(&self.is_start))
 205    }
 206}
 207
 208pub struct InlayChunks<'a> {
 209    transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
 210    buffer_chunks: MultiBufferChunks<'a>,
 211    buffer_chunk: Option<Chunk<'a>>,
 212    inlay_chunks: Option<text::Chunks<'a>>,
 213    inlay_chunk: Option<&'a str>,
 214    output_offset: InlayOffset,
 215    max_output_offset: InlayOffset,
 216    hint_highlight_style: Option<HighlightStyle>,
 217    suggestion_highlight_style: Option<HighlightStyle>,
 218    highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
 219    active_highlights: BTreeMap<Option<TypeId>, HighlightStyle>,
 220    snapshot: &'a InlaySnapshot,
 221}
 222
 223impl<'a> InlayChunks<'a> {
 224    pub fn seek(&mut self, offset: InlayOffset) {
 225        self.transforms.seek(&offset, Bias::Right, &());
 226
 227        let buffer_offset = self.snapshot.to_buffer_offset(offset);
 228        self.buffer_chunks.seek(buffer_offset);
 229        self.inlay_chunks = None;
 230        self.buffer_chunk = None;
 231        self.output_offset = offset;
 232    }
 233
 234    pub fn offset(&self) -> InlayOffset {
 235        self.output_offset
 236    }
 237}
 238
 239impl<'a> Iterator for InlayChunks<'a> {
 240    type Item = Chunk<'a>;
 241
 242    fn next(&mut self) -> Option<Self::Item> {
 243        if self.output_offset == self.max_output_offset {
 244            return None;
 245        }
 246
 247        // TODO kb highlights are not displayed still
 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_highlights: Option<&'a InlayHighlights>,
 997        inlay_highlight_style: Option<HighlightStyle>,
 998        suggestion_highlight_style: Option<HighlightStyle>,
 999    ) -> InlayChunks<'a> {
1000        let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
1001        cursor.seek(&range.start, Bias::Right, &());
1002
1003        let empty_text_highlights = TextHighlights::default();
1004        let text_highlights = text_highlights.unwrap_or_else(|| &empty_text_highlights);
1005        let empty_inlay_highlights = InlayHighlights::default();
1006        let inlay_highlights = inlay_highlights.unwrap_or_else(|| &empty_inlay_highlights);
1007
1008        let mut highlight_endpoints = Vec::new();
1009        if !text_highlights.is_empty() || !inlay_highlights.is_empty() {
1010            while cursor.start().0 < range.end {
1011                let transform_start = self
1012                    .buffer
1013                    .anchor_after(self.to_buffer_offset(cmp::max(range.start, cursor.start().0)));
1014
1015                let transform_end = {
1016                    let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0);
1017                    self.buffer.anchor_before(self.to_buffer_offset(cmp::min(
1018                        cursor.end(&()).0,
1019                        cursor.start().0 + overshoot,
1020                    )))
1021                };
1022
1023                let mut covered_tags = HashSet::default();
1024                for (tag, text_highlights) in text_highlights.iter() {
1025                    covered_tags.insert(*tag);
1026                    let style = text_highlights.0;
1027                    let ranges = &text_highlights.1;
1028
1029                    let start_ix = match ranges.binary_search_by(|probe| {
1030                        let cmp = probe.end.cmp(&transform_start, &self.buffer);
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                        if range.start.cmp(&transform_end, &self.buffer).is_ge() {
1041                            break;
1042                        }
1043
1044                        highlight_endpoints.push(HighlightEndpoint {
1045                            offset: self.to_inlay_offset(range.start.to_offset(&self.buffer)),
1046                            is_start: true,
1047                            tag: *tag,
1048                            style,
1049                        });
1050                        highlight_endpoints.push(HighlightEndpoint {
1051                            offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)),
1052                            is_start: false,
1053                            tag: *tag,
1054                            style,
1055                        });
1056                    }
1057
1058                    if let Some(inlay_highlights) = inlay_highlights.get(tag) {
1059                        self.push_inlay_highlight_range(
1060                            inlay_highlights,
1061                            transform_start,
1062                            transform_end,
1063                            &mut highlight_endpoints,
1064                            tag,
1065                        );
1066                    }
1067                }
1068
1069                for (tag, inlay_highlights) in inlay_highlights
1070                    .iter()
1071                    .filter(|(tag, _)| !covered_tags.contains(tag))
1072                {
1073                    self.push_inlay_highlight_range(
1074                        inlay_highlights,
1075                        transform_start,
1076                        transform_end,
1077                        &mut highlight_endpoints,
1078                        tag,
1079                    );
1080                }
1081
1082                cursor.next(&());
1083            }
1084            highlight_endpoints.sort();
1085            cursor.seek(&range.start, Bias::Right, &());
1086        }
1087
1088        let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
1089        let buffer_chunks = self.buffer.chunks(buffer_range, language_aware);
1090
1091        InlayChunks {
1092            transforms: cursor,
1093            buffer_chunks,
1094            inlay_chunks: None,
1095            inlay_chunk: None,
1096            buffer_chunk: None,
1097            output_offset: range.start,
1098            max_output_offset: range.end,
1099            hint_highlight_style: inlay_highlight_style,
1100            suggestion_highlight_style,
1101            highlight_endpoints: highlight_endpoints.into_iter().peekable(),
1102            active_highlights: Default::default(),
1103            snapshot: self,
1104        }
1105    }
1106
1107    fn push_inlay_highlight_range(
1108        &self,
1109        inlay_highlights: &std::sync::Arc<(
1110            HighlightStyle,
1111            Vec<crate::link_go_to_definition::InlayRange>,
1112        )>,
1113        transform_start: Anchor,
1114        transform_end: Anchor,
1115        highlight_endpoints: &mut Vec<HighlightEndpoint>,
1116        tag: &Option<TypeId>,
1117    ) {
1118        let style = inlay_highlights.0;
1119        let ranges = &inlay_highlights.1;
1120        let start_ix = match ranges
1121            .binary_search_by(|probe| probe.inlay_position.cmp(&transform_start, &self.buffer))
1122        {
1123            Ok(i) | Err(i) => i,
1124        };
1125        for range in &ranges[start_ix..] {
1126            if range
1127                .inlay_position
1128                .cmp(&transform_end, &self.buffer)
1129                .is_ge()
1130            {
1131                break;
1132            }
1133
1134            highlight_endpoints.push(HighlightEndpoint {
1135                offset: range.highlight_start,
1136                is_start: true,
1137                tag: *tag,
1138                style,
1139            });
1140            highlight_endpoints.push(HighlightEndpoint {
1141                offset: range.highlight_end,
1142                is_start: false,
1143                tag: *tag,
1144                style,
1145            });
1146        }
1147    }
1148
1149    #[cfg(test)]
1150    pub fn text(&self) -> String {
1151        self.chunks(
1152            Default::default()..self.len(),
1153            false,
1154            None,
1155            None,
1156            None,
1157            None,
1158        )
1159        .map(|chunk| chunk.text)
1160        .collect()
1161    }
1162
1163    fn check_invariants(&self) {
1164        #[cfg(any(debug_assertions, feature = "test-support"))]
1165        {
1166            assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
1167            let mut transforms = self.transforms.iter().peekable();
1168            while let Some(transform) = transforms.next() {
1169                let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
1170                if let Some(next_transform) = transforms.peek() {
1171                    let next_transform_is_isomorphic =
1172                        matches!(next_transform, Transform::Isomorphic(_));
1173                    assert!(
1174                        !transform_is_isomorphic || !next_transform_is_isomorphic,
1175                        "two adjacent isomorphic transforms"
1176                    );
1177                }
1178            }
1179        }
1180    }
1181}
1182
1183fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
1184    if summary.len == 0 {
1185        return;
1186    }
1187
1188    let mut summary = Some(summary);
1189    sum_tree.update_last(
1190        |transform| {
1191            if let Transform::Isomorphic(transform) = transform {
1192                *transform += summary.take().unwrap();
1193            }
1194        },
1195        &(),
1196    );
1197
1198    if let Some(summary) = summary {
1199        sum_tree.push(Transform::Isomorphic(summary), &());
1200    }
1201}
1202
1203#[cfg(test)]
1204mod tests {
1205    use super::*;
1206    use crate::{InlayId, MultiBuffer};
1207    use gpui::AppContext;
1208    use project::{InlayHint, InlayHintLabel, ResolveState};
1209    use rand::prelude::*;
1210    use settings::SettingsStore;
1211    use std::{cmp::Reverse, env, sync::Arc};
1212    use sum_tree::TreeMap;
1213    use text::Patch;
1214    use util::post_inc;
1215
1216    #[test]
1217    fn test_inlay_properties_label_padding() {
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: false,
1226                    padding_right: false,
1227                    tooltip: None,
1228                    kind: None,
1229                    resolve_state: ResolveState::Resolved,
1230                },
1231            )
1232            .text
1233            .to_string(),
1234            "a",
1235            "Should not pad label if not requested"
1236        );
1237
1238        assert_eq!(
1239            Inlay::hint(
1240                0,
1241                Anchor::min(),
1242                &InlayHint {
1243                    label: InlayHintLabel::String("a".to_string()),
1244                    position: text::Anchor::default(),
1245                    padding_left: true,
1246                    padding_right: true,
1247                    tooltip: None,
1248                    kind: None,
1249                    resolve_state: ResolveState::Resolved,
1250                },
1251            )
1252            .text
1253            .to_string(),
1254            " a ",
1255            "Should pad label for every side requested"
1256        );
1257
1258        assert_eq!(
1259            Inlay::hint(
1260                0,
1261                Anchor::min(),
1262                &InlayHint {
1263                    label: InlayHintLabel::String(" a ".to_string()),
1264                    position: text::Anchor::default(),
1265                    padding_left: false,
1266                    padding_right: false,
1267                    tooltip: None,
1268                    kind: None,
1269                    resolve_state: ResolveState::Resolved,
1270                },
1271            )
1272            .text
1273            .to_string(),
1274            " a ",
1275            "Should not change already padded label"
1276        );
1277
1278        assert_eq!(
1279            Inlay::hint(
1280                0,
1281                Anchor::min(),
1282                &InlayHint {
1283                    label: InlayHintLabel::String(" a ".to_string()),
1284                    position: text::Anchor::default(),
1285                    padding_left: true,
1286                    padding_right: true,
1287                    tooltip: None,
1288                    kind: None,
1289                    resolve_state: ResolveState::Resolved,
1290                },
1291            )
1292            .text
1293            .to_string(),
1294            " a ",
1295            "Should not change already padded label"
1296        );
1297    }
1298
1299    #[gpui::test]
1300    fn test_basic_inlays(cx: &mut AppContext) {
1301        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
1302        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
1303        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1304        assert_eq!(inlay_snapshot.text(), "abcdefghi");
1305        let mut next_inlay_id = 0;
1306
1307        let (inlay_snapshot, _) = inlay_map.splice(
1308            Vec::new(),
1309            vec![Inlay {
1310                id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1311                position: buffer.read(cx).snapshot(cx).anchor_after(3),
1312                text: "|123|".into(),
1313            }],
1314        );
1315        assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
1316        assert_eq!(
1317            inlay_snapshot.to_inlay_point(Point::new(0, 0)),
1318            InlayPoint::new(0, 0)
1319        );
1320        assert_eq!(
1321            inlay_snapshot.to_inlay_point(Point::new(0, 1)),
1322            InlayPoint::new(0, 1)
1323        );
1324        assert_eq!(
1325            inlay_snapshot.to_inlay_point(Point::new(0, 2)),
1326            InlayPoint::new(0, 2)
1327        );
1328        assert_eq!(
1329            inlay_snapshot.to_inlay_point(Point::new(0, 3)),
1330            InlayPoint::new(0, 3)
1331        );
1332        assert_eq!(
1333            inlay_snapshot.to_inlay_point(Point::new(0, 4)),
1334            InlayPoint::new(0, 9)
1335        );
1336        assert_eq!(
1337            inlay_snapshot.to_inlay_point(Point::new(0, 5)),
1338            InlayPoint::new(0, 10)
1339        );
1340        assert_eq!(
1341            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1342            InlayPoint::new(0, 0)
1343        );
1344        assert_eq!(
1345            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1346            InlayPoint::new(0, 0)
1347        );
1348        assert_eq!(
1349            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1350            InlayPoint::new(0, 3)
1351        );
1352        assert_eq!(
1353            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1354            InlayPoint::new(0, 3)
1355        );
1356        assert_eq!(
1357            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1358            InlayPoint::new(0, 3)
1359        );
1360        assert_eq!(
1361            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1362            InlayPoint::new(0, 9)
1363        );
1364
1365        // Edits before or after the inlay should not affect it.
1366        buffer.update(cx, |buffer, cx| {
1367            buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
1368        });
1369        let (inlay_snapshot, _) = inlay_map.sync(
1370            buffer.read(cx).snapshot(cx),
1371            buffer_edits.consume().into_inner(),
1372        );
1373        assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
1374
1375        // An edit surrounding the inlay should invalidate it.
1376        buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
1377        let (inlay_snapshot, _) = inlay_map.sync(
1378            buffer.read(cx).snapshot(cx),
1379            buffer_edits.consume().into_inner(),
1380        );
1381        assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
1382
1383        let (inlay_snapshot, _) = inlay_map.splice(
1384            Vec::new(),
1385            vec![
1386                Inlay {
1387                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1388                    position: buffer.read(cx).snapshot(cx).anchor_before(3),
1389                    text: "|123|".into(),
1390                },
1391                Inlay {
1392                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1393                    position: buffer.read(cx).snapshot(cx).anchor_after(3),
1394                    text: "|456|".into(),
1395                },
1396            ],
1397        );
1398        assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi");
1399
1400        // Edits ending where the inlay starts should not move it if it has a left bias.
1401        buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
1402        let (inlay_snapshot, _) = inlay_map.sync(
1403            buffer.read(cx).snapshot(cx),
1404            buffer_edits.consume().into_inner(),
1405        );
1406        assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
1407
1408        assert_eq!(
1409            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
1410            InlayPoint::new(0, 0)
1411        );
1412        assert_eq!(
1413            inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
1414            InlayPoint::new(0, 0)
1415        );
1416
1417        assert_eq!(
1418            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
1419            InlayPoint::new(0, 1)
1420        );
1421        assert_eq!(
1422            inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
1423            InlayPoint::new(0, 1)
1424        );
1425
1426        assert_eq!(
1427            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
1428            InlayPoint::new(0, 2)
1429        );
1430        assert_eq!(
1431            inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
1432            InlayPoint::new(0, 2)
1433        );
1434
1435        assert_eq!(
1436            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
1437            InlayPoint::new(0, 2)
1438        );
1439        assert_eq!(
1440            inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
1441            InlayPoint::new(0, 8)
1442        );
1443
1444        assert_eq!(
1445            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
1446            InlayPoint::new(0, 2)
1447        );
1448        assert_eq!(
1449            inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
1450            InlayPoint::new(0, 8)
1451        );
1452
1453        assert_eq!(
1454            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
1455            InlayPoint::new(0, 2)
1456        );
1457        assert_eq!(
1458            inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
1459            InlayPoint::new(0, 8)
1460        );
1461
1462        assert_eq!(
1463            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
1464            InlayPoint::new(0, 2)
1465        );
1466        assert_eq!(
1467            inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
1468            InlayPoint::new(0, 8)
1469        );
1470
1471        assert_eq!(
1472            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
1473            InlayPoint::new(0, 2)
1474        );
1475        assert_eq!(
1476            inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
1477            InlayPoint::new(0, 8)
1478        );
1479
1480        assert_eq!(
1481            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
1482            InlayPoint::new(0, 8)
1483        );
1484        assert_eq!(
1485            inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
1486            InlayPoint::new(0, 8)
1487        );
1488
1489        assert_eq!(
1490            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
1491            InlayPoint::new(0, 9)
1492        );
1493        assert_eq!(
1494            inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
1495            InlayPoint::new(0, 9)
1496        );
1497
1498        assert_eq!(
1499            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
1500            InlayPoint::new(0, 10)
1501        );
1502        assert_eq!(
1503            inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
1504            InlayPoint::new(0, 10)
1505        );
1506
1507        assert_eq!(
1508            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
1509            InlayPoint::new(0, 11)
1510        );
1511        assert_eq!(
1512            inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
1513            InlayPoint::new(0, 11)
1514        );
1515
1516        assert_eq!(
1517            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
1518            InlayPoint::new(0, 11)
1519        );
1520        assert_eq!(
1521            inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
1522            InlayPoint::new(0, 17)
1523        );
1524
1525        assert_eq!(
1526            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
1527            InlayPoint::new(0, 11)
1528        );
1529        assert_eq!(
1530            inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
1531            InlayPoint::new(0, 17)
1532        );
1533
1534        assert_eq!(
1535            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
1536            InlayPoint::new(0, 11)
1537        );
1538        assert_eq!(
1539            inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
1540            InlayPoint::new(0, 17)
1541        );
1542
1543        assert_eq!(
1544            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
1545            InlayPoint::new(0, 11)
1546        );
1547        assert_eq!(
1548            inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
1549            InlayPoint::new(0, 17)
1550        );
1551
1552        assert_eq!(
1553            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
1554            InlayPoint::new(0, 11)
1555        );
1556        assert_eq!(
1557            inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
1558            InlayPoint::new(0, 17)
1559        );
1560
1561        assert_eq!(
1562            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
1563            InlayPoint::new(0, 17)
1564        );
1565        assert_eq!(
1566            inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
1567            InlayPoint::new(0, 17)
1568        );
1569
1570        assert_eq!(
1571            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
1572            InlayPoint::new(0, 18)
1573        );
1574        assert_eq!(
1575            inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
1576            InlayPoint::new(0, 18)
1577        );
1578
1579        // The inlays can be manually removed.
1580        let (inlay_snapshot, _) = inlay_map.splice(
1581            inlay_map.inlays.iter().map(|inlay| inlay.id).collect(),
1582            Vec::new(),
1583        );
1584        assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi");
1585    }
1586
1587    #[gpui::test]
1588    fn test_inlay_buffer_rows(cx: &mut AppContext) {
1589        let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
1590        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
1591        assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
1592        let mut next_inlay_id = 0;
1593
1594        let (inlay_snapshot, _) = inlay_map.splice(
1595            Vec::new(),
1596            vec![
1597                Inlay {
1598                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1599                    position: buffer.read(cx).snapshot(cx).anchor_before(0),
1600                    text: "|123|\n".into(),
1601                },
1602                Inlay {
1603                    id: InlayId::Hint(post_inc(&mut next_inlay_id)),
1604                    position: buffer.read(cx).snapshot(cx).anchor_before(4),
1605                    text: "|456|".into(),
1606                },
1607                Inlay {
1608                    id: InlayId::Suggestion(post_inc(&mut next_inlay_id)),
1609                    position: buffer.read(cx).snapshot(cx).anchor_before(7),
1610                    text: "\n|567|\n".into(),
1611                },
1612            ],
1613        );
1614        assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
1615        assert_eq!(
1616            inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
1617            vec![Some(0), None, Some(1), None, None, Some(2)]
1618        );
1619    }
1620
1621    #[gpui::test(iterations = 100)]
1622    fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) {
1623        init_test(cx);
1624
1625        let operations = env::var("OPERATIONS")
1626            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1627            .unwrap_or(10);
1628
1629        let len = rng.gen_range(0..30);
1630        let buffer = if rng.gen() {
1631            let text = util::RandomCharIter::new(&mut rng)
1632                .take(len)
1633                .collect::<String>();
1634            MultiBuffer::build_simple(&text, cx)
1635        } else {
1636            MultiBuffer::build_random(&mut rng, cx)
1637        };
1638        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1639        let mut next_inlay_id = 0;
1640        log::info!("buffer text: {:?}", buffer_snapshot.text());
1641
1642        let mut highlights = TreeMap::default();
1643        let highlight_count = rng.gen_range(0_usize..10);
1644        let mut highlight_ranges = (0..highlight_count)
1645            .map(|_| buffer_snapshot.random_byte_range(0, &mut rng))
1646            .collect::<Vec<_>>();
1647        highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
1648        log::info!("highlighting ranges {:?}", highlight_ranges);
1649        let highlight_ranges = highlight_ranges
1650            .into_iter()
1651            .map(|range| {
1652                buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end)
1653            })
1654            .collect::<Vec<_>>();
1655
1656        highlights.insert(
1657            Some(TypeId::of::<()>()),
1658            Arc::new((HighlightStyle::default(), highlight_ranges)),
1659        );
1660
1661        let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1662        for _ in 0..operations {
1663            let mut inlay_edits = Patch::default();
1664
1665            let mut prev_inlay_text = inlay_snapshot.text();
1666            let mut buffer_edits = Vec::new();
1667            match rng.gen_range(0..=100) {
1668                0..=50 => {
1669                    let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1670                    log::info!("mutated text: {:?}", snapshot.text());
1671                    inlay_edits = Patch::new(edits);
1672                }
1673                _ => buffer.update(cx, |buffer, cx| {
1674                    let subscription = buffer.subscribe();
1675                    let edit_count = rng.gen_range(1..=5);
1676                    buffer.randomly_mutate(&mut rng, edit_count, cx);
1677                    buffer_snapshot = buffer.snapshot(cx);
1678                    let edits = subscription.consume().into_inner();
1679                    log::info!("editing {:?}", edits);
1680                    buffer_edits.extend(edits);
1681                }),
1682            };
1683
1684            let (new_inlay_snapshot, new_inlay_edits) =
1685                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1686            inlay_snapshot = new_inlay_snapshot;
1687            inlay_edits = inlay_edits.compose(new_inlay_edits);
1688
1689            log::info!("buffer text: {:?}", buffer_snapshot.text());
1690            log::info!("inlay text: {:?}", inlay_snapshot.text());
1691
1692            let inlays = inlay_map
1693                .inlays
1694                .iter()
1695                .filter(|inlay| inlay.position.is_valid(&buffer_snapshot))
1696                .map(|inlay| {
1697                    let offset = inlay.position.to_offset(&buffer_snapshot);
1698                    (offset, inlay.clone())
1699                })
1700                .collect::<Vec<_>>();
1701            let mut expected_text = Rope::from(buffer_snapshot.text());
1702            for (offset, inlay) in inlays.into_iter().rev() {
1703                expected_text.replace(offset..offset, &inlay.text.to_string());
1704            }
1705            assert_eq!(inlay_snapshot.text(), expected_text.to_string());
1706
1707            let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
1708            assert_eq!(
1709                expected_buffer_rows.len() as u32,
1710                expected_text.max_point().row + 1
1711            );
1712            for row_start in 0..expected_buffer_rows.len() {
1713                assert_eq!(
1714                    inlay_snapshot
1715                        .buffer_rows(row_start as u32)
1716                        .collect::<Vec<_>>(),
1717                    &expected_buffer_rows[row_start..],
1718                    "incorrect buffer rows starting at {}",
1719                    row_start
1720                );
1721            }
1722
1723            for _ in 0..5 {
1724                let mut end = rng.gen_range(0..=inlay_snapshot.len().0);
1725                end = expected_text.clip_offset(end, Bias::Right);
1726                let mut start = rng.gen_range(0..=end);
1727                start = expected_text.clip_offset(start, Bias::Right);
1728
1729                let actual_text = inlay_snapshot
1730                    .chunks(
1731                        InlayOffset(start)..InlayOffset(end),
1732                        false,
1733                        Some(&highlights),
1734                        // TODO kb add tests
1735                        None,
1736                        None,
1737                        None,
1738                    )
1739                    .map(|chunk| chunk.text)
1740                    .collect::<String>();
1741                assert_eq!(
1742                    actual_text,
1743                    expected_text.slice(start..end).to_string(),
1744                    "incorrect text in range {:?}",
1745                    start..end
1746                );
1747
1748                assert_eq!(
1749                    inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)),
1750                    expected_text.slice(start..end).summary()
1751                );
1752            }
1753
1754            for edit in inlay_edits {
1755                prev_inlay_text.replace_range(
1756                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
1757                    &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0],
1758                );
1759            }
1760            assert_eq!(prev_inlay_text, inlay_snapshot.text());
1761
1762            assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
1763            assert_eq!(expected_text.len(), inlay_snapshot.len().0);
1764
1765            let mut buffer_point = Point::default();
1766            let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1767            let mut buffer_chars = buffer_snapshot.chars_at(0);
1768            loop {
1769                // Ensure conversion from buffer coordinates to inlay coordinates
1770                // is consistent.
1771                let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
1772                assert_eq!(
1773                    inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
1774                    inlay_point
1775                );
1776
1777                // No matter which bias we clip an inlay point with, it doesn't move
1778                // because it was constructed from a buffer point.
1779                assert_eq!(
1780                    inlay_snapshot.clip_point(inlay_point, Bias::Left),
1781                    inlay_point,
1782                    "invalid inlay point for buffer point {:?} when clipped left",
1783                    buffer_point
1784                );
1785                assert_eq!(
1786                    inlay_snapshot.clip_point(inlay_point, Bias::Right),
1787                    inlay_point,
1788                    "invalid inlay point for buffer point {:?} when clipped right",
1789                    buffer_point
1790                );
1791
1792                if let Some(ch) = buffer_chars.next() {
1793                    if ch == '\n' {
1794                        buffer_point += Point::new(1, 0);
1795                    } else {
1796                        buffer_point += Point::new(0, ch.len_utf8() as u32);
1797                    }
1798
1799                    // Ensure that moving forward in the buffer always moves the inlay point forward as well.
1800                    let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
1801                    assert!(new_inlay_point > inlay_point);
1802                    inlay_point = new_inlay_point;
1803                } else {
1804                    break;
1805                }
1806            }
1807
1808            let mut inlay_point = InlayPoint::default();
1809            let mut inlay_offset = InlayOffset::default();
1810            for ch in expected_text.chars() {
1811                assert_eq!(
1812                    inlay_snapshot.to_offset(inlay_point),
1813                    inlay_offset,
1814                    "invalid to_offset({:?})",
1815                    inlay_point
1816                );
1817                assert_eq!(
1818                    inlay_snapshot.to_point(inlay_offset),
1819                    inlay_point,
1820                    "invalid to_point({:?})",
1821                    inlay_offset
1822                );
1823
1824                let mut bytes = [0; 4];
1825                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
1826                    inlay_offset.0 += 1;
1827                    if *byte == b'\n' {
1828                        inlay_point.0 += Point::new(1, 0);
1829                    } else {
1830                        inlay_point.0 += Point::new(0, 1);
1831                    }
1832
1833                    let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left);
1834                    let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
1835                    assert!(
1836                        clipped_left_point <= clipped_right_point,
1837                        "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
1838                        inlay_point,
1839                        clipped_left_point,
1840                        clipped_right_point
1841                    );
1842
1843                    // Ensure the clipped points are at valid text locations.
1844                    assert_eq!(
1845                        clipped_left_point.0,
1846                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
1847                    );
1848                    assert_eq!(
1849                        clipped_right_point.0,
1850                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
1851                    );
1852
1853                    // Ensure the clipped points never overshoot the end of the map.
1854                    assert!(clipped_left_point <= inlay_snapshot.max_point());
1855                    assert!(clipped_right_point <= inlay_snapshot.max_point());
1856
1857                    // Ensure the clipped points are at valid buffer locations.
1858                    assert_eq!(
1859                        inlay_snapshot
1860                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
1861                        clipped_left_point,
1862                        "to_buffer_point({:?}) = {:?}",
1863                        clipped_left_point,
1864                        inlay_snapshot.to_buffer_point(clipped_left_point),
1865                    );
1866                    assert_eq!(
1867                        inlay_snapshot
1868                            .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
1869                        clipped_right_point,
1870                        "to_buffer_point({:?}) = {:?}",
1871                        clipped_right_point,
1872                        inlay_snapshot.to_buffer_point(clipped_right_point),
1873                    );
1874                }
1875            }
1876        }
1877    }
1878
1879    fn init_test(cx: &mut AppContext) {
1880        cx.set_global(SettingsStore::test(cx));
1881        theme::init((), cx);
1882    }
1883}