block_map.rs

   1use super::{
   2    Highlights,
   3    fold_map::Chunk,
   4    wrap_map::{self, WrapEdit, WrapPatch, WrapPoint, WrapSnapshot},
   5};
   6use crate::{
   7    EditorStyle, GutterDimensions,
   8    display_map::{dimensions::RowDelta, wrap_map::WrapRow},
   9};
  10use collections::{Bound, HashMap, HashSet};
  11use gpui::{AnyElement, App, EntityId, Pixels, Window};
  12use language::{Patch, Point};
  13use multi_buffer::{
  14    Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, RowInfo,
  15    ToOffset, ToPoint as _,
  16};
  17use parking_lot::Mutex;
  18use std::{
  19    cell::RefCell,
  20    cmp::{self, Ordering},
  21    fmt::Debug,
  22    ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive},
  23    sync::{
  24        Arc,
  25        atomic::{AtomicUsize, Ordering::SeqCst},
  26    },
  27};
  28use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
  29use text::{BufferId, Edit};
  30use ui::ElementId;
  31
  32const NEWLINES: &[u8; rope::Chunk::MASK_BITS] = &[b'\n'; _];
  33const BULLETS: &[u8; rope::Chunk::MASK_BITS] = &[b'*'; _];
  34
  35/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
  36///
  37/// See the [`display_map` module documentation](crate::display_map) for more information.
  38pub struct BlockMap {
  39    pub(super) wrap_snapshot: RefCell<WrapSnapshot>,
  40    next_block_id: AtomicUsize,
  41    custom_blocks: Vec<Arc<CustomBlock>>,
  42    custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
  43    transforms: RefCell<SumTree<Transform>>,
  44    buffer_header_height: u32,
  45    excerpt_header_height: u32,
  46    pub(super) folded_buffers: HashSet<BufferId>,
  47    buffers_with_disabled_headers: HashSet<BufferId>,
  48}
  49
  50pub struct BlockMapReader<'a> {
  51    blocks: &'a Vec<Arc<CustomBlock>>,
  52    pub snapshot: BlockSnapshot,
  53}
  54
  55pub struct BlockMapWriter<'a>(&'a mut BlockMap);
  56
  57#[derive(Clone)]
  58pub struct BlockSnapshot {
  59    pub(super) wrap_snapshot: WrapSnapshot,
  60    transforms: SumTree<Transform>,
  61    custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
  62    pub(super) buffer_header_height: u32,
  63    pub(super) excerpt_header_height: u32,
  64}
  65
  66#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
  67pub struct CustomBlockId(pub usize);
  68
  69impl From<CustomBlockId> for ElementId {
  70    fn from(val: CustomBlockId) -> Self {
  71        val.0.into()
  72    }
  73}
  74
  75/// A zero-indexed point in a text buffer consisting of a row and column
  76/// adjusted for inserted blocks, wrapped rows, tabs, folds and inlays.
  77#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
  78pub struct BlockPoint(pub Point);
  79
  80#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
  81pub struct BlockRow(pub u32);
  82
  83impl_for_row_types! {
  84    BlockRow => RowDelta
  85}
  86
  87impl BlockPoint {
  88    pub fn row(&self) -> BlockRow {
  89        BlockRow(self.0.row)
  90    }
  91}
  92
  93pub type RenderBlock = Arc<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
  94
  95/// Where to place a block.
  96#[derive(Clone, Debug, Eq, PartialEq)]
  97pub enum BlockPlacement<T> {
  98    /// Place the block above the given position.
  99    Above(T),
 100    /// Place the block below the given position.
 101    Below(T),
 102    /// Place the block next the given position.
 103    Near(T),
 104    /// Replace the given range of positions with the block.
 105    Replace(RangeInclusive<T>),
 106}
 107
 108impl<T> BlockPlacement<T> {
 109    pub fn start(&self) -> &T {
 110        match self {
 111            BlockPlacement::Above(position) => position,
 112            BlockPlacement::Below(position) => position,
 113            BlockPlacement::Near(position) => position,
 114            BlockPlacement::Replace(range) => range.start(),
 115        }
 116    }
 117
 118    fn end(&self) -> &T {
 119        match self {
 120            BlockPlacement::Above(position) => position,
 121            BlockPlacement::Below(position) => position,
 122            BlockPlacement::Near(position) => position,
 123            BlockPlacement::Replace(range) => range.end(),
 124        }
 125    }
 126
 127    pub fn as_ref(&self) -> BlockPlacement<&T> {
 128        match self {
 129            BlockPlacement::Above(position) => BlockPlacement::Above(position),
 130            BlockPlacement::Below(position) => BlockPlacement::Below(position),
 131            BlockPlacement::Near(position) => BlockPlacement::Near(position),
 132            BlockPlacement::Replace(range) => BlockPlacement::Replace(range.start()..=range.end()),
 133        }
 134    }
 135
 136    pub fn map<R>(self, mut f: impl FnMut(T) -> R) -> BlockPlacement<R> {
 137        match self {
 138            BlockPlacement::Above(position) => BlockPlacement::Above(f(position)),
 139            BlockPlacement::Below(position) => BlockPlacement::Below(f(position)),
 140            BlockPlacement::Near(position) => BlockPlacement::Near(f(position)),
 141            BlockPlacement::Replace(range) => {
 142                let (start, end) = range.into_inner();
 143                BlockPlacement::Replace(f(start)..=f(end))
 144            }
 145        }
 146    }
 147
 148    fn tie_break(&self) -> u8 {
 149        match self {
 150            BlockPlacement::Replace(_) => 0,
 151            BlockPlacement::Above(_) => 1,
 152            BlockPlacement::Near(_) => 2,
 153            BlockPlacement::Below(_) => 3,
 154        }
 155    }
 156}
 157
 158impl BlockPlacement<Anchor> {
 159    fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
 160        self.start()
 161            .cmp(other.start(), buffer)
 162            .then_with(|| other.end().cmp(self.end(), buffer))
 163            .then_with(|| self.tie_break().cmp(&other.tie_break()))
 164    }
 165
 166    fn to_wrap_row(&self, wrap_snapshot: &WrapSnapshot) -> Option<BlockPlacement<WrapRow>> {
 167        let buffer_snapshot = wrap_snapshot.buffer_snapshot();
 168        match self {
 169            BlockPlacement::Above(position) => {
 170                let mut position = position.to_point(buffer_snapshot);
 171                position.column = 0;
 172                let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
 173                Some(BlockPlacement::Above(wrap_row))
 174            }
 175            BlockPlacement::Near(position) => {
 176                let mut position = position.to_point(buffer_snapshot);
 177                position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
 178                let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
 179                Some(BlockPlacement::Near(wrap_row))
 180            }
 181            BlockPlacement::Below(position) => {
 182                let mut position = position.to_point(buffer_snapshot);
 183                position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
 184                let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
 185                Some(BlockPlacement::Below(wrap_row))
 186            }
 187            BlockPlacement::Replace(range) => {
 188                let mut start = range.start().to_point(buffer_snapshot);
 189                let mut end = range.end().to_point(buffer_snapshot);
 190                if start == end {
 191                    None
 192                } else {
 193                    start.column = 0;
 194                    let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
 195                    end.column = buffer_snapshot.line_len(MultiBufferRow(end.row));
 196                    let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
 197                    Some(BlockPlacement::Replace(start_wrap_row..=end_wrap_row))
 198                }
 199            }
 200        }
 201    }
 202}
 203
 204pub struct CustomBlock {
 205    pub id: CustomBlockId,
 206    pub placement: BlockPlacement<Anchor>,
 207    pub height: Option<u32>,
 208    style: BlockStyle,
 209    render: Arc<Mutex<RenderBlock>>,
 210    priority: usize,
 211}
 212
 213#[derive(Clone)]
 214pub struct BlockProperties<P> {
 215    pub placement: BlockPlacement<P>,
 216    // None if the block takes up no space
 217    // (e.g. a horizontal line)
 218    pub height: Option<u32>,
 219    pub style: BlockStyle,
 220    pub render: RenderBlock,
 221    pub priority: usize,
 222}
 223
 224impl<P: Debug> Debug for BlockProperties<P> {
 225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 226        f.debug_struct("BlockProperties")
 227            .field("placement", &self.placement)
 228            .field("height", &self.height)
 229            .field("style", &self.style)
 230            .finish()
 231    }
 232}
 233
 234#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
 235pub enum BlockStyle {
 236    Fixed,
 237    Flex,
 238    Sticky,
 239}
 240
 241#[derive(Debug, Default, Copy, Clone)]
 242pub struct EditorMargins {
 243    pub gutter: GutterDimensions,
 244    pub right: Pixels,
 245}
 246
 247#[derive(gpui::AppContext, gpui::VisualContext)]
 248pub struct BlockContext<'a, 'b> {
 249    #[window]
 250    pub window: &'a mut Window,
 251    #[app]
 252    pub app: &'b mut App,
 253    pub anchor_x: Pixels,
 254    pub max_width: Pixels,
 255    pub margins: &'b EditorMargins,
 256    pub em_width: Pixels,
 257    pub line_height: Pixels,
 258    pub block_id: BlockId,
 259    pub selected: bool,
 260    pub editor_style: &'b EditorStyle,
 261}
 262
 263#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
 264pub enum BlockId {
 265    ExcerptBoundary(ExcerptId),
 266    FoldedBuffer(ExcerptId),
 267    Custom(CustomBlockId),
 268}
 269
 270impl From<BlockId> for ElementId {
 271    fn from(value: BlockId) -> Self {
 272        match value {
 273            BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
 274            BlockId::ExcerptBoundary(excerpt_id) => {
 275                ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
 276            }
 277            BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
 278        }
 279    }
 280}
 281
 282impl std::fmt::Display for BlockId {
 283    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 284        match self {
 285            Self::Custom(id) => write!(f, "Block({id:?})"),
 286            Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
 287            Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
 288        }
 289    }
 290}
 291
 292#[derive(Clone, Debug)]
 293struct Transform {
 294    summary: TransformSummary,
 295    block: Option<Block>,
 296}
 297
 298#[derive(Clone)]
 299pub enum Block {
 300    Custom(Arc<CustomBlock>),
 301    FoldedBuffer {
 302        first_excerpt: ExcerptInfo,
 303        height: u32,
 304    },
 305    ExcerptBoundary {
 306        excerpt: ExcerptInfo,
 307        height: u32,
 308    },
 309    BufferHeader {
 310        excerpt: ExcerptInfo,
 311        height: u32,
 312    },
 313}
 314
 315impl Block {
 316    pub fn id(&self) -> BlockId {
 317        match self {
 318            Block::Custom(block) => BlockId::Custom(block.id),
 319            Block::ExcerptBoundary {
 320                excerpt: next_excerpt,
 321                ..
 322            } => BlockId::ExcerptBoundary(next_excerpt.id),
 323            Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
 324            Block::BufferHeader {
 325                excerpt: next_excerpt,
 326                ..
 327            } => BlockId::ExcerptBoundary(next_excerpt.id),
 328        }
 329    }
 330
 331    pub fn has_height(&self) -> bool {
 332        match self {
 333            Block::Custom(block) => block.height.is_some(),
 334            Block::ExcerptBoundary { .. }
 335            | Block::FoldedBuffer { .. }
 336            | Block::BufferHeader { .. } => true,
 337        }
 338    }
 339
 340    pub fn height(&self) -> u32 {
 341        match self {
 342            Block::Custom(block) => block.height.unwrap_or(0),
 343            Block::ExcerptBoundary { height, .. }
 344            | Block::FoldedBuffer { height, .. }
 345            | Block::BufferHeader { height, .. } => *height,
 346        }
 347    }
 348
 349    pub fn style(&self) -> BlockStyle {
 350        match self {
 351            Block::Custom(block) => block.style,
 352            Block::ExcerptBoundary { .. }
 353            | Block::FoldedBuffer { .. }
 354            | Block::BufferHeader { .. } => BlockStyle::Sticky,
 355        }
 356    }
 357
 358    fn place_above(&self) -> bool {
 359        match self {
 360            Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)),
 361            Block::FoldedBuffer { .. } => false,
 362            Block::ExcerptBoundary { .. } => true,
 363            Block::BufferHeader { .. } => true,
 364        }
 365    }
 366
 367    pub fn place_near(&self) -> bool {
 368        match self {
 369            Block::Custom(block) => matches!(block.placement, BlockPlacement::Near(_)),
 370            Block::FoldedBuffer { .. } => false,
 371            Block::ExcerptBoundary { .. } => false,
 372            Block::BufferHeader { .. } => false,
 373        }
 374    }
 375
 376    fn place_below(&self) -> bool {
 377        match self {
 378            Block::Custom(block) => matches!(
 379                block.placement,
 380                BlockPlacement::Below(_) | BlockPlacement::Near(_)
 381            ),
 382            Block::FoldedBuffer { .. } => false,
 383            Block::ExcerptBoundary { .. } => false,
 384            Block::BufferHeader { .. } => false,
 385        }
 386    }
 387
 388    fn is_replacement(&self) -> bool {
 389        match self {
 390            Block::Custom(block) => matches!(block.placement, BlockPlacement::Replace(_)),
 391            Block::FoldedBuffer { .. } => true,
 392            Block::ExcerptBoundary { .. } => false,
 393            Block::BufferHeader { .. } => false,
 394        }
 395    }
 396
 397    fn is_header(&self) -> bool {
 398        match self {
 399            Block::Custom(_) => false,
 400            Block::FoldedBuffer { .. } => true,
 401            Block::ExcerptBoundary { .. } => true,
 402            Block::BufferHeader { .. } => true,
 403        }
 404    }
 405
 406    pub fn is_buffer_header(&self) -> bool {
 407        match self {
 408            Block::Custom(_) => false,
 409            Block::FoldedBuffer { .. } => true,
 410            Block::ExcerptBoundary { .. } => false,
 411            Block::BufferHeader { .. } => true,
 412        }
 413    }
 414}
 415
 416impl Debug for Block {
 417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 418        match self {
 419            Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
 420            Self::FoldedBuffer {
 421                first_excerpt,
 422                height,
 423            } => f
 424                .debug_struct("FoldedBuffer")
 425                .field("first_excerpt", &first_excerpt)
 426                .field("height", height)
 427                .finish(),
 428            Self::ExcerptBoundary { excerpt, height } => f
 429                .debug_struct("ExcerptBoundary")
 430                .field("excerpt", excerpt)
 431                .field("height", height)
 432                .finish(),
 433            Self::BufferHeader { excerpt, height } => f
 434                .debug_struct("BufferHeader")
 435                .field("excerpt", excerpt)
 436                .field("height", height)
 437                .finish(),
 438        }
 439    }
 440}
 441
 442#[derive(Clone, Debug, Default)]
 443struct TransformSummary {
 444    input_rows: WrapRow,
 445    output_rows: BlockRow,
 446    longest_row: BlockRow,
 447    longest_row_chars: u32,
 448}
 449
 450pub struct BlockChunks<'a> {
 451    transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
 452    input_chunks: wrap_map::WrapChunks<'a>,
 453    input_chunk: Chunk<'a>,
 454    output_row: BlockRow,
 455    max_output_row: BlockRow,
 456    line_count_overflow: RowDelta,
 457    masked: bool,
 458}
 459
 460#[derive(Clone)]
 461pub struct BlockRows<'a> {
 462    transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
 463    input_rows: wrap_map::WrapRows<'a>,
 464    output_row: BlockRow,
 465    started: bool,
 466}
 467
 468impl BlockMap {
 469    pub fn new(
 470        wrap_snapshot: WrapSnapshot,
 471        buffer_header_height: u32,
 472        excerpt_header_height: u32,
 473    ) -> Self {
 474        let row_count = wrap_snapshot.max_point().row() + WrapRow(1);
 475        let mut transforms = SumTree::default();
 476        push_isomorphic(&mut transforms, row_count - WrapRow(0), &wrap_snapshot);
 477        let map = Self {
 478            next_block_id: AtomicUsize::new(0),
 479            custom_blocks: Vec::new(),
 480            custom_blocks_by_id: TreeMap::default(),
 481            folded_buffers: HashSet::default(),
 482            buffers_with_disabled_headers: HashSet::default(),
 483            transforms: RefCell::new(transforms),
 484            wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
 485            buffer_header_height,
 486            excerpt_header_height,
 487        };
 488        map.sync(
 489            &wrap_snapshot,
 490            Patch::new(vec![Edit {
 491                old: WrapRow(0)..row_count,
 492                new: WrapRow(0)..row_count,
 493            }]),
 494        );
 495        map
 496    }
 497
 498    pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: WrapPatch) -> BlockMapReader<'_> {
 499        self.sync(&wrap_snapshot, edits);
 500        *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
 501        BlockMapReader {
 502            blocks: &self.custom_blocks,
 503            snapshot: BlockSnapshot {
 504                wrap_snapshot,
 505                transforms: self.transforms.borrow().clone(),
 506                custom_blocks_by_id: self.custom_blocks_by_id.clone(),
 507                buffer_header_height: self.buffer_header_height,
 508                excerpt_header_height: self.excerpt_header_height,
 509            },
 510        }
 511    }
 512
 513    pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: WrapPatch) -> BlockMapWriter<'_> {
 514        self.sync(&wrap_snapshot, edits);
 515        *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
 516        BlockMapWriter(self)
 517    }
 518
 519    fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: WrapPatch) {
 520        let buffer = wrap_snapshot.buffer_snapshot();
 521
 522        // Handle changing the last excerpt if it is empty.
 523        if buffer.trailing_excerpt_update_count()
 524            != self
 525                .wrap_snapshot
 526                .borrow()
 527                .buffer_snapshot()
 528                .trailing_excerpt_update_count()
 529        {
 530            let max_point = wrap_snapshot.max_point();
 531            let edit_start = wrap_snapshot.prev_row_boundary(max_point);
 532            let edit_end = max_point.row() + WrapRow(1);
 533            edits = edits.compose([WrapEdit {
 534                old: edit_start..edit_end,
 535                new: edit_start..edit_end,
 536            }]);
 537        }
 538
 539        let edits = edits.into_inner();
 540        if edits.is_empty() {
 541            return;
 542        }
 543
 544        let mut transforms = self.transforms.borrow_mut();
 545        let mut new_transforms = SumTree::default();
 546        let mut cursor = transforms.cursor::<WrapRow>(());
 547        let mut last_block_ix = 0;
 548        let mut blocks_in_edit = Vec::new();
 549        let mut edits = edits.into_iter().peekable();
 550
 551        while let Some(edit) = edits.next() {
 552            let mut old_start = edit.old.start;
 553            let mut new_start = edit.new.start;
 554
 555            // Only preserve transforms that:
 556            // * Strictly precedes this edit
 557            // * Isomorphic transforms that end *at* the start of the edit
 558            // * Below blocks that end at the start of the edit
 559            // However, if we hit a replace block that ends at the start of the edit we want to reconstruct it.
 560            new_transforms.append(cursor.slice(&old_start, Bias::Left), ());
 561            if let Some(transform) = cursor.item()
 562                && transform.summary.input_rows > WrapRow(0)
 563                && cursor.end() == old_start
 564                && transform.block.as_ref().is_none_or(|b| !b.is_replacement())
 565            {
 566                // Preserve the transform (push and next)
 567                new_transforms.push(transform.clone(), ());
 568                cursor.next();
 569
 570                // Preserve below blocks at end of edit
 571                while let Some(transform) = cursor.item() {
 572                    if transform.block.as_ref().is_some_and(|b| b.place_below()) {
 573                        new_transforms.push(transform.clone(), ());
 574                        cursor.next();
 575                    } else {
 576                        break;
 577                    }
 578                }
 579            }
 580
 581            // Ensure the edit starts at a transform boundary.
 582            // If the edit starts within an isomorphic transform, preserve its prefix
 583            // If the edit lands within a replacement block, expand the edit to include the start of the replaced input range
 584            let transform = cursor.item().unwrap();
 585            let transform_rows_before_edit = old_start - *cursor.start();
 586            if transform_rows_before_edit > RowDelta(0) {
 587                if transform.block.is_none() {
 588                    // Preserve any portion of the old isomorphic transform that precedes this edit.
 589                    push_isomorphic(
 590                        &mut new_transforms,
 591                        transform_rows_before_edit,
 592                        wrap_snapshot,
 593                    );
 594                } else {
 595                    // We landed within a block that replaces some lines, so we
 596                    // extend the edit to start at the beginning of the
 597                    // replacement.
 598                    debug_assert!(transform.summary.input_rows > WrapRow(0));
 599                    old_start -= transform_rows_before_edit;
 600                    new_start -= transform_rows_before_edit;
 601                }
 602            }
 603
 604            // Decide where the edit ends
 605            // * It should end at a transform boundary
 606            // * Coalesce edits that intersect the same transform
 607            let mut old_end = edit.old.end;
 608            let mut new_end = edit.new.end;
 609            loop {
 610                // Seek to the transform starting at or after the end of the edit
 611                cursor.seek(&old_end, Bias::Left);
 612                cursor.next();
 613
 614                // Extend edit to the end of the discarded transform so it is reconstructed in full
 615                let transform_rows_after_edit = *cursor.start() - old_end;
 616                old_end += transform_rows_after_edit;
 617                new_end += transform_rows_after_edit;
 618
 619                // Combine this edit with any subsequent edits that intersect the same transform.
 620                while let Some(next_edit) = edits.peek() {
 621                    if next_edit.old.start <= *cursor.start() {
 622                        old_end = next_edit.old.end;
 623                        new_end = next_edit.new.end;
 624                        cursor.seek(&old_end, Bias::Left);
 625                        cursor.next();
 626                        edits.next();
 627                    } else {
 628                        break;
 629                    }
 630                }
 631
 632                if *cursor.start() == old_end {
 633                    break;
 634                }
 635            }
 636
 637            // Discard below blocks at the end of the edit. They'll be reconstructed.
 638            while let Some(transform) = cursor.item() {
 639                if transform.block.as_ref().is_some_and(|b| b.place_below()) {
 640                    cursor.next();
 641                } else {
 642                    break;
 643                }
 644            }
 645
 646            // Find the blocks within this edited region.
 647            let new_buffer_start = wrap_snapshot.to_point(WrapPoint::new(new_start, 0), Bias::Left);
 648            let start_bound = Bound::Included(new_buffer_start);
 649            let start_block_ix =
 650                match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
 651                    probe
 652                        .start()
 653                        .to_point(buffer)
 654                        .cmp(&new_buffer_start)
 655                        // Move left until we find the index of the first block starting within this edit
 656                        .then(Ordering::Greater)
 657                }) {
 658                    Ok(ix) | Err(ix) => last_block_ix + ix,
 659                };
 660
 661            let end_bound;
 662            let end_block_ix = if new_end > wrap_snapshot.max_point().row() {
 663                end_bound = Bound::Unbounded;
 664                self.custom_blocks.len()
 665            } else {
 666                let new_buffer_end = wrap_snapshot.to_point(WrapPoint::new(new_end, 0), Bias::Left);
 667                end_bound = Bound::Excluded(new_buffer_end);
 668                match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
 669                    probe
 670                        .start()
 671                        .to_point(buffer)
 672                        .cmp(&new_buffer_end)
 673                        .then(Ordering::Greater)
 674                }) {
 675                    Ok(ix) | Err(ix) => start_block_ix + ix,
 676                }
 677            };
 678            last_block_ix = end_block_ix;
 679
 680            debug_assert!(blocks_in_edit.is_empty());
 681
 682            blocks_in_edit.extend(
 683                self.custom_blocks[start_block_ix..end_block_ix]
 684                    .iter()
 685                    .filter_map(|block| {
 686                        let placement = block.placement.to_wrap_row(wrap_snapshot)?;
 687                        if let BlockPlacement::Above(row) = placement
 688                            && row < new_start
 689                        {
 690                            return None;
 691                        }
 692                        Some((placement, Block::Custom(block.clone())))
 693                    }),
 694            );
 695
 696            blocks_in_edit.extend(self.header_and_footer_blocks(
 697                buffer,
 698                (start_bound, end_bound),
 699                wrap_snapshot,
 700            ));
 701
 702            BlockMap::sort_blocks(&mut blocks_in_edit);
 703
 704            // For each of these blocks, insert a new isomorphic transform preceding the block,
 705            // and then insert the block itself.
 706            let mut just_processed_folded_buffer = false;
 707            for (block_placement, block) in blocks_in_edit.drain(..) {
 708                let mut summary = TransformSummary {
 709                    input_rows: WrapRow(0),
 710                    output_rows: BlockRow(block.height()),
 711                    longest_row: BlockRow(0),
 712                    longest_row_chars: 0,
 713                };
 714
 715                let rows_before_block;
 716                match block_placement {
 717                    BlockPlacement::Above(position) => {
 718                        rows_before_block = position - new_transforms.summary().input_rows;
 719                        just_processed_folded_buffer = false;
 720                    }
 721                    BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
 722                        if just_processed_folded_buffer {
 723                            continue;
 724                        }
 725                        if position + RowDelta(1) < new_transforms.summary().input_rows {
 726                            continue;
 727                        }
 728                        rows_before_block =
 729                            (position + RowDelta(1)) - new_transforms.summary().input_rows;
 730                    }
 731                    BlockPlacement::Replace(range) => {
 732                        rows_before_block = *range.start() - new_transforms.summary().input_rows;
 733                        summary.input_rows = WrapRow(1) + (*range.end() - *range.start());
 734                        just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. });
 735                    }
 736                }
 737
 738                push_isomorphic(&mut new_transforms, rows_before_block, wrap_snapshot);
 739                new_transforms.push(
 740                    Transform {
 741                        summary,
 742                        block: Some(block),
 743                    },
 744                    (),
 745                );
 746            }
 747
 748            // Insert an isomorphic transform after the final block.
 749            let rows_after_last_block =
 750                RowDelta(new_end.0).saturating_sub(RowDelta(new_transforms.summary().input_rows.0));
 751            push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot);
 752        }
 753
 754        new_transforms.append(cursor.suffix(), ());
 755        debug_assert_eq!(
 756            new_transforms.summary().input_rows,
 757            wrap_snapshot.max_point().row() + WrapRow(1)
 758        );
 759
 760        drop(cursor);
 761        *transforms = new_transforms;
 762    }
 763
 764    pub fn replace_blocks(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
 765        for block in &mut self.custom_blocks {
 766            if let Some(render) = renderers.remove(&block.id) {
 767                *block.render.lock() = render;
 768            }
 769        }
 770    }
 771
 772    fn header_and_footer_blocks<'a, R, T>(
 773        &'a self,
 774        buffer: &'a multi_buffer::MultiBufferSnapshot,
 775        range: R,
 776        wrap_snapshot: &'a WrapSnapshot,
 777    ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
 778    where
 779        R: RangeBounds<T>,
 780        T: multi_buffer::ToOffset,
 781    {
 782        let mut boundaries = buffer.excerpt_boundaries_in_range(range).peekable();
 783
 784        std::iter::from_fn(move || {
 785            loop {
 786                let excerpt_boundary = boundaries.next()?;
 787                let wrap_row = wrap_snapshot
 788                    .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
 789                    .row();
 790
 791                let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
 792                    (None, next) => Some(next.buffer_id),
 793                    (Some(prev), next) => {
 794                        if prev.buffer_id != next.buffer_id {
 795                            Some(next.buffer_id)
 796                        } else {
 797                            None
 798                        }
 799                    }
 800                };
 801
 802                let mut height = 0;
 803
 804                if let Some(new_buffer_id) = new_buffer_id {
 805                    let first_excerpt = excerpt_boundary.next.clone();
 806                    if self.buffers_with_disabled_headers.contains(&new_buffer_id) {
 807                        continue;
 808                    }
 809                    if self.folded_buffers.contains(&new_buffer_id) && buffer.show_headers() {
 810                        let mut last_excerpt_end_row = first_excerpt.end_row;
 811
 812                        while let Some(next_boundary) = boundaries.peek() {
 813                            if next_boundary.next.buffer_id == new_buffer_id {
 814                                last_excerpt_end_row = next_boundary.next.end_row;
 815                            } else {
 816                                break;
 817                            }
 818
 819                            boundaries.next();
 820                        }
 821
 822                        let wrap_end_row = wrap_snapshot
 823                            .make_wrap_point(
 824                                Point::new(
 825                                    last_excerpt_end_row.0,
 826                                    buffer.line_len(last_excerpt_end_row),
 827                                ),
 828                                Bias::Right,
 829                            )
 830                            .row();
 831
 832                        return Some((
 833                            BlockPlacement::Replace(wrap_row..=wrap_end_row),
 834                            Block::FoldedBuffer {
 835                                height: height + self.buffer_header_height,
 836                                first_excerpt,
 837                            },
 838                        ));
 839                    }
 840                }
 841
 842                let starts_new_buffer = new_buffer_id.is_some();
 843                let block = if starts_new_buffer && buffer.show_headers() {
 844                    height += self.buffer_header_height;
 845                    Block::BufferHeader {
 846                        excerpt: excerpt_boundary.next,
 847                        height,
 848                    }
 849                } else if excerpt_boundary.prev.is_some() {
 850                    height += self.excerpt_header_height;
 851                    Block::ExcerptBoundary {
 852                        excerpt: excerpt_boundary.next,
 853                        height,
 854                    }
 855                } else {
 856                    continue;
 857                };
 858
 859                return Some((BlockPlacement::Above(wrap_row), block));
 860            }
 861        })
 862    }
 863
 864    fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
 865        blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
 866            placement_a
 867                .start()
 868                .cmp(placement_b.start())
 869                .then_with(|| placement_b.end().cmp(placement_a.end()))
 870                .then_with(|| placement_a.tie_break().cmp(&placement_b.tie_break()))
 871                .then_with(|| {
 872                    if block_a.is_header() {
 873                        Ordering::Less
 874                    } else if block_b.is_header() {
 875                        Ordering::Greater
 876                    } else {
 877                        Ordering::Equal
 878                    }
 879                })
 880                .then_with(|| match (block_a, block_b) {
 881                    (
 882                        Block::ExcerptBoundary {
 883                            excerpt: excerpt_a, ..
 884                        }
 885                        | Block::BufferHeader {
 886                            excerpt: excerpt_a, ..
 887                        },
 888                        Block::ExcerptBoundary {
 889                            excerpt: excerpt_b, ..
 890                        }
 891                        | Block::BufferHeader {
 892                            excerpt: excerpt_b, ..
 893                        },
 894                    ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
 895                    (
 896                        Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
 897                        Block::Custom(_),
 898                    ) => Ordering::Less,
 899                    (
 900                        Block::Custom(_),
 901                        Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
 902                    ) => Ordering::Greater,
 903                    (Block::Custom(block_a), Block::Custom(block_b)) => block_a
 904                        .priority
 905                        .cmp(&block_b.priority)
 906                        .then_with(|| block_a.id.cmp(&block_b.id)),
 907                    _ => {
 908                        unreachable!()
 909                    }
 910                })
 911        });
 912        blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
 913            (BlockPlacement::Replace(range), BlockPlacement::Above(row))
 914            | (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
 915            (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
 916                if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
 917                    left.0 = BlockPlacement::Replace(
 918                        *range_a.start()..=*range_a.end().max(range_b.end()),
 919                    );
 920                    true
 921                } else {
 922                    false
 923                }
 924            }
 925            _ => false,
 926        });
 927    }
 928}
 929
 930fn push_isomorphic(tree: &mut SumTree<Transform>, rows: RowDelta, wrap_snapshot: &WrapSnapshot) {
 931    if rows == RowDelta(0) {
 932        return;
 933    }
 934
 935    let wrap_row_start = tree.summary().input_rows;
 936    let wrap_row_end = wrap_row_start + rows;
 937    let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
 938    let summary = TransformSummary {
 939        input_rows: WrapRow(rows.0),
 940        output_rows: BlockRow(rows.0),
 941        longest_row: BlockRow(wrap_summary.longest_row),
 942        longest_row_chars: wrap_summary.longest_row_chars,
 943    };
 944    let mut merged = false;
 945    tree.update_last(
 946        |last_transform| {
 947            if last_transform.block.is_none() {
 948                last_transform.summary.add_summary(&summary);
 949                merged = true;
 950            }
 951        },
 952        (),
 953    );
 954    if !merged {
 955        tree.push(
 956            Transform {
 957                summary,
 958                block: None,
 959            },
 960            (),
 961        );
 962    }
 963}
 964
 965impl BlockPoint {
 966    pub fn new(row: BlockRow, column: u32) -> Self {
 967        Self(Point::new(row.0, column))
 968    }
 969}
 970
 971impl Deref for BlockPoint {
 972    type Target = Point;
 973
 974    fn deref(&self) -> &Self::Target {
 975        &self.0
 976    }
 977}
 978
 979impl std::ops::DerefMut for BlockPoint {
 980    fn deref_mut(&mut self) -> &mut Self::Target {
 981        &mut self.0
 982    }
 983}
 984
 985impl Deref for BlockMapReader<'_> {
 986    type Target = BlockSnapshot;
 987
 988    fn deref(&self) -> &Self::Target {
 989        &self.snapshot
 990    }
 991}
 992
 993impl DerefMut for BlockMapReader<'_> {
 994    fn deref_mut(&mut self) -> &mut Self::Target {
 995        &mut self.snapshot
 996    }
 997}
 998
 999impl BlockMapReader<'_> {
