block_map.rs

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