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