1000    pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
1001        let block = self.blocks.iter().find(|block| block.id == block_id)?;
1002        let buffer_row = block
1003            .start()
1004            .to_point(self.wrap_snapshot.buffer_snapshot())
1005            .row;
1006        let wrap_row = self
1007            .wrap_snapshot
1008            .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
1009            .row();
1010        let start_wrap_row = self
1011            .wrap_snapshot
1012            .prev_row_boundary(WrapPoint::new(wrap_row, 0));
1013        let end_wrap_row = self
1014            .wrap_snapshot
1015            .next_row_boundary(WrapPoint::new(wrap_row, 0))
1016            .unwrap_or(self.wrap_snapshot.max_point().row() + WrapRow(1));
1017
1018        let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
1019        cursor.seek(&start_wrap_row, Bias::Left);
1020        while let Some(transform) = cursor.item() {
1021            if cursor.start().0 > end_wrap_row {
1022                break;
1023            }
1024
1025            if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id())
1026                && id == block_id
1027            {
1028                return Some(cursor.start().1);
1029            }
1030            cursor.next();
1031        }
1032
1033        None
1034    }
1035}
1036
1037impl BlockMapWriter<'_> {
1038    pub fn insert(
1039        &mut self,
1040        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1041    ) -> Vec<CustomBlockId> {
1042        let blocks = blocks.into_iter();
1043        let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
1044        let mut edits = Patch::default();
1045        let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1046        let buffer = wrap_snapshot.buffer_snapshot();
1047
1048        let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1049        for block in blocks {
1050            if let BlockPlacement::Replace(_) = &block.placement {
1051                debug_assert!(block.height.unwrap() > 0);
1052            }
1053
1054            let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
1055            ids.push(id);
1056
1057            let start = block.placement.start().to_point(buffer);
1058            let end = block.placement.end().to_point(buffer);
1059            let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1060            let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1061
1062            let (start_row, end_row) = {
1063                previous_wrap_row_range.take_if(|range| {
1064                    !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1065                });
1066                let range = previous_wrap_row_range.get_or_insert_with(|| {
1067                    let start_row =
1068                        wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1069                    let end_row = wrap_snapshot
1070                        .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1071                        .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1072                    start_row..end_row
1073                });
1074                (range.start, range.end)
1075            };
1076            let block_ix = match self
1077                .0
1078                .custom_blocks
1079                .binary_search_by(|probe| probe.placement.cmp(&block.placement, buffer))
1080            {
1081                Ok(ix) | Err(ix) => ix,
1082            };
1083            let new_block = Arc::new(CustomBlock {
1084                id,
1085                placement: block.placement,
1086                height: block.height,
1087                render: Arc::new(Mutex::new(block.render)),
1088                style: block.style,
1089                priority: block.priority,
1090            });
1091            self.0.custom_blocks.insert(block_ix, new_block.clone());
1092            self.0.custom_blocks_by_id.insert(id, new_block);
1093
1094            edits = edits.compose([Edit {
1095                old: start_row..end_row,
1096                new: start_row..end_row,
1097            }]);
1098        }
1099
1100        self.0.sync(wrap_snapshot, edits);
1101        ids
1102    }
1103
1104    pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
1105        let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1106        let buffer = wrap_snapshot.buffer_snapshot();
1107        let mut edits = Patch::default();
1108        let mut last_block_buffer_row = None;
1109
1110        for block in &mut self.0.custom_blocks {
1111            if let Some(new_height) = heights.remove(&block.id) {
1112                if let BlockPlacement::Replace(_) = &block.placement {
1113                    debug_assert!(new_height > 0);
1114                }
1115
1116                if block.height != Some(new_height) {
1117                    let new_block = CustomBlock {
1118                        id: block.id,
1119                        placement: block.placement.clone(),
1120                        height: Some(new_height),
1121                        style: block.style,
1122                        render: block.render.clone(),
1123                        priority: block.priority,
1124                    };
1125                    let new_block = Arc::new(new_block);
1126                    *block = new_block.clone();
1127                    self.0.custom_blocks_by_id.insert(block.id, new_block);
1128
1129                    let start_row = block.placement.start().to_point(buffer).row;
1130                    let end_row = block.placement.end().to_point(buffer).row;
1131                    if last_block_buffer_row != Some(end_row) {
1132                        last_block_buffer_row = Some(end_row);
1133                        let start_wrap_row = wrap_snapshot
1134                            .make_wrap_point(Point::new(start_row, 0), Bias::Left)
1135                            .row();
1136                        let end_wrap_row = wrap_snapshot
1137                            .make_wrap_point(Point::new(end_row, 0), Bias::Left)
1138                            .row();
1139                        let start =
1140                            wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1141                        let end = wrap_snapshot
1142                            .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1143                            .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1144                        edits.push(Edit {
1145                            old: start..end,
1146                            new: start..end,
1147                        })
1148                    }
1149                }
1150            }
1151        }
1152
1153        self.0.sync(wrap_snapshot, edits);
1154    }
1155
1156    pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
1157        let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1158        let buffer = wrap_snapshot.buffer_snapshot();
1159        let mut edits = Patch::default();
1160        let mut last_block_buffer_row = None;
1161        let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1162        self.0.custom_blocks.retain(|block| {
1163            if block_ids.contains(&block.id) {
1164                let start = block.placement.start().to_point(buffer);
1165                let end = block.placement.end().to_point(buffer);
1166                if last_block_buffer_row != Some(end.row) {
1167                    last_block_buffer_row = Some(end.row);
1168                    let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1169                    let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1170                    let (start_row, end_row) = {
1171                        previous_wrap_row_range.take_if(|range| {
1172                            !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1173                        });
1174                        let range = previous_wrap_row_range.get_or_insert_with(|| {
1175                            let start_row =
1176                                wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1177                            let end_row = wrap_snapshot
1178                                .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1179                                .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1180                            start_row..end_row
1181                        });
1182                        (range.start, range.end)
1183                    };
1184
1185                    edits.push(Edit {
1186                        old: start_row..end_row,
1187                        new: start_row..end_row,
1188                    })
1189                }
1190                false
1191            } else {
1192                true
1193            }
1194        });
1195        self.0
1196            .custom_blocks_by_id
1197            .retain(|id, _| !block_ids.contains(id));
1198        self.0.sync(wrap_snapshot, edits);
1199    }
1200
1201    pub fn remove_intersecting_replace_blocks(
1202        &mut self,
1203        ranges: impl IntoIterator<Item = Range<usize>>,
1204        inclusive: bool,
1205    ) {
1206        let wrap_snapshot = self.0.wrap_snapshot.borrow();
1207        let mut blocks_to_remove = HashSet::default();
1208        for range in ranges {
1209            for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1210                if matches!(block.placement, BlockPlacement::Replace(_)) {
1211                    blocks_to_remove.insert(block.id);
1212                }
1213            }
1214        }
1215        drop(wrap_snapshot);
1216        self.remove(blocks_to_remove);
1217    }
1218
1219    pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1220        self.0.buffers_with_disabled_headers.insert(buffer_id);
1221    }
1222
1223    pub fn fold_buffers(
1224        &mut self,
1225        buffer_ids: impl IntoIterator<Item = BufferId>,
1226        multi_buffer: &MultiBuffer,
1227        cx: &App,
1228    ) {
1229        self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
1230    }
1231
1232    pub fn unfold_buffers(
1233        &mut self,
1234        buffer_ids: impl IntoIterator<Item = BufferId>,
1235        multi_buffer: &MultiBuffer,
1236        cx: &App,
1237    ) {
1238        self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
1239    }
1240
1241    fn fold_or_unfold_buffers(
1242        &mut self,
1243        fold: bool,
1244        buffer_ids: impl IntoIterator<Item = BufferId>,
1245        multi_buffer: &MultiBuffer,
1246        cx: &App,
1247    ) {
1248        let mut ranges = Vec::new();
1249        for buffer_id in buffer_ids {
1250            if fold {
1251                self.0.folded_buffers.insert(buffer_id);
1252            } else {
1253                self.0.folded_buffers.remove(&buffer_id);
1254            }
1255            ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
1256        }
1257        ranges.sort_unstable_by_key(|range| range.start);
1258
1259        let mut edits = Patch::default();
1260        let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
1261        for range in ranges {
1262            let last_edit_row = cmp::min(
1263                wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + WrapRow(1),
1264                wrap_snapshot.max_point().row(),
1265            ) + WrapRow(1);
1266            let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1267            edits.push(Edit {
1268                old: range.clone(),
1269                new: range,
1270            });
1271        }
1272
1273        self.0.sync(&wrap_snapshot, edits);
1274    }
1275
1276    fn blocks_intersecting_buffer_range(
1277        &self,
1278        range: Range<usize>,
1279        inclusive: bool,
1280    ) -> &[Arc<CustomBlock>] {
1281        if range.is_empty() && !inclusive {
1282            return &[];
1283        }
1284        let wrap_snapshot = self.0.wrap_snapshot.borrow();
1285        let buffer = wrap_snapshot.buffer_snapshot();
1286
1287        let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1288            let block_end = block.end().to_offset(buffer);
1289            block_end.cmp(&range.start).then(Ordering::Greater)
1290        }) {
1291            Ok(ix) | Err(ix) => ix,
1292        };
1293        let end_block_ix = match self.0.custom_blocks[start_block_ix..].binary_search_by(|block| {
1294            let block_start = block.start().to_offset(buffer);
1295            block_start.cmp(&range.end).then(if inclusive {
1296                Ordering::Less
1297            } else {
1298                Ordering::Greater
1299            })
1300        }) {
1301            Ok(ix) | Err(ix) => ix,
1302        };
1303
1304        &self.0.custom_blocks[start_block_ix..][..end_block_ix]
1305    }
1306}
1307
1308impl BlockSnapshot {
1309    #[cfg(test)]
1310    pub fn text(&self) -> String {
1311        self.chunks(
1312            BlockRow(0)..self.transforms.summary().output_rows,
1313            false,
1314            false,
1315            Highlights::default(),
1316        )
1317        .map(|chunk| chunk.text)
1318        .collect()
1319    }
1320
1321    pub(crate) fn chunks<'a>(
1322        &'a self,
1323        rows: Range<BlockRow>,
1324        language_aware: bool,
1325        masked: bool,
1326        highlights: Highlights<'a>,
1327    ) -> BlockChunks<'a> {
1328        let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1329
1330        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1331        cursor.seek(&rows.start, Bias::Right);
1332        let transform_output_start = cursor.start().0;
1333        let transform_input_start = cursor.start().1;
1334
1335        let mut input_start = transform_input_start;
1336        let mut input_end = transform_input_start;
1337        if let Some(transform) = cursor.item()
1338            && transform.block.is_none()
1339        {
1340            input_start += rows.start - transform_output_start;
1341            input_end += cmp::min(
1342                rows.end - transform_output_start,
1343                RowDelta(transform.summary.input_rows.0),
1344            );
1345        }
1346
1347        BlockChunks {
1348            input_chunks: self.wrap_snapshot.chunks(
1349                input_start..input_end,
1350                language_aware,
1351                highlights,
1352            ),
1353            input_chunk: Default::default(),
1354            transforms: cursor,
1355            output_row: rows.start,
1356            line_count_overflow: RowDelta(0),
1357            max_output_row,
1358            masked,
1359        }
1360    }
1361
1362    pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
1363        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1364        cursor.seek(&start_row, Bias::Right);
1365        let Dimensions(output_start, input_start, _) = cursor.start();
1366        let overshoot = if cursor
1367            .item()
1368            .is_some_and(|transform| transform.block.is_none())
1369        {
1370            start_row - *output_start
1371        } else {
1372            RowDelta(0)
1373        };
1374        let input_start_row = *input_start + overshoot;
1375        BlockRows {
1376            transforms: cursor,
1377            input_rows: self.wrap_snapshot.row_infos(input_start_row),
1378            output_row: start_row,
1379            started: false,
1380        }
1381    }
1382
1383    pub fn blocks_in_range(
1384        &self,
1385        rows: Range<BlockRow>,
1386    ) -> impl Iterator<Item = (BlockRow, &Block)> {
1387        let mut cursor = self.transforms.cursor::<BlockRow>(());
1388        cursor.seek(&rows.start, Bias::Left);
1389        while *cursor.start() < rows.start && cursor.end() <= rows.start {
1390            cursor.next();
1391        }
1392
1393        std::iter::from_fn(move || {
1394            while let Some(transform) = cursor.item() {
1395                let start_row = *cursor.start();
1396                if start_row > rows.end
1397                    || (start_row == rows.end
1398                        && transform
1399                            .block
1400                            .as_ref()
1401                            .is_some_and(|block| block.height() > 0))
1402                {
1403                    break;
1404                }
1405                if let Some(block) = &transform.block {
1406                    cursor.next();
1407                    return Some((start_row, block));
1408                } else {
1409                    cursor.next();
1410                }
1411            }
1412            None
1413        })
1414    }
1415
1416    pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
1417        let top_row = position as u32;
1418        let mut cursor = self.transforms.cursor::<BlockRow>(());
1419        cursor.seek(&BlockRow(top_row), Bias::Right);
1420
1421        while let Some(transform) = cursor.item() {
1422            match &transform.block {
1423                Some(
1424                    Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
1425                ) => {
1426                    return Some(StickyHeaderExcerpt { excerpt });
1427                }
1428                Some(block) if block.is_buffer_header() => return None,
1429                _ => {
1430                    cursor.prev();
1431                    continue;
1432                }
1433            }
1434        }
1435
1436        None
1437    }
1438
1439    pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1440        let buffer = self.wrap_snapshot.buffer_snapshot();
1441        let wrap_point = match block_id {
1442            BlockId::Custom(custom_block_id) => {
1443                let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1444                return Some(Block::Custom(custom_block.clone()));
1445            }
1446            BlockId::ExcerptBoundary(next_excerpt_id) => {
1447                let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
1448                self.wrap_snapshot
1449                    .make_wrap_point(excerpt_range.start, Bias::Left)
1450            }
1451            BlockId::FoldedBuffer(excerpt_id) => self
1452                .wrap_snapshot
1453                .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
1454        };
1455        let wrap_row = wrap_point.row();
1456
1457        let mut cursor = self.transforms.cursor::<WrapRow>(());
1458        cursor.seek(&wrap_row, Bias::Left);
1459
1460        while let Some(transform) = cursor.item() {
1461            if let Some(block) = transform.block.as_ref() {
1462                if block.id() == block_id {
1463                    return Some(block.clone());
1464                }
1465            } else if *cursor.start() > wrap_row {
1466                break;
1467            }
1468
1469            cursor.next();
1470        }
1471
1472        None
1473    }
1474
1475    pub fn max_point(&self) -> BlockPoint {
1476        let row = self
1477            .transforms
1478            .summary()
1479            .output_rows
1480            .saturating_sub(RowDelta(1));
1481        BlockPoint::new(row, self.line_len(row))
1482    }
1483
1484    pub fn longest_row(&self) -> BlockRow {
1485        self.transforms.summary().longest_row
1486    }
1487
1488    pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
1489        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1490        cursor.seek(&range.start, Bias::Right);
1491
1492        let mut longest_row = range.start;
1493        let mut longest_row_chars = 0;
1494        if let Some(transform) = cursor.item() {
1495            if transform.block.is_none() {
1496                let &Dimensions(output_start, input_start, _) = cursor.start();
1497                let overshoot = range.start - output_start;
1498                let wrap_start_row = input_start + WrapRow(overshoot.0);
1499                let wrap_end_row = cmp::min(
1500                    input_start + WrapRow((range.end - output_start).0),
1501                    cursor.end().1,
1502                );
1503                let summary = self
1504                    .wrap_snapshot
1505                    .text_summary_for_range(wrap_start_row..wrap_end_row);
1506                longest_row = BlockRow(range.start.0 + summary.longest_row);
1507                longest_row_chars = summary.longest_row_chars;
1508            }
1509            cursor.next();
1510        }
1511
1512        let cursor_start_row = cursor.start().0;
1513        if range.end > cursor_start_row {
1514            let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
1515            if summary.longest_row_chars > longest_row_chars {
1516                longest_row = cursor_start_row + summary.longest_row;
1517                longest_row_chars = summary.longest_row_chars;
1518            }
1519
1520            if let Some(transform) = cursor.item()
1521                && transform.block.is_none()
1522            {
1523                let &Dimensions(output_start, input_start, _) = cursor.start();
1524                let overshoot = range.end - output_start;
1525                let wrap_start_row = input_start;
1526                let wrap_end_row = input_start + overshoot;
1527                let summary = self
1528                    .wrap_snapshot
1529                    .text_summary_for_range(wrap_start_row..wrap_end_row);
1530                if summary.longest_row_chars > longest_row_chars {
1531                    longest_row = output_start + RowDelta(summary.longest_row);
1532                }
1533            }
1534        }
1535
1536        longest_row
1537    }
1538
1539    pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1540        let (start, _, item) =
1541            self.transforms
1542                .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
1543        if let Some(transform) = item {
1544            let Dimensions(output_start, input_start, _) = start;
1545            let overshoot = row - output_start;
1546            if transform.block.is_some() {
1547                0
1548            } else {
1549                self.wrap_snapshot.line_len(input_start + overshoot)
1550            }
1551        } else if row == BlockRow(0) {
1552            0
1553        } else {
1554            panic!("row out of range");
1555        }
1556    }
1557
1558    pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1559        let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
1560        item.is_some_and(|t| t.block.is_some())
1561    }
1562
1563    pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
1564        let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
1565        let Some(transform) = item else {
1566            return false;
1567        };
1568        matches!(transform.block, Some(Block::FoldedBuffer { .. }))
1569    }
1570
1571    pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
1572        let wrap_point = self
1573            .wrap_snapshot
1574            .make_wrap_point(Point::new(row.0, 0), Bias::Left);
1575        let (_, _, item) = self
1576            .transforms
1577            .find::<WrapRow, _>((), &wrap_point.row(), Bias::Right);
1578        item.is_some_and(|transform| {
1579            transform
1580                .block
1581                .as_ref()
1582                .is_some_and(|block| block.is_replacement())
1583        })
1584    }
1585
1586    pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1587        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1588        cursor.seek(&BlockRow(point.row), Bias::Right);
1589
1590        let max_input_row = self.transforms.summary().input_rows;
1591        let mut search_left = (bias == Bias::Left && cursor.start().1 > WrapRow(0))
1592            || cursor.end().1 == max_input_row;
1593        let mut reversed = false;
1594
1595        loop {
1596            if let Some(transform) = cursor.item() {
1597                let Dimensions(output_start_row, input_start_row, _) = cursor.start();
1598                let Dimensions(output_end_row, input_end_row, _) = cursor.end();
1599                let output_start = Point::new(output_start_row.0, 0);
1600                let input_start = Point::new(input_start_row.0, 0);
1601                let input_end = Point::new(input_end_row.0, 0);
1602
1603                match transform.block.as_ref() {
1604                    Some(block) => {
1605                        if block.is_replacement()
1606                            && (((bias == Bias::Left || search_left) && output_start <= point.0)
1607                                || (!search_left && output_start >= point.0))
1608                        {
1609                            return BlockPoint(output_start);
1610                        }
1611                    }
1612                    None => {
1613                        let input_point = if point.row >= output_end_row.0 {
1614                            let line_len = self.wrap_snapshot.line_len(input_end_row - RowDelta(1));
1615                            self.wrap_snapshot.clip_point(
1616                                WrapPoint::new(input_end_row - RowDelta(1), line_len),
1617                                bias,
1618                            )
1619                        } else {
1620                            let output_overshoot = point.0.saturating_sub(output_start);
1621                            self.wrap_snapshot
1622                                .clip_point(WrapPoint(input_start + output_overshoot), bias)
1623                        };
1624
1625                        if (input_start..input_end).contains(&input_point.0) {
1626                            let input_overshoot = input_point.0.saturating_sub(input_start);
1627                            return BlockPoint(output_start + input_overshoot);
1628                        }
1629                    }
1630                }
1631
1632                if search_left {
1633                    cursor.prev();
1634                } else {
1635                    cursor.next();
1636                }
1637            } else if reversed {
1638                return self.max_point();
1639            } else {
1640                reversed = true;
1641                search_left = !search_left;
1642                cursor.seek(&BlockRow(point.row), Bias::Right);
1643            }
1644        }
1645    }
1646
1647    pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1648        let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
1649            (),
1650            &wrap_point.row(),
1651            Bias::Right,
1652        );
1653        if let Some(transform) = item {
1654            if transform.block.is_some() {
1655                BlockPoint::new(start.1, 0)
1656            } else {
1657                let Dimensions(input_start_row, output_start_row, _) = start;
1658                let input_start = Point::new(input_start_row.0, 0);
1659                let output_start = Point::new(output_start_row.0, 0);
1660                let input_overshoot = wrap_point.0 - input_start;
1661                BlockPoint(output_start + input_overshoot)
1662            }
1663        } else {
1664            self.max_point()
1665        }
1666    }
1667
1668    pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
1669        let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
1670            (),
1671            &BlockRow(block_point.row),
1672            Bias::Right,
1673        );
1674        if let Some(transform) = item {
1675            match transform.block.as_ref() {
1676                Some(block) => {
1677                    if block.place_below() {
1678                        let wrap_row = start.1 - RowDelta(1);
1679                        WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1680                    } else if block.place_above() {
1681                        WrapPoint::new(start.1, 0)
1682                    } else if bias == Bias::Left {
1683                        WrapPoint::new(start.1, 0)
1684                    } else {
1685                        let wrap_row = end.1 - RowDelta(1);
1686                        WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1687                    }
1688                }
1689                None => {
1690                    let overshoot = block_point.row() - start.0;
1691                    let wrap_row = start.1 + RowDelta(overshoot.0);
1692                    WrapPoint::new(wrap_row, block_point.column)
1693                }
1694            }
1695        } else {
1696            self.wrap_snapshot.max_point()
1697        }
1698    }
1699}
1700
1701impl BlockChunks<'_> {
1702    /// Go to the next transform
1703    fn advance(&mut self) {
1704        self.input_chunk = Chunk::default();
1705        self.transforms.next();
1706        while let Some(transform) = self.transforms.item() {
1707            if transform
1708                .block
1709                .as_ref()
1710                .is_some_and(|block| block.height() == 0)
1711            {
1712                self.transforms.next();
1713            } else {
1714                break;
1715            }
1716        }
1717
1718        if self
1719            .transforms
1720            .item()
1721            .is_some_and(|transform| transform.block.is_none())
1722        {
1723            let start_input_row = self.transforms.start().1;
1724            let start_output_row = self.transforms.start().0;
1725            if start_output_row < self.max_output_row {
1726                let end_input_row = cmp::min(
1727                    self.transforms.end().1,
1728                    start_input_row + (self.max_output_row - start_output_row),
1729                );
1730                self.input_chunks.seek(start_input_row..end_input_row);
1731            }
1732        }
1733    }
1734}
1735
1736pub struct StickyHeaderExcerpt<'a> {
1737    pub excerpt: &'a ExcerptInfo,
1738}
1739
1740impl<'a> Iterator for BlockChunks<'a> {
1741    type Item = Chunk<'a>;
1742
1743    fn next(&mut self) -> Option<Self::Item> {
1744        if self.output_row >= self.max_output_row {
1745            return None;
1746        }
1747
1748        if self.line_count_overflow > RowDelta(0) {
1749            let lines = self.line_count_overflow.0.min(u128::BITS);
1750            self.line_count_overflow.0 -= lines;
1751            self.output_row += RowDelta(lines);
1752            return Some(Chunk {
1753                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines as usize]) },
1754                chars: 1u128.unbounded_shl(lines).wrapping_sub(1),
1755                ..Default::default()
1756            });
1757        }
1758
1759        let transform = self.transforms.item()?;
1760        if transform.block.is_some() {
1761            let block_start = self.transforms.start().0;
1762            let mut block_end = self.transforms.end().0;
1763            self.advance();
1764            if self.transforms.item().is_none() {
1765                block_end -= RowDelta(1);
1766            }
1767
1768            let start_in_block = self.output_row - block_start;
1769            let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1770            let line_count = end_in_block - start_in_block;
1771            let lines = RowDelta(line_count.0.min(u128::BITS));
1772            self.line_count_overflow = line_count - lines;
1773            self.output_row += lines;
1774
1775            return Some(Chunk {
1776                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines.0 as usize]) },
1777                chars: 1u128.unbounded_shl(lines.0).wrapping_sub(1),
1778                ..Default::default()
1779            });
1780        }
1781
1782        if self.input_chunk.text.is_empty() {
1783            if let Some(input_chunk) = self.input_chunks.next() {
1784                self.input_chunk = input_chunk;
1785            } else {
1786                if self.output_row < self.max_output_row {
1787                    self.output_row.0 += 1;
1788                    self.advance();
1789                    if self.transforms.item().is_some() {
1790                        return Some(Chunk {
1791                            text: "\n",
1792                            chars: 1,
1793                            ..Default::default()
1794                        });
1795                    }
1796                }
1797                return None;
1798            }
1799        }
1800
1801        let transform_end = self.transforms.end().0;
1802        let (prefix_rows, prefix_bytes) =
1803            offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1804        self.output_row += prefix_rows;
1805
1806        let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1807        self.input_chunk.text = suffix;
1808        self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
1809        self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
1810
1811        let mut tabs = self.input_chunk.tabs;
1812        let mut chars = self.input_chunk.chars;
1813
1814        if self.masked {
1815            // Not great for multibyte text because to keep cursor math correct we
1816            // need to have the same number of chars in the input as output.
1817            let chars_count = prefix.chars().count();
1818            let bullet_len = chars_count;
1819            prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
1820            chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
1821            tabs = 0;
1822        }
1823
1824        let chunk = Chunk {
1825            text: prefix,
1826            tabs,
1827            chars,
1828            ..self.input_chunk.clone()
1829        };
1830
1831        if self.output_row == transform_end {
1832            self.advance();
1833        }
1834
1835        Some(chunk)
1836    }
1837}
1838
1839impl Iterator for BlockRows<'_> {
1840    type Item = RowInfo;
1841
1842    fn next(&mut self) -> Option<Self::Item> {
1843        if self.started {
1844            self.output_row.0 += 1;
1845        } else {
1846            self.started = true;
1847        }
1848
1849        if self.output_row >= self.transforms.end().0 {
1850            self.transforms.next();
1851            while let Some(transform) = self.transforms.item() {
1852                if transform
1853                    .block
1854                    .as_ref()
1855                    .is_some_and(|block| block.height() == 0)
1856                {
1857                    self.transforms.next();
1858                } else {
1859                    break;
1860                }
1861            }
1862
1863            let transform = self.transforms.item()?;
1864            if transform
1865                .block
1866                .as_ref()
1867                .is_none_or(|block| block.is_replacement())
1868            {
1869                self.input_rows.seek(self.transforms.start().1);
1870            }
1871        }
1872
1873        let transform = self.transforms.item()?;
1874        if let Some(block) = transform.block.as_ref() {
1875            if block.is_replacement() && self.transforms.start().0 == self.output_row {
1876                if matches!(block, Block::FoldedBuffer { .. }) {
1877                    Some(RowInfo::default())
1878                } else {
1879                    Some(self.input_rows.next().unwrap())
1880                }
1881            } else {
1882                Some(RowInfo::default())
1883            }
1884        } else {
1885            Some(self.input_rows.next().unwrap())
1886        }
1887    }
1888}
1889
1890impl sum_tree::Item for Transform {
1891    type Summary = TransformSummary;
1892
1893    fn summary(&self, _cx: ()) -> Self::Summary {
1894        self.summary.clone()
1895    }
1896}
1897
1898impl sum_tree::ContextLessSummary for TransformSummary {
1899    fn zero() -> Self {
1900        Default::default()
1901    }
1902
1903    fn add_summary(&mut self, summary: &Self) {
1904        if summary.longest_row_chars > self.longest_row_chars {
1905            self.longest_row = self.output_rows + summary.longest_row;
1906            self.longest_row_chars = summary.longest_row_chars;
1907        }
1908        self.input_rows += summary.input_rows;
1909        self.output_rows += summary.output_rows;
1910    }
1911}
1912
1913impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1914    fn zero(_cx: ()) -> Self {
1915        Default::default()
1916    }
1917
1918    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1919        *self += summary.input_rows;
1920    }
1921}
1922
1923impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1924    fn zero(_cx: ()) -> Self {
1925        Default::default()
1926    }
1927
1928    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1929        *self += summary.output_rows;
1930    }
1931}
1932
1933impl Deref for BlockContext<'_, '_> {
1934    type Target = App;
1935
1936    fn deref(&self) -> &Self::Target {
1937        self.app
1938    }
1939}
1940
1941impl DerefMut for BlockContext<'_, '_> {
1942    fn deref_mut(&mut self) -> &mut Self::Target {
1943        self.app
1944    }
1945}
1946
1947impl CustomBlock {
1948    pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1949        self.render.lock()(cx)
1950    }
1951
1952    pub fn start(&self) -> Anchor {
1953        *self.placement.start()
1954    }
1955
1956    pub fn end(&self) -> Anchor {
1957        *self.placement.end()
1958    }
1959
1960    pub fn style(&self) -> BlockStyle {
1961        self.style
1962    }
1963}
1964
1965impl Debug for CustomBlock {
1966    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1967        f.debug_struct("Block")
1968            .field("id", &self.id)
1969            .field("placement", &self.placement)
1970            .field("height", &self.height)
1971            .field("style", &self.style)
1972            .field("priority", &self.priority)
1973            .finish_non_exhaustive()
1974    }
1975}
1976
1977// Count the number of bytes prior to a target point. If the string doesn't contain the target
1978// point, return its total extent. Otherwise return the target point itself.
1979fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) {
1980    let mut row = 0;
1981    let mut offset = 0;
1982    for (ix, line) in s.split('\n').enumerate() {
1983        if ix > 0 {
1984            row += 1;
1985            offset += 1;
1986        }
1987        if row >= target.0 {
1988            break;
1989        }
1990        offset += line.len();
1991    }
1992    (RowDelta(row), offset)
1993}
1994
1995#[cfg(test)]
1996mod tests {
1997    use super::*;
1998    use crate::{
1999        display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap},
2000        test::test_font,
2001    };
2002    use gpui::{App, AppContext as _, Element, div, font, px};
2003    use itertools::Itertools;
2004    use language::{Buffer, Capability};
2005    use multi_buffer::{ExcerptRange, MultiBuffer};
2006    use rand::prelude::*;
2007    use settings::SettingsStore;
2008    use std::env;
2009    use util::RandomCharIter;
2010
2011    #[gpui::test]
2012    fn test_offset_for_row() {
2013        assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0));
2014        assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0));
2015        assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0));
2016        assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4));
2017        assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0));
2018        assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1));
2019        assert_eq!(
2020            offset_for_row("abc\ndef\nghi", RowDelta(0)),
2021            (RowDelta(0), 0)
2022        );
2023        assert_eq!(
2024            offset_for_row("abc\ndef\nghi", RowDelta(1)),
2025            (RowDelta(1), 4)
2026        );
2027        assert_eq!(
2028            offset_for_row("abc\ndef\nghi", RowDelta(2)),
2029            (RowDelta(2), 8)
2030        );
2031        assert_eq!(
2032            offset_for_row("abc\ndef\nghi", RowDelta(3)),
2033            (RowDelta(2), 11)
2034        );
2035    }
2036
2037    #[gpui::test]
2038    fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2039        cx.update(init_test);
2040
2041        let text = "aaa\nbbb\nccc\nddd";
2042
2043        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2044        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2045        let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2046        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2047        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2048        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2049        let (wrap_map, wraps_snapshot) =
2050            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2051        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2052
2053        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2054        let block_ids = writer.insert(vec![
2055            BlockProperties {
2056                style: BlockStyle::Fixed,
2057                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2058                height: Some(1),
2059                render: Arc::new(|_| div().into_any()),
2060                priority: 0,
2061            },
2062            BlockProperties {
2063                style: BlockStyle::Fixed,
2064                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2065                height: Some(2),
2066                render: Arc::new(|_| div().into_any()),
2067                priority: 0,
2068            },
2069            BlockProperties {
2070                style: BlockStyle::Fixed,
2071                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2072                height: Some(3),
2073                render: Arc::new(|_| div().into_any()),
2074                priority: 0,
2075            },
2076        ]);
2077
2078        let snapshot = block_map.read(wraps_snapshot, Default::default());
2079        assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2080
2081        let blocks = snapshot
2082            .blocks_in_range(BlockRow(0)..BlockRow(8))
2083            .map(|(start_row, block)| {
2084                let block = block.as_custom().unwrap();
2085                (start_row.0..start_row.0 + block.height.unwrap(), block.id)
2086            })
2087            .collect::<Vec<_>>();
2088
2089        // When multiple blocks are on the same line, the newer blocks appear first.
2090        assert_eq!(
2091            blocks,
2092            &[
2093                (1..2, block_ids[0]),
2094                (2..4, block_ids[1]),
2095                (7..10, block_ids[2]),
2096            ]
2097        );
2098
2099        assert_eq!(
2100            snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)),
2101            BlockPoint::new(BlockRow(0), 3)
2102        );
2103        assert_eq!(
2104            snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)),
2105            BlockPoint::new(BlockRow(4), 0)
2106        );
2107        assert_eq!(
2108            snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)),
2109            BlockPoint::new(BlockRow(6), 3)
2110        );
2111
2112        assert_eq!(
2113            snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left),
2114            WrapPoint::new(WrapRow(0), 3)
2115        );
2116        assert_eq!(
2117            snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2118            WrapPoint::new(WrapRow(1), 0)
2119        );
2120        assert_eq!(
2121            snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left),
2122            WrapPoint::new(WrapRow(1), 0)
2123        );
2124        assert_eq!(
2125            snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2126            WrapPoint::new(WrapRow(3), 3)
2127        );
2128
2129        assert_eq!(
2130            snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2131            BlockPoint::new(BlockRow(0), 3)
2132        );
2133        assert_eq!(
2134            snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right),
2135            BlockPoint::new(BlockRow(4), 0)
2136        );
2137        assert_eq!(
2138            snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left),
2139            BlockPoint::new(BlockRow(0), 3)
2140        );
2141        assert_eq!(
2142            snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right),
2143            BlockPoint::new(BlockRow(4), 0)
2144        );
2145        assert_eq!(
2146            snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left),
2147            BlockPoint::new(BlockRow(4), 0)
2148        );
2149        assert_eq!(
2150            snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right),
2151            BlockPoint::new(BlockRow(4), 0)
2152        );
2153        assert_eq!(
2154            snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left),
2155            BlockPoint::new(BlockRow(6), 3)
2156        );
2157        assert_eq!(
2158            snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right),
2159            BlockPoint::new(BlockRow(6), 3)
2160        );
2161        assert_eq!(
2162            snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2163            BlockPoint::new(BlockRow(6), 3)
2164        );
2165        assert_eq!(
2166            snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right),
2167            BlockPoint::new(BlockRow(6), 3)
2168        );
2169
2170        assert_eq!(
2171            snapshot
2172                .row_infos(BlockRow(0))
2173                .map(|row_info| row_info.buffer_row)
2174                .collect::<Vec<_>>(),
2175            &[
2176                Some(0),
2177                None,
2178                None,
2179                None,
2180                Some(1),
2181                Some(2),
2182                Some(3),
2183                None,
2184                None,
2185                None
2186            ]
2187        );
2188
2189        // Insert a line break, separating two block decorations into separate lines.
2190        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2191            buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2192            buffer.snapshot(cx)
2193        });
2194
2195        let (inlay_snapshot, inlay_edits) =
2196            inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2197        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2198        let (tab_snapshot, tab_edits) =
2199            tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2200        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2201            wrap_map.sync(tab_snapshot, tab_edits, cx)
2202        });
2203        let snapshot = block_map.read(wraps_snapshot, wrap_edits);
2204        assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2205    }
2206
2207    #[gpui::test]
2208    fn test_multibuffer_headers_and_footers(cx: &mut App) {
2209        init_test(cx);
2210
2211        let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2212        let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2213        let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2214
2215        let mut excerpt_ids = Vec::new();
2216        let multi_buffer = cx.new(|cx| {
2217            let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2218            excerpt_ids.extend(multi_buffer.push_excerpts(
2219                buffer1.clone(),
2220                [ExcerptRange::new(0..buffer1.read(cx).len())],
2221                cx,
2222            ));
2223            excerpt_ids.extend(multi_buffer.push_excerpts(
2224                buffer2.clone(),
2225                [ExcerptRange::new(0..buffer2.read(cx).len())],
2226                cx,
2227            ));
2228            excerpt_ids.extend(multi_buffer.push_excerpts(
2229                buffer3.clone(),
2230                [ExcerptRange::new(0..buffer3.read(cx).len())],
2231                cx,
2232            ));
2233
2234            multi_buffer
2235        });
2236
2237        let font = test_font();
2238        let font_size = px(14.);
2239        let font_id = cx.text_system().resolve_font(&font);
2240        let mut wrap_width = px(0.);
2241        for c in "Buff".chars() {
2242            wrap_width += cx
2243                .text_system()
2244                .advance(font_id, font_size, c)
2245                .unwrap()
2246                .width;
2247        }
2248
2249        let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2250        let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
2251        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2252        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2253        let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2254
2255        let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2256        let snapshot = block_map.read(wraps_snapshot, Default::default());
2257
2258        // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2259        assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
2260
2261        let blocks: Vec<_> = snapshot
2262            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2263            .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
2264            .collect();
2265        assert_eq!(
2266            blocks,
2267            vec![
2268                (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
2269                (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
2270                (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
2271            ]
2272        );
2273    }
2274
2275    #[gpui::test]
2276    fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2277        cx.update(init_test);
2278
2279        let text = "aaa\nbbb\nccc\nddd";
2280
2281        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2282        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2283        let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2284        let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2285        let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2286        let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2287        let (_wrap_map, wraps_snapshot) =
2288            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2289        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2290
2291        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2292        let block_ids = writer.insert(vec![
2293            BlockProperties {
2294                style: BlockStyle::Fixed,
2295                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2296                height: Some(1),
2297                render: Arc::new(|_| div().into_any()),
2298                priority: 0,
2299            },
2300            BlockProperties {
2301                style: BlockStyle::Fixed,
2302                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2303                height: Some(2),
2304                render: Arc::new(|_| div().into_any()),
2305                priority: 0,
2306            },
2307            BlockProperties {
2308                style: BlockStyle::Fixed,
2309                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2310                height: Some(3),
2311                render: Arc::new(|_| div().into_any()),
2312                priority: 0,
2313            },
2314        ]);
2315
2316        {
2317            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2318            assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2319
2320            let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2321
2322            let mut new_heights = HashMap::default();
2323            new_heights.insert(block_ids[0], 2);
2324            block_map_writer.resize(new_heights);
2325            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2326            assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2327        }
2328
2329        {
2330            let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2331
2332            let mut new_heights = HashMap::default();
2333            new_heights.insert(block_ids[0], 1);
2334            block_map_writer.resize(new_heights);
2335
2336            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2337            assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2338        }
2339
2340        {
2341            let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2342
2343            let mut new_heights = HashMap::default();
2344            new_heights.insert(block_ids[0], 0);
2345            block_map_writer.resize(new_heights);
2346
2347            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2348            assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2349        }
2350
2351        {
2352            let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2353
2354            let mut new_heights = HashMap::default();
2355            new_heights.insert(block_ids[0], 3);
2356            block_map_writer.resize(new_heights);
2357
2358            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2359            assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2360        }
2361
2362        {
2363            let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2364
2365            let mut new_heights = HashMap::default();
2366            new_heights.insert(block_ids[0], 3);
2367            block_map_writer.resize(new_heights);
2368
2369            let snapshot = block_map.read(wraps_snapshot, Default::default());
2370            // Same height as before, should remain the same
2371            assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2372        }
2373    }
2374
2375    #[gpui::test]
2376    fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2377        cx.update(init_test);
2378
2379        let text = "one two three\nfour five six\nseven eight";
2380
2381        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2382        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2383        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2384        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2385        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2386        let (_, wraps_snapshot) = cx.update(|cx| {
2387            WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
2388        });
2389        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2390
2391        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2392        writer.insert(vec![
2393            BlockProperties {
2394                style: BlockStyle::Fixed,
2395                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2396                render: Arc::new(|_| div().into_any()),
2397                height: Some(1),
2398                priority: 0,
2399            },
2400            BlockProperties {
2401                style: BlockStyle::Fixed,
2402                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2403                render: Arc::new(|_| div().into_any()),
2404                height: Some(1),
2405                priority: 0,
2406            },
2407        ]);
2408
2409        // Blocks with an 'above' disposition go above their corresponding buffer line.
2410        // Blocks with a 'below' disposition go below their corresponding buffer line.
2411        let snapshot = block_map.read(wraps_snapshot, Default::default());
2412        assert_eq!(
2413            snapshot.text(),
2414            "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2415        );
2416    }
2417
2418    #[gpui::test]
2419    fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2420        cx.update(init_test);
2421
2422        let text = "line1\nline2\nline3\nline4\nline5";
2423
2424        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2425        let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2426        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2427        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2428        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2429        let tab_size = 1.try_into().unwrap();
2430        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2431        let (wrap_map, wraps_snapshot) =
2432            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2433        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2434
2435        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2436        let replace_block_id = writer.insert(vec![BlockProperties {
2437            style: BlockStyle::Fixed,
2438            placement: BlockPlacement::Replace(
2439                buffer_snapshot.anchor_after(Point::new(1, 3))
2440                    ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2441            ),
2442            height: Some(4),
2443            render: Arc::new(|_| div().into_any()),
2444            priority: 0,
2445        }])[0];
2446
2447        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2448        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2449
2450        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2451            buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2452            buffer.snapshot(cx)
2453        });
2454        let (inlay_snapshot, inlay_edits) =
2455            inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
2456        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2457        let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2458        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2459            wrap_map.sync(tab_snapshot, tab_edits, cx)
2460        });
2461        let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits);
2462        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2463
2464        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2465            buffer.edit(
2466                [(
2467                    Point::new(1, 5)..Point::new(1, 5),
2468                    "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2469                )],
2470                None,
2471                cx,
2472            );
2473            buffer.snapshot(cx)
2474        });
2475        let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2476            buffer_snapshot.clone(),
2477            buffer_subscription.consume().into_inner(),
2478        );
2479        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2480        let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2481        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2482            wrap_map.sync(tab_snapshot, tab_edits, cx)
2483        });
2484        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2485        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2486
2487        // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2488        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2489        writer.insert(vec![
2490            BlockProperties {
2491                style: BlockStyle::Fixed,
2492                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2493                height: Some(1),
2494                render: Arc::new(|_| div().into_any()),
2495                priority: 0,
2496            },
2497            BlockProperties {
2498                style: BlockStyle::Fixed,
2499                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2500                height: Some(1),
2501                render: Arc::new(|_| div().into_any()),
2502                priority: 0,
2503            },
2504            BlockProperties {
2505                style: BlockStyle::Fixed,
2506                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2507                height: Some(1),
2508                render: Arc::new(|_| div().into_any()),
2509                priority: 0,
2510            },
2511        ]);
2512        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2513        assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2514
2515        // Ensure blocks inserted *inside* replaced region are hidden.
2516        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2517        writer.insert(vec![
2518            BlockProperties {
2519                style: BlockStyle::Fixed,
2520                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2521                height: Some(1),
2522                render: Arc::new(|_| div().into_any()),
2523                priority: 0,
2524            },
2525            BlockProperties {
2526                style: BlockStyle::Fixed,
2527                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2528                height: Some(1),
2529                render: Arc::new(|_| div().into_any()),
2530                priority: 0,
2531            },
2532            BlockProperties {
2533                style: BlockStyle::Fixed,
2534                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2535                height: Some(1),
2536                render: Arc::new(|_| div().into_any()),
2537                priority: 0,
2538            },
2539        ]);
2540        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2541        assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2542
2543        // Removing the replace block shows all the hidden blocks again.
2544        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2545        writer.remove(HashSet::from_iter([replace_block_id]));
2546        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2547        assert_eq!(
2548            blocks_snapshot.text(),
2549            "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2550        );
2551    }
2552
2553    #[gpui::test]
2554    fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2555        cx.update(init_test);
2556
2557        let text = "111\n222\n333\n444\n555\n666";
2558
2559        let buffer = cx.update(|cx| {
2560            MultiBuffer::build_multi(
2561                [
2562                    (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2563                    (
2564                        text,
2565                        vec![
2566                            Point::new(1, 0)..Point::new(1, 3),
2567                            Point::new(2, 0)..Point::new(2, 3),
2568                            Point::new(3, 0)..Point::new(3, 3),
2569                        ],
2570                    ),
2571                    (
2572                        text,
2573                        vec![
2574                            Point::new(4, 0)..Point::new(4, 3),
2575                            Point::new(5, 0)..Point::new(5, 3),
2576                        ],
2577                    ),
2578                ],
2579                cx,
2580            )
2581        });
2582        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2583        let buffer_ids = buffer_snapshot
2584            .excerpts()
2585            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2586            .dedup()
2587            .collect::<Vec<_>>();
2588        assert_eq!(buffer_ids.len(), 3);
2589        let buffer_id_1 = buffer_ids[0];
2590        let buffer_id_2 = buffer_ids[1];
2591        let buffer_id_3 = buffer_ids[2];
2592
2593        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2594        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2595        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2596        let (_, wrap_snapshot) =
2597            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2598        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2599        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2600
2601        assert_eq!(
2602            blocks_snapshot.text(),
2603            "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
2604        );
2605        assert_eq!(
2606            blocks_snapshot
2607                .row_infos(BlockRow(0))
2608                .map(|i| i.buffer_row)
2609                .collect::<Vec<_>>(),
2610            vec![
2611                None,
2612                None,
2613                Some(0),
2614                None,
2615                None,
2616                Some(1),
2617                None,
2618                Some(2),
2619                None,
2620                Some(3),
2621                None,
2622                None,
2623                Some(4),
2624                None,
2625                Some(5),
2626            ]
2627        );
2628
2629        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2630        let excerpt_blocks_2 = writer.insert(vec![
2631            BlockProperties {
2632                style: BlockStyle::Fixed,
2633                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2634                height: Some(1),
2635                render: Arc::new(|_| div().into_any()),
2636                priority: 0,
2637            },
2638            BlockProperties {
2639                style: BlockStyle::Fixed,
2640                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2641                height: Some(1),
2642                render: Arc::new(|_| div().into_any()),
2643                priority: 0,
2644            },
2645            BlockProperties {
2646                style: BlockStyle::Fixed,
2647                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2648                height: Some(1),
2649                render: Arc::new(|_| div().into_any()),
2650                priority: 0,
2651            },
2652        ]);
2653        let excerpt_blocks_3 = writer.insert(vec![
2654            BlockProperties {
2655                style: BlockStyle::Fixed,
2656                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2657                height: Some(1),
2658                render: Arc::new(|_| div().into_any()),
2659                priority: 0,
2660            },
2661            BlockProperties {
2662                style: BlockStyle::Fixed,
2663                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2664                height: Some(1),
2665                render: Arc::new(|_| div().into_any()),
2666                priority: 0,
2667            },
2668        ]);
2669
2670        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2671        assert_eq!(
2672            blocks_snapshot.text(),
2673            "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2674        );
2675        assert_eq!(
2676            blocks_snapshot
2677                .row_infos(BlockRow(0))
2678                .map(|i| i.buffer_row)
2679                .collect::<Vec<_>>(),
2680            vec![
2681                None,
2682                None,
2683                Some(0),
2684                None,
2685                None,
2686                None,
2687                Some(1),
2688                None,
2689                None,
2690                Some(2),
2691                None,
2692                Some(3),
2693                None,
2694                None,
2695                None,
2696                None,
2697                Some(4),
2698                None,
2699                Some(5),
2700                None,
2701            ]
2702        );
2703
2704        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2705        buffer.read_with(cx, |buffer, cx| {
2706            writer.fold_buffers([buffer_id_1], buffer, cx);
2707        });
2708        let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2709            style: BlockStyle::Fixed,
2710            placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2711            height: Some(1),
2712            render: Arc::new(|_| div().into_any()),
2713            priority: 0,
2714        }]);
2715        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2716        let blocks = blocks_snapshot
2717            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2718            .collect::<Vec<_>>();
2719        for (_, block) in &blocks {
2720            if let BlockId::Custom(custom_block_id) = block.id() {
2721                assert!(
2722                    !excerpt_blocks_1.contains(&custom_block_id),
2723                    "Should have no blocks from the folded buffer"
2724                );
2725                assert!(
2726                    excerpt_blocks_2.contains(&custom_block_id)
2727                        || excerpt_blocks_3.contains(&custom_block_id),
2728                    "Should have only blocks from unfolded buffers"
2729                );
2730            }
2731        }
2732        assert_eq!(
2733            1,
2734            blocks
2735                .iter()
2736                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2737                .count(),
2738            "Should have one folded block, producing a header of the second buffer"
2739        );
2740        assert_eq!(
2741            blocks_snapshot.text(),
2742            "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2743        );
2744        assert_eq!(
2745            blocks_snapshot
2746                .row_infos(BlockRow(0))
2747                .map(|i| i.buffer_row)
2748                .collect::<Vec<_>>(),
2749            vec![
2750                None,
2751                None,
2752                None,
2753                None,
2754                None,
2755                Some(1),
2756                None,
2757                None,
2758                Some(2),
2759                None,
2760                Some(3),
2761                None,
2762                None,
2763                None,
2764                None,
2765                Some(4),
2766                None,
2767                Some(5),
2768                None,
2769            ]
2770        );
2771
2772        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2773        buffer.read_with(cx, |buffer, cx| {
2774            writer.fold_buffers([buffer_id_2], buffer, cx);
2775        });
2776        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2777        let blocks = blocks_snapshot
2778            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2779            .collect::<Vec<_>>();
2780        for (_, block) in &blocks {
2781            if let BlockId::Custom(custom_block_id) = block.id() {
2782                assert!(
2783                    !excerpt_blocks_1.contains(&custom_block_id),
2784                    "Should have no blocks from the folded buffer_1"
2785                );
2786                assert!(
2787                    !excerpt_blocks_2.contains(&custom_block_id),
2788                    "Should have no blocks from the folded buffer_2"
2789                );
2790                assert!(
2791                    excerpt_blocks_3.contains(&custom_block_id),
2792                    "Should have only blocks from unfolded buffers"
2793                );
2794            }
2795        }
2796        assert_eq!(
2797            2,
2798            blocks
2799                .iter()
2800                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2801                .count(),
2802            "Should have two folded blocks, producing headers"
2803        );
2804        assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
2805        assert_eq!(
2806            blocks_snapshot
2807                .row_infos(BlockRow(0))
2808                .map(|i| i.buffer_row)
2809                .collect::<Vec<_>>(),
2810            vec![
2811                None,
2812                None,
2813                None,
2814                None,
2815                None,
2816                None,
2817                None,
2818                Some(4),
2819                None,
2820                Some(5),
2821                None,
2822            ]
2823        );
2824
2825        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2826        buffer.read_with(cx, |buffer, cx| {
2827            writer.unfold_buffers([buffer_id_1], buffer, cx);
2828        });
2829        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2830        let blocks = blocks_snapshot
2831            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2832            .collect::<Vec<_>>();
2833        for (_, block) in &blocks {
2834            if let BlockId::Custom(custom_block_id) = block.id() {
2835                assert!(
2836                    !excerpt_blocks_2.contains(&custom_block_id),
2837                    "Should have no blocks from the folded buffer_2"
2838                );
2839                assert!(
2840                    excerpt_blocks_1.contains(&custom_block_id)
2841                        || excerpt_blocks_3.contains(&custom_block_id),
2842                    "Should have only blocks from unfolded buffers"
2843                );
2844            }
2845        }
2846        assert_eq!(
2847            1,
2848            blocks
2849                .iter()
2850                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2851                .count(),
2852            "Should be back to a single folded buffer, producing a header for buffer_2"
2853        );
2854        assert_eq!(
2855            blocks_snapshot.text(),
2856            "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
2857            "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2858        );
2859        assert_eq!(
2860            blocks_snapshot
2861                .row_infos(BlockRow(0))
2862                .map(|i| i.buffer_row)
2863                .collect::<Vec<_>>(),
2864            vec![
2865                None,
2866                None,
2867                None,
2868                Some(0),
2869                None,
2870                None,
2871                None,
2872                None,
2873                None,
2874                Some(4),
2875                None,
2876                Some(5),
2877                None,
2878            ]
2879        );
2880
2881        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2882        buffer.read_with(cx, |buffer, cx| {
2883            writer.fold_buffers([buffer_id_3], buffer, cx);
2884        });
2885        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2886        let blocks = blocks_snapshot
2887            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2888            .collect::<Vec<_>>();
2889        for (_, block) in &blocks {
2890            if let BlockId::Custom(custom_block_id) = block.id() {
2891                assert!(
2892                    excerpt_blocks_1.contains(&custom_block_id),
2893                    "Should have no blocks from the folded buffer_1"
2894                );
2895                assert!(
2896                    !excerpt_blocks_2.contains(&custom_block_id),
2897                    "Should have only blocks from unfolded buffers"
2898                );
2899                assert!(
2900                    !excerpt_blocks_3.contains(&custom_block_id),
2901                    "Should have only blocks from unfolded buffers"
2902                );
2903            }
2904        }
2905
2906        assert_eq!(
2907            blocks_snapshot.text(),
2908            "\n\n\n111\n\n\n\n",
2909            "Should have a single, first buffer left after folding"
2910        );
2911        assert_eq!(
2912            blocks_snapshot
2913                .row_infos(BlockRow(0))
2914                .map(|i| i.buffer_row)
2915                .collect::<Vec<_>>(),
2916            vec![None, None, None, Some(0), None, None, None, None,]
2917        );
2918    }
2919
2920    #[gpui::test]
2921    fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2922        cx.update(init_test);
2923
2924        let text = "111";
2925
2926        let buffer = cx.update(|cx| {
2927            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2928        });
2929        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2930        let buffer_ids = buffer_snapshot
2931            .excerpts()
2932            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2933            .dedup()
2934            .collect::<Vec<_>>();
2935        assert_eq!(buffer_ids.len(), 1);
2936        let buffer_id = buffer_ids[0];
2937
2938        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
2939        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2940        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2941        let (_, wrap_snapshot) =
2942            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2943        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2944        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2945
2946        assert_eq!(blocks_snapshot.text(), "\n\n111");
2947
2948        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2949        buffer.read_with(cx, |buffer, cx| {
2950            writer.fold_buffers([buffer_id], buffer, cx);
2951        });
2952        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2953        let blocks = blocks_snapshot
2954            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2955            .collect::<Vec<_>>();
2956        assert_eq!(
2957            1,
2958            blocks
2959                .iter()
2960                .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
2961                .count(),
2962            "Should have one folded block, producing a header of the second buffer"
2963        );
2964        assert_eq!(blocks_snapshot.text(), "\n");
2965        assert_eq!(
2966            blocks_snapshot
2967                .row_infos(BlockRow(0))
2968                .map(|i| i.buffer_row)
2969                .collect::<Vec<_>>(),
2970            vec![None, None],
2971            "When fully folded, should be no buffer rows"
2972        );
2973    }
2974
2975    #[gpui::test(iterations = 100)]
2976    fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2977        cx.update(init_test);
2978
2979        let operations = env::var("OPERATIONS")
2980            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2981            .unwrap_or(10);
2982
2983        let wrap_width = if rng.random_bool(0.2) {
2984            None
2985        } else {
2986            Some(px(rng.random_range(0.0..=100.0)))
2987        };
2988        let tab_size = 1.try_into().unwrap();
2989        let font_size = px(14.0);
2990        let buffer_start_header_height = rng.random_range(1..=5);
2991        let excerpt_header_height = rng.random_range(1..=5);
2992
2993        log::info!("Wrap width: {:?}", wrap_width);
2994        log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
2995        let is_singleton = rng.random();
2996        let buffer = if is_singleton {
2997            let len = rng.random_range(0..10);
2998            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
2999            log::info!("initial singleton buffer text: {:?}", text);
3000            cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3001        } else {
3002            cx.update(|cx| {
3003                let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3004                log::info!(
3005                    "initial multi-buffer text: {:?}",
3006                    multibuffer.read(cx).read(cx).text()
3007                );
3008                multibuffer
3009            })
3010        };
3011
3012        let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3013        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3014        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3015        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3016        let font = test_font();
3017        let (wrap_map, wraps_snapshot) =
3018            cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3019        let mut block_map = BlockMap::new(
3020            wraps_snapshot,
3021            buffer_start_header_height,
3022            excerpt_header_height,
3023        );
3024
3025        for _ in 0..operations {
3026            let mut buffer_edits = Vec::new();
3027            match rng.random_range(0..=100) {
3028                0..=19 => {
3029                    let wrap_width = if rng.random_bool(0.2) {
3030                        None
3031                    } else {
3032                        Some(px(rng.random_range(0.0..=100.0)))
3033                    };
3034                    log::info!("Setting wrap width to {:?}", wrap_width);
3035                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3036                }
3037                20..=39 => {
3038                    let block_count = rng.random_range(1..=5);
3039                    let block_properties = (0..block_count)
3040                        .map(|_| {
3041                            let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3042                            let offset =
3043                                buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Left);
3044                            let mut min_height = 0;
3045                            let placement = match rng.random_range(0..3) {
3046                                0 => {
3047                                    min_height = 1;
3048                                    let start = buffer.anchor_after(offset);
3049                                    let end = buffer.anchor_after(buffer.clip_offset(
3050                                        rng.random_range(offset..=buffer.len()),
3051                                        Bias::Left,
3052                                    ));
3053                                    BlockPlacement::Replace(start..=end)
3054                                }
3055                                1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3056                                _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3057                            };
3058
3059                            let height = rng.random_range(min_height..512);
3060                            BlockProperties {
3061                                style: BlockStyle::Fixed,
3062                                placement,
3063                                height: Some(height),
3064                                render: Arc::new(|_| div().into_any()),
3065                                priority: 0,
3066                            }
3067                        })
3068                        .collect::<Vec<_>>();
3069
3070                    let (inlay_snapshot, inlay_edits) =
3071                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3072                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3073                    let (tab_snapshot, tab_edits) =
3074                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3075                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3076                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3077                    });
3078                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3079                    let block_ids =
3080                        block_map.insert(block_properties.iter().map(|props| BlockProperties {
3081                            placement: props.placement.clone(),
3082                            height: props.height,
3083                            style: props.style,
3084                            render: Arc::new(|_| div().into_any()),
3085                            priority: 0,
3086                        }));
3087
3088                    for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3089                        log::info!(
3090                            "inserted block {:?} with height {:?} and id {:?}",
3091                            block_properties
3092                                .placement
3093                                .as_ref()
3094                                .map(|p| p.to_point(&buffer_snapshot)),
3095                            block_properties.height,
3096                            block_id
3097                        );
3098                    }
3099                }
3100                40..=59 if !block_map.custom_blocks.is_empty() => {
3101                    let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3102                    let block_ids_to_remove = block_map
3103                        .custom_blocks
3104                        .choose_multiple(&mut rng, block_count)
3105                        .map(|block| block.id)
3106                        .collect::<HashSet<_>>();
3107
3108                    let (inlay_snapshot, inlay_edits) =
3109                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3110                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3111                    let (tab_snapshot, tab_edits) =
3112                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3113                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3114                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3115                    });
3116                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3117                    log::info!(
3118                        "removing {} blocks: {:?}",
3119                        block_ids_to_remove.len(),
3120                        block_ids_to_remove
3121                    );
3122                    block_map.remove(block_ids_to_remove);
3123                }
3124                60..=79 => {
3125                    if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3126                        log::info!("Noop fold/unfold operation on a singleton buffer");
3127                        continue;
3128                    }
3129                    let (inlay_snapshot, inlay_edits) =
3130                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3131                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3132                    let (tab_snapshot, tab_edits) =
3133                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3134                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3135                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3136                    });
3137                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3138                    let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3139                        let folded_buffers = block_map
3140                            .0
3141                            .folded_buffers
3142                            .iter()
3143                            .cloned()
3144                            .collect::<Vec<_>>();
3145                        let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3146                        unfolded_buffers.dedup();
3147                        log::debug!("All buffers {unfolded_buffers:?}");
3148                        log::debug!("Folded buffers {folded_buffers:?}");
3149                        unfolded_buffers
3150                            .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3151                        (unfolded_buffers, folded_buffers)
3152                    });
3153                    let mut folded_count = folded_buffers.len();
3154                    let mut unfolded_count = unfolded_buffers.len();
3155
3156                    let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3157                    let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3158                    if !fold && !unfold {
3159                        log::info!(
3160                            "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3161                        );
3162                        continue;
3163                    }
3164
3165                    buffer.update(cx, |buffer, cx| {
3166                        if fold {
3167                            let buffer_to_fold =
3168                                unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3169                            log::info!("Folding {buffer_to_fold:?}");
3170                            let related_excerpts = buffer_snapshot
3171                                .excerpts()
3172                                .filter_map(|(excerpt_id, buffer, range)| {
3173                                    if buffer.remote_id() == buffer_to_fold {
3174                                        Some((
3175                                            excerpt_id,
3176                                            buffer
3177                                                .text_for_range(range.context)
3178                                                .collect::<String>(),
3179                                        ))
3180                                    } else {
3181                                        None
3182                                    }
3183                                })
3184                                .collect::<Vec<_>>();
3185                            log::info!(
3186                                "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3187                            );
3188                            folded_count += 1;
3189                            unfolded_count -= 1;
3190                            block_map.fold_buffers([buffer_to_fold], buffer, cx);
3191                        }
3192                        if unfold {
3193                            let buffer_to_unfold =
3194                                folded_buffers[rng.random_range(0..folded_buffers.len())];
3195                            log::info!("Unfolding {buffer_to_unfold:?}");
3196                            unfolded_count += 1;
3197                            folded_count -= 1;
3198                            block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3199                        }
3200                        log::info!(
3201                            "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3202                        );
3203                    });
3204                }
3205                _ => {
3206                    buffer.update(cx, |buffer, cx| {
3207                        let mutation_count = rng.random_range(1..=5);
3208                        let subscription = buffer.subscribe();
3209                        buffer.randomly_mutate(&mut rng, mutation_count, cx);
3210                        buffer_snapshot = buffer.snapshot(cx);
3211                        buffer_edits.extend(subscription.consume());
3212                        log::info!("buffer text: {:?}", buffer_snapshot.text());
3213                    });
3214                }
3215            }
3216
3217            let (inlay_snapshot, inlay_edits) =
3218                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3219            let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3220            let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3221            let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3222                wrap_map.sync(tab_snapshot, tab_edits, cx)
3223            });
3224            let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3225            assert_eq!(
3226                blocks_snapshot.transforms.summary().input_rows,
3227                wraps_snapshot.max_point().row() + RowDelta(1)
3228            );
3229            log::info!("wrapped text: {:?}", wraps_snapshot.text());
3230            log::info!("blocks text: {:?}", blocks_snapshot.text());
3231
3232            let mut expected_blocks = Vec::new();
3233            expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3234                Some((
3235                    block.placement.to_wrap_row(&wraps_snapshot)?,
3236                    Block::Custom(block.clone()),
3237                ))
3238            }));
3239
3240            // Note that this needs to be synced with the related section in BlockMap::sync
3241            expected_blocks.extend(block_map.header_and_footer_blocks(
3242                &buffer_snapshot,
3243                0..,
3244                &wraps_snapshot,
3245            ));
3246
3247            BlockMap::sort_blocks(&mut expected_blocks);
3248
3249            for (placement, block) in &expected_blocks {
3250                log::info!(
3251                    "Block {:?} placement: {:?} Height: {:?}",
3252                    block.id(),
3253                    placement,
3254                    block.height()
3255                );
3256            }
3257
3258            let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3259
3260            let input_buffer_rows = buffer_snapshot
3261                .row_infos(MultiBufferRow(0))
3262                .map(|row| row.buffer_row)
3263                .collect::<Vec<_>>();
3264            let mut expected_buffer_rows = Vec::new();
3265            let mut expected_text = String::new();
3266            let mut expected_block_positions = Vec::new();
3267            let mut expected_replaced_buffer_rows = HashSet::default();
3268            let input_text = wraps_snapshot.text();
3269
3270            // Loop over the input lines, creating (N - 1) empty lines for
3271            // blocks of height N.
3272            //
3273            // It's important to note that output *starts* as one empty line,
3274            // so we special case row 0 to assume a leading '\n'.
3275            //
3276            // Linehood is the birthright of strings.
3277            let input_text_lines = input_text.split('\n').enumerate().peekable();
3278            let mut block_row = 0;
3279            for (wrap_row, input_line) in input_text_lines {
3280                let wrap_row = WrapRow(wrap_row as u32);
3281                let multibuffer_row = wraps_snapshot
3282                    .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3283                    .row;
3284
3285                // Create empty lines for the above block
3286                while let Some((placement, block)) = sorted_blocks_iter.peek() {
3287                    if *placement.start() == wrap_row && block.place_above() {
3288                        let (_, block) = sorted_blocks_iter.next().unwrap();
3289                        expected_block_positions.push((block_row, block.id()));
3290                        if block.height() > 0 {
3291                            let text = "\n".repeat((block.height() - 1) as usize);
3292                            if block_row > 0 {
3293                                expected_text.push('\n')
3294                            }
3295                            expected_text.push_str(&text);
3296                            for _ in 0..block.height() {
3297                                expected_buffer_rows.push(None);
3298                            }
3299                            block_row += block.height();
3300                        }
3301                    } else {
3302                        break;
3303                    }
3304                }
3305
3306                // Skip lines within replace blocks, then create empty lines for the replace block's height
3307                let mut is_in_replace_block = false;
3308                if let Some((BlockPlacement::Replace(replace_range), block)) =
3309                    sorted_blocks_iter.peek()
3310                    && wrap_row >= *replace_range.start()
3311                {
3312                    is_in_replace_block = true;
3313
3314                    if wrap_row == *replace_range.start() {
3315                        if matches!(block, Block::FoldedBuffer { .. }) {
3316                            expected_buffer_rows.push(None);
3317                        } else {
3318                            expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
3319                        }
3320                    }
3321
3322                    if wrap_row == *replace_range.end() {
3323                        expected_block_positions.push((block_row, block.id()));
3324                        let text = "\n".repeat((block.height() - 1) as usize);
3325                        if block_row > 0 {
3326                            expected_text.push('\n');
3327                        }
3328                        expected_text.push_str(&text);
3329
3330                        for _ in 1..block.height() {
3331                            expected_buffer_rows.push(None);
3332                        }
3333                        block_row += block.height();
3334
3335                        sorted_blocks_iter.next();
3336                    }
3337                }
3338
3339                if is_in_replace_block {
3340                    expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3341                } else {
3342                    let buffer_row = input_buffer_rows[multibuffer_row as usize];
3343                    let soft_wrapped = wraps_snapshot
3344                        .to_tab_point(WrapPoint::new(wrap_row, 0))
3345                        .column()
3346                        > 0;
3347                    expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3348                    if block_row > 0 {
3349                        expected_text.push('\n');
3350                    }
3351                    expected_text.push_str(input_line);
3352                    block_row += 1;
3353                }
3354
3355                while let Some((placement, block)) = sorted_blocks_iter.peek() {
3356                    if *placement.end() == wrap_row && block.place_below() {
3357                        let (_, block) = sorted_blocks_iter.next().unwrap();
3358                        expected_block_positions.push((block_row, block.id()));
3359                        if block.height() > 0 {
3360                            let text = "\n".repeat((block.height() - 1) as usize);
3361                            if block_row > 0 {
3362                                expected_text.push('\n')
3363                            }
3364                            expected_text.push_str(&text);
3365                            for _ in 0..block.height() {
3366                                expected_buffer_rows.push(None);
3367                            }
3368                            block_row += block.height();
3369                        }
3370                    } else {
3371                        break;
3372                    }
3373                }
3374            }
3375
3376            let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3377            let expected_row_count = expected_lines.len();
3378            log::info!("expected text: {expected_text:?}");
3379
3380            assert_eq!(
3381                blocks_snapshot.max_point().row + 1,
3382                expected_row_count as u32,
3383                "actual row count != expected row count",
3384            );
3385            assert_eq!(
3386                blocks_snapshot.text(),
3387                expected_text,
3388                "actual text != expected text",
3389            );
3390
3391            for start_row in 0..expected_row_count {
3392                let end_row = rng.random_range(start_row + 1..=expected_row_count);
3393                let mut expected_text = expected_lines[start_row..end_row].join("\n");
3394                if end_row < expected_row_count {
3395                    expected_text.push('\n');
3396                }
3397
3398                let actual_text = blocks_snapshot
3399                    .chunks(
3400                        BlockRow(start_row as u32)..BlockRow(end_row as u32),
3401                        false,
3402                        false,
3403                        Highlights::default(),
3404                    )
3405                    .map(|chunk| chunk.text)
3406                    .collect::<String>();
3407                assert_eq!(
3408                    actual_text,
3409                    expected_text,
3410                    "incorrect text starting row row range {:?}",
3411                    start_row..end_row
3412                );
3413                assert_eq!(
3414                    blocks_snapshot
3415                        .row_infos(BlockRow(start_row as u32))
3416                        .map(|row_info| row_info.buffer_row)
3417                        .collect::<Vec<_>>(),
3418                    &expected_buffer_rows[start_row..],
3419                    "incorrect buffer_rows starting at row {:?}",
3420                    start_row
3421                );
3422            }
3423
3424            assert_eq!(
3425                blocks_snapshot
3426                    .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
3427                    .map(|(row, block)| (row.0, block.id()))
3428                    .collect::<Vec<_>>(),
3429                expected_block_positions,
3430                "invalid blocks_in_range({:?})",
3431                0..expected_row_count
3432            );
3433
3434            for (_, expected_block) in
3435                blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
3436            {
3437                let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3438                assert_eq!(
3439                    actual_block.map(|block| block.id()),
3440                    Some(expected_block.id())
3441                );
3442            }
3443
3444            for (block_row, block_id) in expected_block_positions {
3445                if let BlockId::Custom(block_id) = block_id {
3446                    assert_eq!(
3447                        blocks_snapshot.row_for_block(block_id),
3448                        Some(BlockRow(block_row))
3449                    );
3450                }
3451            }
3452
3453            let mut expected_longest_rows = Vec::new();
3454            let mut longest_line_len = -1_isize;
3455            for (row, line) in expected_lines.iter().enumerate() {
3456                let row = row as u32;
3457
3458                assert_eq!(
3459                    blocks_snapshot.line_len(BlockRow(row)),
3460                    line.len() as u32,
3461                    "invalid line len for row {}",
3462                    row
3463                );
3464
3465                let line_char_count = line.chars().count() as isize;
3466                match line_char_count.cmp(&longest_line_len) {
3467                    Ordering::Less => {}
3468                    Ordering::Equal => expected_longest_rows.push(row),
3469                    Ordering::Greater => {
3470                        longest_line_len = line_char_count;
3471                        expected_longest_rows.clear();
3472                        expected_longest_rows.push(row);
3473                    }
3474                }
3475            }
3476
3477            let longest_row = blocks_snapshot.longest_row();
3478            assert!(
3479                expected_longest_rows.contains(&longest_row.0),
3480                "incorrect longest row {}. expected {:?} with length {}",
3481                longest_row.0,
3482                expected_longest_rows,
3483                longest_line_len,
3484            );
3485
3486            for _ in 0..10 {
3487                let end_row = rng.random_range(1..=expected_lines.len());
3488                let start_row = rng.random_range(0..end_row);
3489
3490                let mut expected_longest_rows_in_range = vec![];
3491                let mut longest_line_len_in_range = 0;
3492
3493                let mut row = start_row as u32;
3494                for line in &expected_lines[start_row..end_row] {
3495                    let line_char_count = line.chars().count() as isize;
3496                    match line_char_count.cmp(&longest_line_len_in_range) {
3497                        Ordering::Less => {}
3498                        Ordering::Equal => expected_longest_rows_in_range.push(row),
3499                        Ordering::Greater => {
3500                            longest_line_len_in_range = line_char_count;
3501                            expected_longest_rows_in_range.clear();
3502                            expected_longest_rows_in_range.push(row);
3503                        }
3504                    }
3505                    row += 1;
3506                }
3507
3508                let longest_row_in_range = blocks_snapshot
3509                    .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3510                assert!(
3511                    expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3512                    "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3513                    longest_row.0,
3514                    start_row..end_row,
3515                    expected_longest_rows_in_range,
3516                    longest_line_len_in_range,
3517                );
3518            }
3519
3520            // Ensure that conversion between block points and wrap points is stable.
3521            for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
3522                let wrap_point = WrapPoint::new(WrapRow(row), 0);
3523                let block_point = blocks_snapshot.to_block_point(wrap_point);
3524                let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3525                let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3526                assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3527                assert_eq!(
3528                    blocks_snapshot.to_block_point(right_wrap_point),
3529                    block_point
3530                );
3531            }
3532
3533            let mut block_point = BlockPoint::new(BlockRow(0), 0);
3534            for c in expected_text.chars() {
3535                let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3536                let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3537                assert_eq!(
3538                    blocks_snapshot
3539                        .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3540                    left_point,
3541                    "block point: {:?}, wrap point: {:?}",
3542                    block_point,
3543                    blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3544                );
3545                assert_eq!(
3546                    left_buffer_point,
3547                    buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3548                    "{:?} is not valid in buffer coordinates",
3549                    left_point
3550                );
3551
3552                let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3553                let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3554                assert_eq!(
3555                    blocks_snapshot
3556                        .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3557                    right_point,
3558                    "block point: {:?}, wrap point: {:?}",
3559                    block_point,
3560                    blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3561                );
3562                assert_eq!(
3563                    right_buffer_point,
3564                    buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3565                    "{:?} is not valid in buffer coordinates",
3566                    right_point
3567                );
3568
3569                if c == '\n' {
3570                    block_point.0 += Point::new(1, 0);
3571                } else {
3572                    block_point.column += c.len_utf8() as u32;
3573                }
3574            }
3575
3576            for buffer_row in 0..=buffer_snapshot.max_point().row {
3577                let buffer_row = MultiBufferRow(buffer_row);
3578                assert_eq!(
3579                    blocks_snapshot.is_line_replaced(buffer_row),
3580                    expected_replaced_buffer_rows.contains(&buffer_row),
3581                    "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3582                );
3583            }
3584        }
3585    }
3586
3587    #[gpui::test]
3588    fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
3589        cx.update(init_test);
3590
3591        let text = "abc\ndef\nghi\njkl\nmno";
3592        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3593        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3594        let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3595        let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3596        let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3597        let (_wrap_map, wraps_snapshot) =
3598            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3599        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3600
3601        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3602        let _block_id = writer.insert(vec![BlockProperties {
3603            style: BlockStyle::Fixed,
3604            placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3605            height: Some(1),
3606            render: Arc::new(|_| div().into_any()),
3607            priority: 0,
3608        }])[0];
3609
3610        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3611        assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3612
3613        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3614        writer.remove_intersecting_replace_blocks(
3615            [buffer_snapshot
3616                .anchor_after(Point::new(1, 0))
3617                .to_offset(&buffer_snapshot)
3618                ..buffer_snapshot
3619                    .anchor_after(Point::new(1, 0))
3620                    .to_offset(&buffer_snapshot)],
3621            false,
3622        );
3623        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
3624        assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3625    }
3626
3627    #[gpui::test]
3628    fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
3629        cx.update(init_test);
3630
3631        let text = "line 1\nline 2\nline 3";
3632        let buffer = cx.update(|cx| {
3633            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
3634        });
3635        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3636        let buffer_ids = buffer_snapshot
3637            .excerpts()
3638            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3639            .dedup()
3640            .collect::<Vec<_>>();
3641        assert_eq!(buffer_ids.len(), 1);
3642        let buffer_id = buffer_ids[0];
3643
3644        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3645        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3646        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3647        let (_, wrap_snapshot) =
3648            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3649        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
3650
3651        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3652        writer.insert(vec![BlockProperties {
3653            style: BlockStyle::Fixed,
3654            placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
3655            height: Some(1),
3656            render: Arc::new(|_| div().into_any()),
3657            priority: 0,
3658        }]);
3659
3660        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3661        assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
3662
3663        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3664        buffer.read_with(cx, |buffer, cx| {
3665            writer.fold_buffers([buffer_id], buffer, cx);
3666        });
3667
3668        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
3669        assert_eq!(blocks_snapshot.text(), "");
3670    }
3671
3672    #[gpui::test]
3673    fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
3674        cx.update(init_test);
3675
3676        let text = "line 1\nline 2\nline 3\nline 4";
3677        let buffer = cx.update(|cx| {
3678            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
3679        });
3680        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3681        let buffer_ids = buffer_snapshot
3682            .excerpts()
3683            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3684            .dedup()
3685            .collect::<Vec<_>>();
3686        assert_eq!(buffer_ids.len(), 1);
3687        let buffer_id = buffer_ids[0];
3688
3689        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3690        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3691        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3692        let (_, wrap_snapshot) =
3693            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3694        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
3695
3696        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3697        writer.insert(vec![BlockProperties {
3698            style: BlockStyle::Fixed,
3699            placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
3700            height: Some(1),
3701            render: Arc::new(|_| div().into_any()),
3702            priority: 0,
3703        }]);
3704
3705        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3706        assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
3707
3708        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3709        buffer.read_with(cx, |buffer, cx| {
3710            writer.fold_buffers([buffer_id], buffer, cx);
3711        });
3712
3713        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
3714        assert_eq!(blocks_snapshot.text(), "");
3715    }
3716
3717    fn init_test(cx: &mut gpui::App) {
3718        let settings = SettingsStore::test(cx);
3719        cx.set_global(settings);
3720        theme::init(theme::LoadThemes::JustBase, cx);
3721        assets::Assets.load_test_fonts(cx);
3722    }
3723
3724    impl Block {
3725        fn as_custom(&self) -> Option<&CustomBlock> {
3726            match self {
3727                Block::Custom(block) => Some(block),
3728                _ => None,
3729            }
3730        }
3731    }
3732
3733    impl BlockSnapshot {
3734        fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3735            self.wrap_snapshot
3736                .to_point(self.to_wrap_point(point, bias), bias)
3737        }
3738    }
3739}