block_map.rs

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