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