inlay_map.rs

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