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