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