inlay_map.rs

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