inlay_map.rs

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