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::{Companion, 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, MultiBufferPoint,
  15    MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
  16};
  17use parking_lot::Mutex;
  18use std::{
  19    cell::{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, IntoElement};
  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    pub(super) deferred_edits: Cell<Patch<WrapRow>>,
  49}
  50
  51pub struct BlockMapReader<'a> {
  52    pub blocks: &'a Vec<Arc<CustomBlock>>,
  53    pub snapshot: BlockSnapshot,
  54}
  55
  56pub struct BlockMapWriter<'a> {
  57    block_map: &'a mut BlockMap,
  58    companion: Option<BlockMapWriterCompanion<'a>>,
  59}
  60
  61/// Auxiliary data needed when modifying a BlockMap whose parent DisplayMap has a companion.
  62struct BlockMapWriterCompanion<'a> {
  63    display_map_id: EntityId,
  64    companion_wrap_snapshot: WrapSnapshot,
  65    companion: &'a Companion,
  66    inverse: Option<BlockMapInverseWriter<'a>>,
  67}
  68
  69struct BlockMapInverseWriter<'a> {
  70    companion_multibuffer: &'a MultiBuffer,
  71    companion_writer: Box<BlockMapWriter<'a>>,
  72}
  73
  74#[derive(Clone)]
  75pub struct BlockSnapshot {
  76    pub(super) wrap_snapshot: WrapSnapshot,
  77    transforms: SumTree<Transform>,
  78    custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
  79    pub(super) buffer_header_height: u32,
  80    pub(super) excerpt_header_height: u32,
  81}
  82
  83impl Deref for BlockSnapshot {
  84    type Target = WrapSnapshot;
  85
  86    fn deref(&self) -> &Self::Target {
  87        &self.wrap_snapshot
  88    }
  89}
  90
  91#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
  92pub struct CustomBlockId(pub usize);
  93
  94impl From<CustomBlockId> for ElementId {
  95    fn from(val: CustomBlockId) -> Self {
  96        val.0.into()
  97    }
  98}
  99
 100#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
 101pub struct SpacerId(pub usize);
 102
 103/// A zero-indexed point in a text buffer consisting of a row and column
 104/// adjusted for inserted blocks, wrapped rows, tabs, folds and inlays.
 105#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 106pub struct BlockPoint(pub Point);
 107
 108#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 109pub struct BlockRow(pub u32);
 110
 111impl_for_row_types! {
 112    BlockRow => RowDelta
 113}
 114
 115impl BlockPoint {
 116    pub fn row(&self) -> BlockRow {
 117        BlockRow(self.0.row)
 118    }
 119}
 120
 121pub type RenderBlock = Arc<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
 122
 123/// Where to place a block.
 124#[derive(Clone, Debug, Eq, PartialEq)]
 125pub enum BlockPlacement<T> {
 126    /// Place the block above the given position.
 127    Above(T),
 128    /// Place the block below the given position.
 129    Below(T),
 130    /// Place the block next the given position.
 131    Near(T),
 132    /// Replace the given range of positions with the block.
 133    Replace(RangeInclusive<T>),
 134}
 135
 136impl<T> BlockPlacement<T> {
 137    pub fn start(&self) -> &T {
 138        match self {
 139            BlockPlacement::Above(position) => position,
 140            BlockPlacement::Below(position) => position,
 141            BlockPlacement::Near(position) => position,
 142            BlockPlacement::Replace(range) => range.start(),
 143        }
 144    }
 145
 146    fn end(&self) -> &T {
 147        match self {
 148            BlockPlacement::Above(position) => position,
 149            BlockPlacement::Below(position) => position,
 150            BlockPlacement::Near(position) => position,
 151            BlockPlacement::Replace(range) => range.end(),
 152        }
 153    }
 154
 155    pub fn as_ref(&self) -> BlockPlacement<&T> {
 156        match self {
 157            BlockPlacement::Above(position) => BlockPlacement::Above(position),
 158            BlockPlacement::Below(position) => BlockPlacement::Below(position),
 159            BlockPlacement::Near(position) => BlockPlacement::Near(position),
 160            BlockPlacement::Replace(range) => BlockPlacement::Replace(range.start()..=range.end()),
 161        }
 162    }
 163
 164    pub fn map<R>(self, mut f: impl FnMut(T) -> R) -> BlockPlacement<R> {
 165        match self {
 166            BlockPlacement::Above(position) => BlockPlacement::Above(f(position)),
 167            BlockPlacement::Below(position) => BlockPlacement::Below(f(position)),
 168            BlockPlacement::Near(position) => BlockPlacement::Near(f(position)),
 169            BlockPlacement::Replace(range) => {
 170                let (start, end) = range.into_inner();
 171                BlockPlacement::Replace(f(start)..=f(end))
 172            }
 173        }
 174    }
 175
 176    fn tie_break(&self) -> u8 {
 177        match self {
 178            BlockPlacement::Replace(_) => 0,
 179            BlockPlacement::Above(_) => 1,
 180            BlockPlacement::Near(_) => 2,
 181            BlockPlacement::Below(_) => 3,
 182        }
 183    }
 184}
 185
 186impl BlockPlacement<Anchor> {
 187    #[ztracing::instrument(skip_all)]
 188    fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
 189        self.start()
 190            .cmp(other.start(), buffer)
 191            .then_with(|| other.end().cmp(self.end(), buffer))
 192            .then_with(|| self.tie_break().cmp(&other.tie_break()))
 193    }
 194
 195    #[ztracing::instrument(skip_all)]
 196    fn to_wrap_row(&self, wrap_snapshot: &WrapSnapshot) -> Option<BlockPlacement<WrapRow>> {
 197        let buffer_snapshot = wrap_snapshot.buffer_snapshot();
 198        match self {
 199            BlockPlacement::Above(position) => {
 200                let mut position = position.to_point(buffer_snapshot);
 201                position.column = 0;
 202                let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
 203                Some(BlockPlacement::Above(wrap_row))
 204            }
 205            BlockPlacement::Near(position) => {
 206                let mut position = position.to_point(buffer_snapshot);
 207                position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
 208                let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
 209                Some(BlockPlacement::Near(wrap_row))
 210            }
 211            BlockPlacement::Below(position) => {
 212                let mut position = position.to_point(buffer_snapshot);
 213                position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
 214                let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row();
 215                Some(BlockPlacement::Below(wrap_row))
 216            }
 217            BlockPlacement::Replace(range) => {
 218                let mut start = range.start().to_point(buffer_snapshot);
 219                let mut end = range.end().to_point(buffer_snapshot);
 220                if start == end {
 221                    None
 222                } else {
 223                    start.column = 0;
 224                    let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
 225                    end.column = buffer_snapshot.line_len(MultiBufferRow(end.row));
 226                    let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
 227                    Some(BlockPlacement::Replace(start_wrap_row..=end_wrap_row))
 228                }
 229            }
 230        }
 231    }
 232}
 233
 234pub struct CustomBlock {
 235    pub id: CustomBlockId,
 236    pub placement: BlockPlacement<Anchor>,
 237    pub height: Option<u32>,
 238    style: BlockStyle,
 239    render: Arc<Mutex<RenderBlock>>,
 240    priority: usize,
 241}
 242
 243#[derive(Clone)]
 244pub struct BlockProperties<P> {
 245    pub placement: BlockPlacement<P>,
 246    // None if the block takes up no space
 247    // (e.g. a horizontal line)
 248    pub height: Option<u32>,
 249    pub style: BlockStyle,
 250    pub render: RenderBlock,
 251    pub priority: usize,
 252}
 253
 254impl<P: Debug> Debug for BlockProperties<P> {
 255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 256        f.debug_struct("BlockProperties")
 257            .field("placement", &self.placement)
 258            .field("height", &self.height)
 259            .field("style", &self.style)
 260            .finish()
 261    }
 262}
 263
 264#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
 265pub enum BlockStyle {
 266    Fixed,
 267    Flex,
 268    Sticky,
 269}
 270
 271#[derive(Debug, Default, Copy, Clone)]
 272pub struct EditorMargins {
 273    pub gutter: GutterDimensions,
 274    pub right: Pixels,
 275}
 276
 277#[derive(gpui::AppContext, gpui::VisualContext)]
 278pub struct BlockContext<'a, 'b> {
 279    #[window]
 280    pub window: &'a mut Window,
 281    #[app]
 282    pub app: &'b mut App,
 283    pub anchor_x: Pixels,
 284    pub max_width: Pixels,
 285    pub margins: &'b EditorMargins,
 286    pub em_width: Pixels,
 287    pub line_height: Pixels,
 288    pub block_id: BlockId,
 289    pub height: u32,
 290    pub selected: bool,
 291    pub editor_style: &'b EditorStyle,
 292}
 293
 294#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
 295pub enum BlockId {
 296    ExcerptBoundary(ExcerptId),
 297    FoldedBuffer(ExcerptId),
 298    Custom(CustomBlockId),
 299    Spacer(SpacerId),
 300}
 301
 302impl From<BlockId> for ElementId {
 303    fn from(value: BlockId) -> Self {
 304        match value {
 305            BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
 306            BlockId::ExcerptBoundary(excerpt_id) => {
 307                ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
 308            }
 309            BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
 310            BlockId::Spacer(SpacerId(id)) => ("Spacer", id).into(),
 311        }
 312    }
 313}
 314
 315impl std::fmt::Display for BlockId {
 316    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 317        match self {
 318            Self::Custom(id) => write!(f, "Block({id:?})"),
 319            Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
 320            Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
 321            Self::Spacer(id) => write!(f, "Spacer({id:?})"),
 322        }
 323    }
 324}
 325
 326#[derive(Clone, Debug)]
 327struct Transform {
 328    summary: TransformSummary,
 329    block: Option<Block>,
 330}
 331
 332#[derive(Clone)]
 333pub enum Block {
 334    Custom(Arc<CustomBlock>),
 335    FoldedBuffer {
 336        first_excerpt: ExcerptInfo,
 337        height: u32,
 338    },
 339    ExcerptBoundary {
 340        excerpt: ExcerptInfo,
 341        height: u32,
 342    },
 343    BufferHeader {
 344        excerpt: ExcerptInfo,
 345        height: u32,
 346    },
 347    Spacer {
 348        id: SpacerId,
 349        height: u32,
 350        is_below: bool,
 351    },
 352}
 353
 354impl Block {
 355    pub fn id(&self) -> BlockId {
 356        match self {
 357            Block::Custom(block) => BlockId::Custom(block.id),
 358            Block::ExcerptBoundary {
 359                excerpt: next_excerpt,
 360                ..
 361            } => BlockId::ExcerptBoundary(next_excerpt.id),
 362            Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
 363            Block::BufferHeader {
 364                excerpt: next_excerpt,
 365                ..
 366            } => BlockId::ExcerptBoundary(next_excerpt.id),
 367            Block::Spacer { id, .. } => BlockId::Spacer(*id),
 368        }
 369    }
 370
 371    pub fn has_height(&self) -> bool {
 372        match self {
 373            Block::Custom(block) => block.height.is_some(),
 374            Block::ExcerptBoundary { .. }
 375            | Block::FoldedBuffer { .. }
 376            | Block::BufferHeader { .. }
 377            | Block::Spacer { .. } => true,
 378        }
 379    }
 380
 381    pub fn height(&self) -> u32 {
 382        match self {
 383            Block::Custom(block) => block.height.unwrap_or(0),
 384            Block::ExcerptBoundary { height, .. }
 385            | Block::FoldedBuffer { height, .. }
 386            | Block::BufferHeader { height, .. }
 387            | Block::Spacer { height, .. } => *height,
 388        }
 389    }
 390
 391    pub fn style(&self) -> BlockStyle {
 392        match self {
 393            Block::Custom(block) => block.style,
 394            Block::ExcerptBoundary { .. }
 395            | Block::FoldedBuffer { .. }
 396            | Block::BufferHeader { .. }
 397            | Block::Spacer { .. } => BlockStyle::Sticky,
 398        }
 399    }
 400
 401    fn place_above(&self) -> bool {
 402        match self {
 403            Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)),
 404            Block::FoldedBuffer { .. } => false,
 405            Block::ExcerptBoundary { .. } => true,
 406            Block::BufferHeader { .. } => true,
 407            Block::Spacer { is_below, .. } => !*is_below,
 408        }
 409    }
 410
 411    pub fn place_near(&self) -> bool {
 412        match self {
 413            Block::Custom(block) => matches!(block.placement, BlockPlacement::Near(_)),
 414            Block::FoldedBuffer { .. } => false,
 415            Block::ExcerptBoundary { .. } => false,
 416            Block::BufferHeader { .. } => false,
 417            Block::Spacer { .. } => false,
 418        }
 419    }
 420
 421    fn place_below(&self) -> bool {
 422        match self {
 423            Block::Custom(block) => matches!(
 424                block.placement,
 425                BlockPlacement::Below(_) | BlockPlacement::Near(_)
 426            ),
 427            Block::FoldedBuffer { .. } => false,
 428            Block::ExcerptBoundary { .. } => false,
 429            Block::BufferHeader { .. } => false,
 430            Block::Spacer { is_below, .. } => *is_below,
 431        }
 432    }
 433
 434    fn is_replacement(&self) -> bool {
 435        match self {
 436            Block::Custom(block) => matches!(block.placement, BlockPlacement::Replace(_)),
 437            Block::FoldedBuffer { .. } => true,
 438            Block::ExcerptBoundary { .. } => false,
 439            Block::BufferHeader { .. } => false,
 440            Block::Spacer { .. } => false,
 441        }
 442    }
 443
 444    fn is_header(&self) -> bool {
 445        match self {
 446            Block::Custom(_) => false,
 447            Block::FoldedBuffer { .. } => true,
 448            Block::ExcerptBoundary { .. } => true,
 449            Block::BufferHeader { .. } => true,
 450            Block::Spacer { .. } => false,
 451        }
 452    }
 453
 454    pub fn is_buffer_header(&self) -> bool {
 455        match self {
 456            Block::Custom(_) => false,
 457            Block::FoldedBuffer { .. } => true,
 458            Block::ExcerptBoundary { .. } => false,
 459            Block::BufferHeader { .. } => true,
 460            Block::Spacer { .. } => false,
 461        }
 462    }
 463}
 464
 465impl Debug for Block {
 466    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 467        match self {
 468            Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
 469            Self::FoldedBuffer {
 470                first_excerpt,
 471                height,
 472            } => f
 473                .debug_struct("FoldedBuffer")
 474                .field("first_excerpt", &first_excerpt)
 475                .field("height", height)
 476                .finish(),
 477            Self::ExcerptBoundary { excerpt, height } => f
 478                .debug_struct("ExcerptBoundary")
 479                .field("excerpt", excerpt)
 480                .field("height", height)
 481                .finish(),
 482            Self::BufferHeader { excerpt, height } => f
 483                .debug_struct("BufferHeader")
 484                .field("excerpt", excerpt)
 485                .field("height", height)
 486                .finish(),
 487            Self::Spacer {
 488                id,
 489                height,
 490                is_below: _,
 491            } => f
 492                .debug_struct("Spacer")
 493                .field("id", id)
 494                .field("height", height)
 495                .finish(),
 496        }
 497    }
 498}
 499
 500#[derive(Clone, Debug, Default)]
 501struct TransformSummary {
 502    input_rows: WrapRow,
 503    output_rows: BlockRow,
 504    longest_row: BlockRow,
 505    longest_row_chars: u32,
 506}
 507
 508pub struct BlockChunks<'a> {
 509    transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
 510    input_chunks: wrap_map::WrapChunks<'a>,
 511    input_chunk: Chunk<'a>,
 512    output_row: BlockRow,
 513    max_output_row: BlockRow,
 514    line_count_overflow: RowDelta,
 515    masked: bool,
 516}
 517
 518#[derive(Clone)]
 519pub struct BlockRows<'a> {
 520    transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
 521    input_rows: wrap_map::WrapRows<'a>,
 522    output_row: BlockRow,
 523    started: bool,
 524}
 525
 526#[derive(Clone, Copy)]
 527pub struct CompanionView<'a> {
 528    display_map_id: EntityId,
 529    companion_wrap_snapshot: &'a WrapSnapshot,
 530    companion_wrap_edits: &'a WrapPatch,
 531    companion: &'a Companion,
 532}
 533
 534impl<'a> CompanionView<'a> {
 535    pub(crate) fn new(
 536        display_map_id: EntityId,
 537        companion_wrap_snapshot: &'a WrapSnapshot,
 538        companion_wrap_edits: &'a WrapPatch,
 539        companion: &'a Companion,
 540    ) -> Self {
 541        Self {
 542            display_map_id,
 543            companion_wrap_snapshot,
 544            companion_wrap_edits,
 545            companion,
 546        }
 547    }
 548}
 549
 550impl<'a> From<CompanionViewMut<'a>> for CompanionView<'a> {
 551    fn from(view_mut: CompanionViewMut<'a>) -> Self {
 552        Self {
 553            display_map_id: view_mut.display_map_id,
 554            companion_wrap_snapshot: view_mut.companion_wrap_snapshot,
 555            companion_wrap_edits: view_mut.companion_wrap_edits,
 556            companion: view_mut.companion,
 557        }
 558    }
 559}
 560
 561impl<'a> From<&'a CompanionViewMut<'a>> for CompanionView<'a> {
 562    fn from(view_mut: &'a CompanionViewMut<'a>) -> Self {
 563        Self {
 564            display_map_id: view_mut.display_map_id,
 565            companion_wrap_snapshot: view_mut.companion_wrap_snapshot,
 566            companion_wrap_edits: view_mut.companion_wrap_edits,
 567            companion: view_mut.companion,
 568        }
 569    }
 570}
 571
 572pub struct CompanionViewMut<'a> {
 573    display_map_id: EntityId,
 574    companion_display_map_id: EntityId,
 575    companion_wrap_snapshot: &'a WrapSnapshot,
 576    companion_wrap_edits: &'a WrapPatch,
 577    companion_multibuffer: &'a MultiBuffer,
 578    companion_block_map: &'a mut BlockMap,
 579    companion: &'a Companion,
 580}
 581
 582impl<'a> CompanionViewMut<'a> {
 583    pub(crate) fn new(
 584        display_map_id: EntityId,
 585        companion_display_map_id: EntityId,
 586        companion_wrap_snapshot: &'a WrapSnapshot,
 587        companion_wrap_edits: &'a WrapPatch,
 588        companion_multibuffer: &'a MultiBuffer,
 589        companion: &'a Companion,
 590        companion_block_map: &'a mut BlockMap,
 591    ) -> Self {
 592        Self {
 593            display_map_id,
 594            companion_display_map_id,
 595            companion_wrap_snapshot,
 596            companion_wrap_edits,
 597            companion_multibuffer,
 598            companion,
 599            companion_block_map,
 600        }
 601    }
 602}
 603
 604impl BlockMap {
 605    #[ztracing::instrument(skip_all)]
 606    pub fn new(
 607        wrap_snapshot: WrapSnapshot,
 608        buffer_header_height: u32,
 609        excerpt_header_height: u32,
 610    ) -> Self {
 611        let row_count = wrap_snapshot.max_point().row() + WrapRow(1);
 612        let mut transforms = SumTree::default();
 613        push_isomorphic(&mut transforms, row_count - WrapRow(0), &wrap_snapshot);
 614        let map = Self {
 615            next_block_id: AtomicUsize::new(0),
 616            custom_blocks: Vec::new(),
 617            custom_blocks_by_id: TreeMap::default(),
 618            folded_buffers: HashSet::default(),
 619            buffers_with_disabled_headers: HashSet::default(),
 620            transforms: RefCell::new(transforms),
 621            wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
 622            buffer_header_height,
 623            excerpt_header_height,
 624            deferred_edits: Cell::default(),
 625        };
 626        map.sync(
 627            &wrap_snapshot,
 628            Patch::new(vec![Edit {
 629                old: WrapRow(0)..row_count,
 630                new: WrapRow(0)..row_count,
 631            }]),
 632            None,
 633        );
 634        map
 635    }
 636
 637    #[ztracing::instrument(skip_all)]
 638    pub(crate) fn read(
 639        &self,
 640        wrap_snapshot: WrapSnapshot,
 641        edits: WrapPatch,
 642        companion_view: Option<CompanionView>,
 643    ) -> BlockMapReader<'_> {
 644        self.sync(&wrap_snapshot, edits, companion_view);
 645        *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
 646        BlockMapReader {
 647            blocks: &self.custom_blocks,
 648            snapshot: BlockSnapshot {
 649                wrap_snapshot,
 650                transforms: self.transforms.borrow().clone(),
 651                custom_blocks_by_id: self.custom_blocks_by_id.clone(),
 652                buffer_header_height: self.buffer_header_height,
 653                excerpt_header_height: self.excerpt_header_height,
 654            },
 655        }
 656    }
 657
 658    #[ztracing::instrument(skip_all)]
 659    pub(crate) fn write<'a>(
 660        &'a mut self,
 661        wrap_snapshot: WrapSnapshot,
 662        edits: WrapPatch,
 663        companion_view: Option<CompanionViewMut<'a>>,
 664    ) -> BlockMapWriter<'a> {
 665        self.sync(
 666            &wrap_snapshot,
 667            edits.clone(),
 668            companion_view.as_ref().map(CompanionView::from),
 669        );
 670        *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
 671        let companion = if let Some(companion_view) = companion_view {
 672            companion_view.companion_block_map.sync(
 673                companion_view.companion_wrap_snapshot,
 674                companion_view.companion_wrap_edits.clone(),
 675                Some(CompanionView::new(
 676                    companion_view.companion_display_map_id,
 677                    &wrap_snapshot,
 678                    &edits,
 679                    companion_view.companion,
 680                )),
 681            );
 682            *companion_view
 683                .companion_block_map
 684                .wrap_snapshot
 685                .borrow_mut() = companion_view.companion_wrap_snapshot.clone();
 686            Some(BlockMapWriterCompanion {
 687                display_map_id: companion_view.display_map_id,
 688                companion_wrap_snapshot: companion_view.companion_wrap_snapshot.clone(),
 689                companion: companion_view.companion,
 690                inverse: Some(BlockMapInverseWriter {
 691                    companion_multibuffer: companion_view.companion_multibuffer,
 692                    companion_writer: Box::new(BlockMapWriter {
 693                        block_map: companion_view.companion_block_map,
 694                        companion: Some(BlockMapWriterCompanion {
 695                            display_map_id: companion_view.companion_display_map_id,
 696                            companion_wrap_snapshot: wrap_snapshot,
 697                            companion: companion_view.companion,
 698                            inverse: None,
 699                        }),
 700                    }),
 701                }),
 702            })
 703        } else {
 704            None
 705        };
 706        BlockMapWriter {
 707            block_map: self,
 708            companion,
 709        }
 710    }
 711
 712    // Warning: doesn't sync the block map, use advisedly
 713    pub(crate) fn insert_block_raw(
 714        &mut self,
 715        block: BlockProperties<Anchor>,
 716        buffer: &MultiBufferSnapshot,
 717    ) -> CustomBlockId {
 718        let id = CustomBlockId(self.next_block_id.fetch_add(1, SeqCst));
 719        let block_ix = match self
 720            .custom_blocks
 721            .binary_search_by(|probe| probe.placement.cmp(&block.placement, &buffer))
 722        {
 723            Ok(ix) | Err(ix) => ix,
 724        };
 725        let new_block = Arc::new(CustomBlock {
 726            id,
 727            placement: block.placement.clone(),
 728            height: block.height,
 729            style: block.style,
 730            render: Arc::new(Mutex::new(block.render.clone())),
 731            priority: block.priority,
 732        });
 733        self.custom_blocks.insert(block_ix, new_block.clone());
 734        self.custom_blocks_by_id.insert(id, new_block);
 735        id
 736    }
 737
 738    // Warning: doesn't sync the block map, use advisedly
 739    pub(crate) fn retain_blocks_raw(&mut self, pred: &mut dyn FnMut(&Arc<CustomBlock>) -> bool) {
 740        let mut ids_to_remove = HashSet::default();
 741        self.custom_blocks.retain(|block| {
 742            let keep = pred(block);
 743            if !keep {
 744                ids_to_remove.insert(block.id);
 745            }
 746            keep
 747        });
 748        self.custom_blocks_by_id
 749            .retain(|id, _| !ids_to_remove.contains(id));
 750    }
 751
 752    // Warning: doesn't sync the block map, use advisedly
 753    pub(crate) fn blocks_raw(&self) -> impl Iterator<Item = &Arc<CustomBlock>> {
 754        self.custom_blocks.iter()
 755    }
 756
 757    #[ztracing::instrument(skip_all, fields(edits = ?edits))]
 758    fn sync(
 759        &self,
 760        wrap_snapshot: &WrapSnapshot,
 761        mut edits: WrapPatch,
 762        companion_view: Option<CompanionView>,
 763    ) {
 764        let buffer = wrap_snapshot.buffer_snapshot();
 765
 766        edits = self.deferred_edits.take().compose(edits);
 767
 768        // Handle changing the last excerpt if it is empty.
 769        if buffer.trailing_excerpt_update_count()
 770            != self
 771                .wrap_snapshot
 772                .borrow()
 773                .buffer_snapshot()
 774                .trailing_excerpt_update_count()
 775        {
 776            let max_point = wrap_snapshot.max_point();
 777            let edit_start = wrap_snapshot.prev_row_boundary(max_point);
 778            let edit_end = max_point.row() + WrapRow(1); // this is end of file
 779            edits = edits.compose([WrapEdit {
 780                old: edit_start..edit_end,
 781                new: edit_start..edit_end,
 782            }]);
 783        }
 784
 785        // Pull in companion edits to ensure we recompute spacers in ranges that have changed in the companion.
 786        if let Some(CompanionView {
 787            companion_wrap_snapshot: companion_new_snapshot,
 788            companion_wrap_edits: companion_edits,
 789            companion,
 790            display_map_id,
 791            ..
 792        }) = companion_view
 793        {
 794            let mut companion_edits_in_my_space: Vec<WrapEdit> = companion_edits
 795                .clone()
 796                .into_inner()
 797                .iter()
 798                .map(|edit| {
 799                    let companion_start = companion_new_snapshot
 800                        .to_point(WrapPoint::new(edit.new.start, 0), Bias::Left);
 801                    let companion_end = companion_new_snapshot
 802                        .to_point(WrapPoint::new(edit.new.end, 0), Bias::Left);
 803
 804                    let my_start = companion
 805                        .convert_point_from_companion(
 806                            display_map_id,
 807                            wrap_snapshot.buffer_snapshot(),
 808                            companion_new_snapshot.buffer_snapshot(),
 809                            companion_start,
 810                        )
 811                        .start;
 812                    let my_end = companion
 813                        .convert_point_from_companion(
 814                            display_map_id,
 815                            wrap_snapshot.buffer_snapshot(),
 816                            companion_new_snapshot.buffer_snapshot(),
 817                            companion_end,
 818                        )
 819                        .end;
 820
 821                    let mut my_start = wrap_snapshot.make_wrap_point(my_start, Bias::Left);
 822                    let mut my_end = wrap_snapshot.make_wrap_point(my_end, Bias::Left);
 823                    // TODO(split-diff) should use trailing_excerpt_update_count for the second case
 824                    if my_end.column() > 0 || my_end == wrap_snapshot.max_point() {
 825                        *my_end.row_mut() += 1;
 826                        *my_end.column_mut() = 0;
 827                    }
 828
 829                    // Empty edits won't survive Patch::compose, but we still need to make sure
 830                    // we recompute spacers when we get them.
 831                    if my_start.row() == my_end.row() {
 832                        if my_end.row() <= wrap_snapshot.max_point().row() {
 833                            *my_end.row_mut() += 1;
 834                            *my_end.column_mut() = 0;
 835                        } else if my_start.row() > WrapRow(0) {
 836                            *my_start.row_mut() += 1;
 837                            *my_start.column_mut() = 0;
 838                        }
 839                    }
 840
 841                    WrapEdit {
 842                        old: my_start.row()..my_end.row(),
 843                        new: my_start.row()..my_end.row(),
 844                    }
 845                })
 846                .collect();
 847
 848            companion_edits_in_my_space.sort_by_key(|edit| edit.old.start);
 849            let mut merged_edits: Vec<WrapEdit> = Vec::new();
 850            for edit in companion_edits_in_my_space {
 851                if let Some(last) = merged_edits.last_mut() {
 852                    if edit.old.start <= last.old.end {
 853                        last.old.end = last.old.end.max(edit.old.end);
 854                        last.new.end = last.new.end.max(edit.new.end);
 855                        continue;
 856                    }
 857                }
 858                merged_edits.push(edit);
 859            }
 860
 861            edits = edits.compose(merged_edits);
 862        }
 863
 864        let edits = edits.into_inner();
 865        if edits.is_empty() {
 866            return;
 867        }
 868
 869        let mut transforms = self.transforms.borrow_mut();
 870        let mut new_transforms = SumTree::default();
 871        let mut cursor = transforms.cursor::<WrapRow>(());
 872        let mut last_block_ix = 0;
 873        let mut blocks_in_edit = Vec::new();
 874        let mut edits = edits.into_iter().peekable();
 875
 876        let mut inlay_point_cursor = wrap_snapshot.inlay_point_cursor();
 877        let mut tab_point_cursor = wrap_snapshot.tab_point_cursor();
 878        let mut fold_point_cursor = wrap_snapshot.fold_point_cursor();
 879        let mut wrap_point_cursor = wrap_snapshot.wrap_point_cursor();
 880
 881        while let Some(edit) = edits.next() {
 882            let span = ztracing::debug_span!("while edits", edit = ?edit);
 883            let _enter = span.enter();
 884
 885            let mut old_start = edit.old.start;
 886            let mut new_start = edit.new.start;
 887
 888            // Only preserve transforms that:
 889            // * Strictly precedes this edit
 890            // * Isomorphic transforms that end *at* the start of the edit
 891            // * Below blocks that end at the start of the edit
 892            // However, if we hit a replace block that ends at the start of the edit we want to reconstruct it.
 893            new_transforms.append(cursor.slice(&old_start, Bias::Left), ());
 894            if let Some(transform) = cursor.item()
 895                && transform.summary.input_rows > WrapRow(0)
 896                && cursor.end() == old_start
 897                && transform.block.as_ref().is_none_or(|b| !b.is_replacement())
 898            {
 899                // Preserve the transform (push and next)
 900                new_transforms.push(transform.clone(), ());
 901                cursor.next();
 902
 903                // Preserve below blocks at start of edit
 904                while let Some(transform) = cursor.item() {
 905                    if transform.block.as_ref().is_some_and(|b| b.place_below()) {
 906                        new_transforms.push(transform.clone(), ());
 907                        cursor.next();
 908                    } else {
 909                        break;
 910                    }
 911                }
 912            }
 913
 914            // Ensure the edit starts at a transform boundary.
 915            // If the edit starts within an isomorphic transform, preserve its prefix
 916            // If the edit lands within a replacement block, expand the edit to include the start of the replaced input range
 917            let transform = cursor.item().unwrap();
 918            let transform_rows_before_edit = old_start - *cursor.start();
 919            if transform_rows_before_edit > RowDelta(0) {
 920                if transform.block.is_none() {
 921                    // Preserve any portion of the old isomorphic transform that precedes this edit.
 922                    push_isomorphic(
 923                        &mut new_transforms,
 924                        transform_rows_before_edit,
 925                        wrap_snapshot,
 926                    );
 927                } else {
 928                    // We landed within a block that replaces some lines, so we
 929                    // extend the edit to start at the beginning of the
 930                    // replacement.
 931                    debug_assert!(transform.summary.input_rows > WrapRow(0));
 932                    old_start -= transform_rows_before_edit;
 933                    new_start -= transform_rows_before_edit;
 934                }
 935            }
 936
 937            // Decide where the edit ends
 938            // * It should end at a transform boundary
 939            // * Coalesce edits that intersect the same transform
 940            let mut old_end = edit.old.end;
 941            let mut new_end = edit.new.end;
 942            loop {
 943                let span = ztracing::debug_span!("decide where edit ends loop");
 944                let _enter = span.enter();
 945                // Seek to the transform starting at or after the end of the edit
 946                cursor.seek(&old_end, Bias::Left);
 947                cursor.next();
 948
 949                // Extend edit to the end of the discarded transform so it is reconstructed in full
 950                let transform_rows_after_edit = *cursor.start() - old_end;
 951                old_end += transform_rows_after_edit;
 952                new_end += transform_rows_after_edit;
 953
 954                // Combine this edit with any subsequent edits that intersect the same transform.
 955                while let Some(next_edit) = edits.peek() {
 956                    if next_edit.old.start <= *cursor.start() {
 957                        old_end = next_edit.old.end;
 958                        new_end = next_edit.new.end;
 959                        cursor.seek(&old_end, Bias::Left);
 960                        cursor.next();
 961                        edits.next();
 962                    } else {
 963                        break;
 964                    }
 965                }
 966
 967                if *cursor.start() == old_end {
 968                    break;
 969                }
 970            }
 971
 972            // Discard below blocks at the end of the edit. They'll be reconstructed.
 973            while let Some(transform) = cursor.item() {
 974                if transform
 975                    .block
 976                    .as_ref()
 977                    .is_some_and(|b| b.place_below() || matches!(b, Block::Spacer { .. }))
 978                {
 979                    cursor.next();
 980                } else {
 981                    break;
 982                }
 983            }
 984
 985            // Find the blocks within this edited region.
 986            let new_buffer_start = wrap_snapshot.to_point(WrapPoint::new(new_start, 0), Bias::Left);
 987            let start_bound = Bound::Included(new_buffer_start);
 988            let start_block_ix =
 989                match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
 990                    probe
 991                        .start()
 992                        .to_point(buffer)
 993                        .cmp(&new_buffer_start)
 994                        // Move left until we find the index of the first block starting within this edit
 995                        .then(Ordering::Greater)
 996                }) {
 997                    Ok(ix) | Err(ix) => last_block_ix + ix,
 998                };
 999
1000            let end_bound;
1001            let end_block_ix = if new_end > wrap_snapshot.max_point().row() {
1002                end_bound = Bound::Unbounded;
1003                self.custom_blocks.len()
1004            } else {
1005                let new_buffer_end = wrap_snapshot.to_point(WrapPoint::new(new_end, 0), Bias::Left);
1006                end_bound = Bound::Excluded(new_buffer_end);
1007                match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
1008                    probe
1009                        .start()
1010                        .to_point(buffer)
1011                        .cmp(&new_buffer_end)
1012                        .then(Ordering::Greater)
1013                }) {
1014                    Ok(ix) | Err(ix) => start_block_ix + ix,
1015                }
1016            };
1017            last_block_ix = end_block_ix;
1018
1019            debug_assert!(blocks_in_edit.is_empty());
1020            // + 8 is chosen arbitrarily to cover some multibuffer headers
1021            blocks_in_edit
1022                .reserve(end_block_ix - start_block_ix + if buffer.is_singleton() { 0 } else { 8 });
1023
1024            blocks_in_edit.extend(
1025                self.custom_blocks[start_block_ix..end_block_ix]
1026                    .iter()
1027                    .filter_map(|block| {
1028                        let placement = block.placement.to_wrap_row(wrap_snapshot)?;
1029                        if let BlockPlacement::Above(row) = placement
1030                            && row < new_start
1031                        {
1032                            return None;
1033                        }
1034                        Some((placement, Block::Custom(block.clone())))
1035                    }),
1036            );
1037
1038            blocks_in_edit.extend(self.header_and_footer_blocks(
1039                buffer,
1040                (start_bound, end_bound),
1041                |point, bias| {
1042                    wrap_point_cursor
1043                        .map(
1044                            tab_point_cursor.map(
1045                                fold_point_cursor.map(inlay_point_cursor.map(point, bias), bias),
1046                            ),
1047                        )
1048                        .row()
1049                },
1050            ));
1051
1052            if let Some(CompanionView {
1053                companion_wrap_snapshot: companion_snapshot,
1054                companion,
1055                display_map_id,
1056                ..
1057            }) = companion_view
1058            {
1059                blocks_in_edit.extend(self.spacer_blocks(
1060                    (start_bound, end_bound),
1061                    wrap_snapshot,
1062                    companion_snapshot,
1063                    companion,
1064                    display_map_id,
1065                ));
1066            }
1067
1068            BlockMap::sort_blocks(&mut blocks_in_edit);
1069
1070            // For each of these blocks, insert a new isomorphic transform preceding the block,
1071            // and then insert the block itself.
1072            let mut just_processed_folded_buffer = false;
1073            for (block_placement, block) in blocks_in_edit.drain(..) {
1074                let span =
1075                    ztracing::debug_span!("for block in edits", block_height = block.height());
1076                let _enter = span.enter();
1077
1078                let mut summary = TransformSummary {
1079                    input_rows: WrapRow(0),
1080                    output_rows: BlockRow(block.height()),
1081                    longest_row: BlockRow(0),
1082                    longest_row_chars: 0,
1083                };
1084
1085                let rows_before_block;
1086                match block_placement {
1087                    BlockPlacement::Above(position) => {
1088                        rows_before_block = position - new_transforms.summary().input_rows;
1089                        just_processed_folded_buffer = false;
1090                    }
1091                    BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
1092                        if just_processed_folded_buffer {
1093                            continue;
1094                        }
1095                        if position + RowDelta(1) < new_transforms.summary().input_rows {
1096                            continue;
1097                        }
1098                        rows_before_block =
1099                            (position + RowDelta(1)) - new_transforms.summary().input_rows;
1100                    }
1101                    BlockPlacement::Replace(ref range) => {
1102                        rows_before_block = *range.start() - new_transforms.summary().input_rows;
1103                        summary.input_rows = WrapRow(1) + (*range.end() - *range.start());
1104                        just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. });
1105                    }
1106                }
1107
1108                push_isomorphic(&mut new_transforms, rows_before_block, wrap_snapshot);
1109                new_transforms.push(
1110                    Transform {
1111                        summary,
1112                        block: Some(block),
1113                    },
1114                    (),
1115                );
1116            }
1117
1118            // Insert an isomorphic transform after the final block.
1119            let rows_after_last_block =
1120                RowDelta(new_end.0).saturating_sub(RowDelta(new_transforms.summary().input_rows.0));
1121            push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot);
1122        }
1123
1124        new_transforms.append(cursor.suffix(), ());
1125        debug_assert_eq!(
1126            new_transforms.summary().input_rows,
1127            wrap_snapshot.max_point().row() + WrapRow(1),
1128        );
1129
1130        drop(cursor);
1131        *transforms = new_transforms;
1132    }
1133
1134    #[ztracing::instrument(skip_all)]
1135    pub fn replace_blocks(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
1136        for block in &mut self.custom_blocks {
1137            if let Some(render) = renderers.remove(&block.id) {
1138                *block.render.lock() = render;
1139            }
1140        }
1141    }
1142
1143    /// Guarantees that `wrap_row_for` is called with points in increasing order.
1144    #[ztracing::instrument(skip_all)]
1145    fn header_and_footer_blocks<'a, R, T>(
1146        &'a self,
1147        buffer: &'a multi_buffer::MultiBufferSnapshot,
1148        range: R,
1149        mut wrap_row_for: impl 'a + FnMut(Point, Bias) -> WrapRow,
1150    ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
1151    where
1152        R: RangeBounds<T>,
1153        T: multi_buffer::ToOffset,
1154    {
1155        let mut boundaries = buffer.excerpt_boundaries_in_range(range).peekable();
1156
1157        std::iter::from_fn(move || {
1158            loop {
1159                let excerpt_boundary = boundaries.next()?;
1160                let wrap_row = wrap_row_for(Point::new(excerpt_boundary.row.0, 0), Bias::Left);
1161
1162                let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
1163                    (None, next) => Some(next.buffer_id),
1164                    (Some(prev), next) => {
1165                        if prev.buffer_id != next.buffer_id {
1166                            Some(next.buffer_id)
1167                        } else {
1168                            None
1169                        }
1170                    }
1171                };
1172
1173                let mut height = 0;
1174
1175                if let Some(new_buffer_id) = new_buffer_id {
1176                    let first_excerpt = excerpt_boundary.next.clone();
1177                    if self.buffers_with_disabled_headers.contains(&new_buffer_id) {
1178                        continue;
1179                    }
1180                    if self.folded_buffers.contains(&new_buffer_id) && buffer.show_headers() {
1181                        let mut last_excerpt_end_row = first_excerpt.end_row;
1182
1183                        while let Some(next_boundary) = boundaries.peek() {
1184                            if next_boundary.next.buffer_id == new_buffer_id {
1185                                last_excerpt_end_row = next_boundary.next.end_row;
1186                            } else {
1187                                break;
1188                            }
1189
1190                            boundaries.next();
1191                        }
1192                        let wrap_end_row = wrap_row_for(
1193                            Point::new(
1194                                last_excerpt_end_row.0,
1195                                buffer.line_len(last_excerpt_end_row),
1196                            ),
1197                            Bias::Right,
1198                        );
1199
1200                        return Some((
1201                            BlockPlacement::Replace(wrap_row..=wrap_end_row),
1202                            Block::FoldedBuffer {
1203                                height: height + self.buffer_header_height,
1204                                first_excerpt,
1205                            },
1206                        ));
1207                    }
1208                }
1209
1210                let starts_new_buffer = new_buffer_id.is_some();
1211                let block = if starts_new_buffer && buffer.show_headers() {
1212                    height += self.buffer_header_height;
1213                    Block::BufferHeader {
1214                        excerpt: excerpt_boundary.next,
1215                        height,
1216                    }
1217                } else if excerpt_boundary.prev.is_some() {
1218                    height += self.excerpt_header_height;
1219                    Block::ExcerptBoundary {
1220                        excerpt: excerpt_boundary.next,
1221                        height,
1222                    }
1223                } else {
1224                    continue;
1225                };
1226
1227                return Some((BlockPlacement::Above(wrap_row), block));
1228            }
1229        })
1230    }
1231
1232    fn spacer_blocks(
1233        &self,
1234        bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
1235        wrap_snapshot: &WrapSnapshot,
1236        companion_snapshot: &WrapSnapshot,
1237        companion: &Companion,
1238        display_map_id: EntityId,
1239    ) -> Vec<(BlockPlacement<WrapRow>, Block)> {
1240        let our_buffer = wrap_snapshot.buffer_snapshot();
1241        let companion_buffer = companion_snapshot.buffer_snapshot();
1242
1243        let patches = companion.convert_rows_to_companion(
1244            display_map_id,
1245            companion_buffer,
1246            our_buffer,
1247            bounds,
1248        );
1249
1250        let mut our_inlay_point_cursor = wrap_snapshot.inlay_point_cursor();
1251        let mut our_fold_point_cursor = wrap_snapshot.fold_point_cursor();
1252        let mut our_tab_point_cursor = wrap_snapshot.tab_point_cursor();
1253        let mut our_wrap_point_cursor = wrap_snapshot.wrap_point_cursor();
1254
1255        let mut our_wrapper = |our_point: Point, bias: Bias| {
1256            our_wrap_point_cursor
1257                .map(our_tab_point_cursor.map(
1258                    our_fold_point_cursor.map(our_inlay_point_cursor.map(our_point, bias), bias),
1259                ))
1260                .row()
1261        };
1262        let mut companion_wrapper = |their_point: Point, bias: Bias| {
1263            // TODO(split-diff) fix companion points being passed in decreasing order
1264            let inlay_point = companion_snapshot
1265                .inlay_snapshot
1266                .inlay_point_cursor()
1267                .map(their_point, bias);
1268            let fold_point = companion_snapshot.to_fold_point(inlay_point, bias);
1269            let tab_point = companion_snapshot.fold_point_to_tab_point(fold_point);
1270            companion_snapshot.tab_point_to_wrap_point(tab_point).row()
1271        };
1272        fn determine_spacer(
1273            our_wrapper: &mut dyn FnMut(Point, Bias) -> WrapRow,
1274            companion_wrapper: &mut dyn FnMut(Point, Bias) -> WrapRow,
1275            our_point: Point,
1276            their_point: Point,
1277            delta: i32,
1278            bias: Bias,
1279        ) -> (i32, Option<(WrapRow, u32)>) {
1280            let our_wrap = our_wrapper(our_point, bias);
1281            let companion_wrap = companion_wrapper(their_point, bias);
1282            let new_delta = companion_wrap.0 as i32 - our_wrap.0 as i32;
1283
1284            let spacer = if new_delta > delta {
1285                let height = (new_delta - delta) as u32;
1286                Some((our_wrap, height))
1287            } else {
1288                None
1289            };
1290            (new_delta, spacer)
1291        }
1292
1293        let mut result = Vec::new();
1294
1295        for excerpt in patches {
1296            let mut source_points = (excerpt.edited_range.start.row..=excerpt.edited_range.end.row)
1297                .map(|row| MultiBufferPoint::new(row, 0))
1298                .chain(if excerpt.edited_range.end.column > 0 {
1299                    Some(excerpt.edited_range.end)
1300                } else {
1301                    None
1302                })
1303                .peekable();
1304            let last_source_point = if excerpt.edited_range.end.column > 0 {
1305                excerpt.edited_range.end
1306            } else {
1307                MultiBufferPoint::new(excerpt.edited_range.end.row, 0)
1308            };
1309
1310            let Some(first_point) = source_points.peek().copied() else {
1311                continue;
1312            };
1313            let edit_for_first_point = excerpt.patch.edit_for_old_position(first_point);
1314
1315            // Because we calculate spacers based on differences in wrap row
1316            // counts between the RHS and LHS for corresponding buffer points,
1317            // we need to calibrate our expectations based on the difference
1318            // in counts before the start of the edit. This difference in
1319            // counts should have been balanced already by spacers above this
1320            // edit, so we only need to insert spacers for when the difference
1321            // in counts diverges from that baseline value.
1322            let (our_baseline, their_baseline) = if edit_for_first_point.old.start < first_point {
1323                // Case 1: We are inside a hunk/group--take the start of the hunk/group on both sides as the baseline.
1324                (
1325                    edit_for_first_point.old.start,
1326                    edit_for_first_point.new.start,
1327                )
1328            } else if first_point.row > excerpt.source_excerpt_range.start.row {
1329                // Case 2: We are not inside a hunk/group--go back by one row to find the baseline.
1330                let prev_point = Point::new(first_point.row - 1, 0);
1331                let edit_for_prev_point = excerpt.patch.edit_for_old_position(prev_point);
1332                (prev_point, edit_for_prev_point.new.end)
1333            } else {
1334                // Case 3: We are at the start of the excerpt--no previous row to use as the baseline.
1335                (first_point, edit_for_first_point.new.start)
1336            };
1337            let our_baseline = our_wrapper(our_baseline, Bias::Left);
1338            let their_baseline = companion_wrapper(
1339                their_baseline.min(excerpt.target_excerpt_range.end),
1340                Bias::Left,
1341            );
1342
1343            let mut delta = their_baseline.0 as i32 - our_baseline.0 as i32;
1344
1345            // If we started out in the middle of a hunk/group, work up to the end of that group to set up the main loop below.
1346            if edit_for_first_point.old.start < first_point {
1347                let mut current_boundary = first_point;
1348                let current_range = edit_for_first_point.new;
1349                while let Some(next_point) = source_points.peek().cloned() {
1350                    let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point);
1351                    if edit_for_next_point.new.end > current_range.end {
1352                        break;
1353                    }
1354                    source_points.next();
1355                    current_boundary = next_point;
1356                }
1357
1358                let (new_delta, spacer) = determine_spacer(
1359                    &mut our_wrapper,
1360                    &mut companion_wrapper,
1361                    current_boundary,
1362                    current_range.end.min(excerpt.target_excerpt_range.end),
1363                    delta,
1364                    Bias::Left,
1365                );
1366
1367                delta = new_delta;
1368                if let Some((wrap_row, height)) = spacer {
1369                    result.push((
1370                        BlockPlacement::Above(wrap_row),
1371                        Block::Spacer {
1372                            id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1373                            height,
1374                            is_below: false,
1375                        },
1376                    ));
1377                }
1378            }
1379
1380            // Main loop: process one hunk/group at a time, possibly inserting spacers before and after.
1381            while let Some(source_point) = source_points.next() {
1382                let mut current_boundary = source_point;
1383                let current_range = excerpt.patch.edit_for_old_position(current_boundary).new;
1384
1385                // This can only occur at the end of an excerpt.
1386                if current_boundary.column > 0 {
1387                    debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
1388                    break;
1389                }
1390
1391                // Align the two sides at the start of this group.
1392                let (delta_at_start, mut spacer_at_start) = determine_spacer(
1393                    &mut our_wrapper,
1394                    &mut companion_wrapper,
1395                    current_boundary,
1396                    current_range.start.min(excerpt.target_excerpt_range.end),
1397                    delta,
1398                    Bias::Left,
1399                );
1400                delta = delta_at_start;
1401
1402                while let Some(next_point) = source_points.peek().copied() {
1403                    let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point);
1404                    if edit_for_next_point.new.end > current_range.end {
1405                        break;
1406                    }
1407
1408                    if let Some((wrap_row, height)) = spacer_at_start.take() {
1409                        result.push((
1410                            BlockPlacement::Above(wrap_row),
1411                            Block::Spacer {
1412                                id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1413                                height,
1414                                is_below: false,
1415                            },
1416                        ));
1417                    }
1418
1419                    current_boundary = next_point;
1420                    source_points.next();
1421                }
1422
1423                // This can only occur at the end of an excerpt.
1424                if current_boundary.column > 0 {
1425                    debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
1426                    break;
1427                }
1428
1429                let edit_for_current_boundary =
1430                    excerpt.patch.edit_for_old_position(current_boundary);
1431
1432                let spacer_at_end = if current_boundary == edit_for_current_boundary.old.end {
1433                    let (delta_at_end, spacer_at_end) = determine_spacer(
1434                        &mut our_wrapper,
1435                        &mut companion_wrapper,
1436                        current_boundary,
1437                        current_range.end.min(excerpt.target_excerpt_range.end),
1438                        delta,
1439                        Bias::Left,
1440                    );
1441                    delta = delta_at_end;
1442                    spacer_at_end
1443                } else {
1444                    None
1445                };
1446
1447                if let Some((wrap_row, mut height)) = spacer_at_start {
1448                    if let Some((_, additional_height)) = spacer_at_end {
1449                        height += additional_height;
1450                    }
1451                    result.push((
1452                        BlockPlacement::Above(wrap_row),
1453                        Block::Spacer {
1454                            id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1455                            height,
1456                            is_below: false,
1457                        },
1458                    ));
1459                } else if let Some((wrap_row, height)) = spacer_at_end {
1460                    result.push((
1461                        BlockPlacement::Above(wrap_row),
1462                        Block::Spacer {
1463                            id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1464                            height,
1465                            is_below: false,
1466                        },
1467                    ));
1468                }
1469            }
1470
1471            if last_source_point == excerpt.source_excerpt_range.end {
1472                let (_new_delta, spacer) = determine_spacer(
1473                    &mut our_wrapper,
1474                    &mut companion_wrapper,
1475                    last_source_point,
1476                    excerpt.target_excerpt_range.end,
1477                    delta,
1478                    Bias::Right,
1479                );
1480                if let Some((wrap_row, height)) = spacer {
1481                    result.push((
1482                        BlockPlacement::Below(wrap_row),
1483                        Block::Spacer {
1484                            id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1485                            height,
1486                            is_below: true,
1487                        },
1488                    ));
1489                }
1490            }
1491        }
1492
1493        result
1494    }
1495
1496    #[ztracing::instrument(skip_all)]
1497    fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
1498        blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
1499            placement_a
1500                .start()
1501                .cmp(placement_b.start())
1502                .then_with(|| placement_b.end().cmp(placement_a.end()))
1503                .then_with(|| placement_a.tie_break().cmp(&placement_b.tie_break()))
1504                .then_with(|| {
1505                    if block_a.is_header() {
1506                        Ordering::Less
1507                    } else if block_b.is_header() {
1508                        Ordering::Greater
1509                    } else {
1510                        Ordering::Equal
1511                    }
1512                })
1513                .then_with(|| match (block_a, block_b) {
1514                    (
1515                        Block::ExcerptBoundary {
1516                            excerpt: excerpt_a, ..
1517                        }
1518                        | Block::BufferHeader {
1519                            excerpt: excerpt_a, ..
1520                        },
1521                        Block::ExcerptBoundary {
1522                            excerpt: excerpt_b, ..
1523                        }
1524                        | Block::BufferHeader {
1525                            excerpt: excerpt_b, ..
1526                        },
1527                    ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
1528                    (
1529                        Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
1530                        Block::Spacer { .. } | Block::Custom(_),
1531                    ) => Ordering::Less,
1532                    (
1533                        Block::Spacer { .. } | Block::Custom(_),
1534                        Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
1535                    ) => Ordering::Greater,
1536                    (Block::Spacer { .. }, Block::Custom(_)) => Ordering::Less,
1537                    (Block::Custom(_), Block::Spacer { .. }) => Ordering::Greater,
1538                    (Block::Custom(block_a), Block::Custom(block_b)) => block_a
1539                        .priority
1540                        .cmp(&block_b.priority)
1541                        .then_with(|| block_a.id.cmp(&block_b.id)),
1542                    _ => {
1543                        unreachable!("comparing blocks: {block_a:?} vs {block_b:?}")
1544                    }
1545                })
1546        });
1547        blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
1548            (BlockPlacement::Replace(range), BlockPlacement::Above(row))
1549            | (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
1550            (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
1551                if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
1552                    left.0 = BlockPlacement::Replace(
1553                        *range_a.start()..=*range_a.end().max(range_b.end()),
1554                    );
1555                    true
1556                } else {
1557                    false
1558                }
1559            }
1560            _ => false,
1561        });
1562    }
1563}
1564
1565#[ztracing::instrument(skip(tree, wrap_snapshot))]
1566fn push_isomorphic(tree: &mut SumTree<Transform>, rows: RowDelta, wrap_snapshot: &WrapSnapshot) {
1567    if rows == RowDelta(0) {
1568        return;
1569    }
1570
1571    let wrap_row_start = tree.summary().input_rows;
1572    let wrap_row_end = wrap_row_start + rows;
1573    let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
1574    let summary = TransformSummary {
1575        input_rows: WrapRow(rows.0),
1576        output_rows: BlockRow(rows.0),
1577        longest_row: BlockRow(wrap_summary.longest_row),
1578        longest_row_chars: wrap_summary.longest_row_chars,
1579    };
1580    let mut merged = false;
1581    tree.update_last(
1582        |last_transform| {
1583            if last_transform.block.is_none() {
1584                last_transform.summary.add_summary(&summary);
1585                merged = true;
1586            }
1587        },
1588        (),
1589    );
1590    if !merged {
1591        tree.push(
1592            Transform {
1593                summary,
1594                block: None,
1595            },
1596            (),
1597        );
1598    }
1599}
1600
1601impl BlockPoint {
1602    pub fn new(row: BlockRow, column: u32) -> Self {
1603        Self(Point::new(row.0, column))
1604    }
1605}
1606
1607impl Deref for BlockPoint {
1608    type Target = Point;
1609
1610    fn deref(&self) -> &Self::Target {
1611        &self.0
1612    }
1613}
1614
1615impl std::ops::DerefMut for BlockPoint {
1616    fn deref_mut(&mut self) -> &mut Self::Target {
1617        &mut self.0
1618    }
1619}
1620
1621impl Deref for BlockMapReader<'_> {
1622    type Target = BlockSnapshot;
1623
1624    fn deref(&self) -> &Self::Target {
1625        &self.snapshot
1626    }
1627}
1628
1629impl DerefMut for BlockMapReader<'_> {
1630    fn deref_mut(&mut self) -> &mut Self::Target {
1631        &mut self.snapshot
1632    }
1633}
1634
1635impl BlockMapReader<'_> {
1636    #[ztracing::instrument(skip_all)]
1637    pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
1638        let block = self.blocks.iter().find(|block| block.id == block_id)?;
1639        let buffer_row = block
1640            .start()
1641            .to_point(self.wrap_snapshot.buffer_snapshot())
1642            .row;
1643        let wrap_row = self
1644            .wrap_snapshot
1645            .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
1646            .row();
1647        let start_wrap_row = self
1648            .wrap_snapshot
1649            .prev_row_boundary(WrapPoint::new(wrap_row, 0));
1650        let end_wrap_row = self
1651            .wrap_snapshot
1652            .next_row_boundary(WrapPoint::new(wrap_row, 0))
1653            .unwrap_or(self.wrap_snapshot.max_point().row() + WrapRow(1));
1654
1655        let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
1656        cursor.seek(&start_wrap_row, Bias::Left);
1657        while let Some(transform) = cursor.item() {
1658            if cursor.start().0 > end_wrap_row {
1659                break;
1660            }
1661
1662            if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id())
1663                && id == block_id
1664            {
1665                return Some(cursor.start().1);
1666            }
1667            cursor.next();
1668        }
1669
1670        None
1671    }
1672}
1673
1674pub(crate) fn balancing_block(
1675    my_block: &BlockProperties<Anchor>,
1676    my_snapshot: &MultiBufferSnapshot,
1677    their_snapshot: &MultiBufferSnapshot,
1678    my_display_map_id: EntityId,
1679    companion: &Companion,
1680) -> Option<BlockProperties<Anchor>> {
1681    let my_anchor = my_block.placement.start();
1682    let my_point = my_anchor.to_point(&my_snapshot);
1683    let their_range = companion.convert_point_to_companion(
1684        my_display_map_id,
1685        my_snapshot,
1686        their_snapshot,
1687        my_point,
1688    );
1689    let their_anchor = their_snapshot.anchor_at(their_range.start, my_anchor.bias());
1690    let their_placement = match my_block.placement {
1691        BlockPlacement::Above(_) => BlockPlacement::Above(their_anchor),
1692        BlockPlacement::Below(_) => {
1693            if their_range.is_empty() {
1694                BlockPlacement::Above(their_anchor)
1695            } else {
1696                BlockPlacement::Below(their_anchor)
1697            }
1698        }
1699        // Not supported for balancing
1700        BlockPlacement::Near(_) | BlockPlacement::Replace(_) => return None,
1701    };
1702    Some(BlockProperties {
1703        placement: their_placement,
1704        height: my_block.height,
1705        style: BlockStyle::Sticky,
1706        render: Arc::new(move |cx| {
1707            crate::EditorElement::render_spacer_block(
1708                cx.block_id,
1709                cx.height,
1710                cx.line_height,
1711                cx.window,
1712                cx.app,
1713            )
1714        }),
1715        priority: my_block.priority,
1716    })
1717}
1718
1719impl BlockMapWriterCompanion<'_> {
1720    fn companion_view(&self) -> CompanionView<'_> {
1721        static EMPTY_PATCH: Patch<WrapRow> = Patch::empty();
1722        CompanionView {
1723            display_map_id: self.display_map_id,
1724            companion_wrap_snapshot: &self.companion_wrap_snapshot,
1725            companion_wrap_edits: &EMPTY_PATCH,
1726            companion: self.companion,
1727        }
1728    }
1729}
1730
1731impl BlockMapWriter<'_> {
1732    #[ztracing::instrument(skip_all)]
1733    pub fn insert(
1734        &mut self,
1735        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1736    ) -> Vec<CustomBlockId> {
1737        let blocks = blocks.into_iter();
1738        let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
1739        let mut edits = Patch::default();
1740        let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
1741        let buffer = wrap_snapshot.buffer_snapshot();
1742
1743        let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1744        let mut companion_blocks = Vec::new();
1745        for block in blocks {
1746            if let BlockPlacement::Replace(_) = &block.placement {
1747                debug_assert!(block.height.unwrap() > 0);
1748            }
1749
1750            let id = self.block_map.insert_block_raw(block.clone(), &buffer);
1751            ids.push(id);
1752
1753            let start = block.placement.start().to_point(&buffer);
1754            let end = block.placement.end().to_point(&buffer);
1755            let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1756            let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1757
1758            let (start_row, end_row) = {
1759                previous_wrap_row_range.take_if(|range| {
1760                    !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1761                });
1762                let range = previous_wrap_row_range.get_or_insert_with(|| {
1763                    let start_row =
1764                        wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1765                    let end_row = wrap_snapshot
1766                        .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1767                        .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1768                    start_row..end_row
1769                });
1770                (range.start, range.end)
1771            };
1772
1773            // Insert a matching custom block in the companion (if any)
1774            if let Some(companion) = &mut self.companion
1775                && companion.inverse.is_some()
1776            {
1777                companion_blocks.extend(balancing_block(
1778                    &block,
1779                    &buffer,
1780                    companion.companion_wrap_snapshot.buffer(),
1781                    companion.display_map_id,
1782                    companion.companion,
1783                ));
1784            }
1785
1786            edits = edits.compose([Edit {
1787                old: start_row..end_row,
1788                new: start_row..end_row,
1789            }]);
1790        }
1791
1792        self.block_map.sync(
1793            &wrap_snapshot,
1794            edits,
1795            self.companion
1796                .as_ref()
1797                .map(BlockMapWriterCompanion::companion_view),
1798        );
1799
1800        if let Some(companion) = &mut self.companion
1801            && let Some(inverse) = &mut companion.inverse
1802        {
1803            let companion_ids = inverse.companion_writer.insert(companion_blocks);
1804            companion
1805                .companion
1806                .custom_block_to_balancing_block(companion.display_map_id)
1807                .borrow_mut()
1808                .extend(ids.iter().copied().zip(companion_ids));
1809        }
1810
1811        ids
1812    }
1813
1814    #[ztracing::instrument(skip_all)]
1815    pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
1816        let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
1817        let buffer = wrap_snapshot.buffer_snapshot();
1818        let mut edits = Patch::default();
1819        let mut last_block_buffer_row = None;
1820
1821        let mut companion_heights = HashMap::default();
1822        for block in &mut self.block_map.custom_blocks {
1823            if let Some(new_height) = heights.remove(&block.id) {
1824                if let BlockPlacement::Replace(_) = &block.placement {
1825                    debug_assert!(new_height > 0);
1826                }
1827
1828                if block.height != Some(new_height) {
1829                    let new_block = CustomBlock {
1830                        id: block.id,
1831                        placement: block.placement.clone(),
1832                        height: Some(new_height),
1833                        style: block.style,
1834                        render: block.render.clone(),
1835                        priority: block.priority,
1836                    };
1837                    let new_block = Arc::new(new_block);
1838                    *block = new_block.clone();
1839                    self.block_map
1840                        .custom_blocks_by_id
1841                        .insert(block.id, new_block);
1842
1843                    if let Some(companion) = &self.companion
1844                        && companion.inverse.is_some()
1845                        && let Some(companion_block_id) = companion
1846                            .companion
1847                            .custom_block_to_balancing_block(companion.display_map_id)
1848                            .borrow()
1849                            .get(&block.id)
1850                            .copied()
1851                    {
1852                        companion_heights.insert(companion_block_id, new_height);
1853                    }
1854
1855                    let start_row = block.placement.start().to_point(buffer).row;
1856                    let end_row = block.placement.end().to_point(buffer).row;
1857                    if last_block_buffer_row != Some(end_row) {
1858                        last_block_buffer_row = Some(end_row);
1859                        let start_wrap_row = wrap_snapshot
1860                            .make_wrap_point(Point::new(start_row, 0), Bias::Left)
1861                            .row();
1862                        let end_wrap_row = wrap_snapshot
1863                            .make_wrap_point(Point::new(end_row, 0), Bias::Left)
1864                            .row();
1865                        let start =
1866                            wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1867                        let end = wrap_snapshot
1868                            .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1869                            .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1870                        edits.push(Edit {
1871                            old: start..end,
1872                            new: start..end,
1873                        })
1874                    }
1875                }
1876            }
1877        }
1878
1879        self.block_map.sync(
1880            &wrap_snapshot,
1881            edits,
1882            self.companion
1883                .as_ref()
1884                .map(BlockMapWriterCompanion::companion_view),
1885        );
1886        if let Some(companion) = &mut self.companion
1887            && let Some(inverse) = &mut companion.inverse
1888        {
1889            inverse.companion_writer.resize(companion_heights);
1890        }
1891    }
1892
1893    #[ztracing::instrument(skip_all)]
1894    pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
1895        let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
1896        let buffer = wrap_snapshot.buffer_snapshot();
1897        let mut edits = Patch::default();
1898        let mut last_block_buffer_row = None;
1899        let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1900        let mut companion_block_ids: HashSet<CustomBlockId> = HashSet::default();
1901        self.block_map.custom_blocks.retain(|block| {
1902            if block_ids.contains(&block.id) {
1903                let start = block.placement.start().to_point(buffer);
1904                let end = block.placement.end().to_point(buffer);
1905                if last_block_buffer_row != Some(end.row) {
1906                    last_block_buffer_row = Some(end.row);
1907                    let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1908                    let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1909                    let (start_row, end_row) = {
1910                        previous_wrap_row_range.take_if(|range| {
1911                            !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1912                        });
1913                        let range = previous_wrap_row_range.get_or_insert_with(|| {
1914                            let start_row =
1915                                wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1916                            let end_row = wrap_snapshot
1917                                .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1918                                .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1919                            start_row..end_row
1920                        });
1921                        (range.start, range.end)
1922                    };
1923
1924                    edits.push(Edit {
1925                        old: start_row..end_row,
1926                        new: start_row..end_row,
1927                    })
1928                }
1929                if let Some(companion) = &self.companion
1930                    && companion.inverse.is_some()
1931                {
1932                    companion_block_ids.extend(
1933                        companion
1934                            .companion
1935                            .custom_block_to_balancing_block(companion.display_map_id)
1936                            .borrow()
1937                            .get(&block.id)
1938                            .copied(),
1939                    );
1940                }
1941                false
1942            } else {
1943                true
1944            }
1945        });
1946        self.block_map
1947            .custom_blocks_by_id
1948            .retain(|id, _| !block_ids.contains(id));
1949
1950        self.block_map.sync(
1951            wrap_snapshot,
1952            edits,
1953            self.companion
1954                .as_ref()
1955                .map(BlockMapWriterCompanion::companion_view),
1956        );
1957        if let Some(companion) = &mut self.companion
1958            && let Some(inverse) = &mut companion.inverse
1959        {
1960            companion
1961                .companion
1962                .custom_block_to_balancing_block(companion.display_map_id)
1963                .borrow_mut()
1964                .retain(|id, _| !block_ids.contains(&id));
1965            inverse.companion_writer.remove(companion_block_ids);
1966        }
1967    }
1968
1969    #[ztracing::instrument(skip_all)]
1970    pub fn remove_intersecting_replace_blocks(
1971        &mut self,
1972        ranges: impl IntoIterator<Item = Range<MultiBufferOffset>>,
1973        inclusive: bool,
1974    ) {
1975        let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
1976        let mut blocks_to_remove = HashSet::default();
1977        for range in ranges {
1978            for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1979                if matches!(block.placement, BlockPlacement::Replace(_)) {
1980                    blocks_to_remove.insert(block.id);
1981                }
1982            }
1983        }
1984        drop(wrap_snapshot);
1985        self.remove(blocks_to_remove);
1986    }
1987
1988    pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1989        self.block_map
1990            .buffers_with_disabled_headers
1991            .insert(buffer_id);
1992    }
1993
1994    #[ztracing::instrument(skip_all)]
1995    pub fn fold_buffers(
1996        &mut self,
1997        buffer_ids: impl IntoIterator<Item = BufferId>,
1998        multi_buffer: &MultiBuffer,
1999        cx: &App,
2000    ) {
2001        self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
2002    }
2003
2004    #[ztracing::instrument(skip_all)]
2005    pub fn unfold_buffers(
2006        &mut self,
2007        buffer_ids: impl IntoIterator<Item = BufferId>,
2008        multi_buffer: &MultiBuffer,
2009        cx: &App,
2010    ) {
2011        self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
2012    }
2013
2014    #[ztracing::instrument(skip_all)]
2015    fn fold_or_unfold_buffers(
2016        &mut self,
2017        fold: bool,
2018        buffer_ids: impl IntoIterator<Item = BufferId>,
2019        multi_buffer: &MultiBuffer,
2020        cx: &App,
2021    ) {
2022        let mut ranges = Vec::new();
2023        let mut companion_buffer_ids = HashSet::default();
2024        for buffer_id in buffer_ids {
2025            if fold {
2026                self.block_map.folded_buffers.insert(buffer_id);
2027            } else {
2028                self.block_map.folded_buffers.remove(&buffer_id);
2029            }
2030            ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
2031            if let Some(companion) = &self.companion
2032                && companion.inverse.is_some()
2033            {
2034                companion_buffer_ids.extend(
2035                    companion
2036                        .companion
2037                        .buffer_to_companion_buffer(companion.display_map_id)
2038                        .get(&buffer_id)
2039                        .copied(),
2040                )
2041            }
2042        }
2043        ranges.sort_unstable_by_key(|range| range.start);
2044
2045        let mut edits = Patch::default();
2046        let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
2047        for range in ranges {
2048            let last_edit_row = cmp::min(
2049                wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + WrapRow(1),
2050                wrap_snapshot.max_point().row(),
2051            ) + WrapRow(1);
2052            let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
2053            edits.push(Edit {
2054                old: range.clone(),
2055                new: range,
2056            });
2057        }
2058
2059        self.block_map.sync(
2060            &wrap_snapshot,
2061            edits.clone(),
2062            self.companion
2063                .as_ref()
2064                .map(BlockMapWriterCompanion::companion_view),
2065        );
2066        if let Some(companion) = &mut self.companion
2067            && let Some(inverse) = &mut companion.inverse
2068        {
2069            inverse.companion_writer.fold_or_unfold_buffers(
2070                fold,
2071                companion_buffer_ids,
2072                inverse.companion_multibuffer,
2073                cx,
2074            );
2075        }
2076    }
2077
2078    #[ztracing::instrument(skip_all)]
2079    fn blocks_intersecting_buffer_range(
2080        &self,
2081        range: Range<MultiBufferOffset>,
2082        inclusive: bool,
2083    ) -> &[Arc<CustomBlock>] {
2084        if range.is_empty() && !inclusive {
2085            return &[];
2086        }
2087        let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
2088        let buffer = wrap_snapshot.buffer_snapshot();
2089
2090        let start_block_ix = match self.block_map.custom_blocks.binary_search_by(|block| {
2091            let block_end = block.end().to_offset(buffer);
2092            block_end.cmp(&range.start).then(Ordering::Greater)
2093        }) {
2094            Ok(ix) | Err(ix) => ix,
2095        };
2096        let end_block_ix =
2097            match self.block_map.custom_blocks[start_block_ix..].binary_search_by(|block| {
2098                let block_start = block.start().to_offset(buffer);
2099                block_start.cmp(&range.end).then(if inclusive {
2100                    Ordering::Less
2101                } else {
2102                    Ordering::Greater
2103                })
2104            }) {
2105                Ok(ix) | Err(ix) => ix,
2106            };
2107
2108        &self.block_map.custom_blocks[start_block_ix..][..end_block_ix]
2109    }
2110}
2111
2112impl BlockSnapshot {
2113    #[cfg(test)]
2114    #[ztracing::instrument(skip_all)]
2115    pub fn text(&self) -> String {
2116        self.chunks(
2117            BlockRow(0)..self.transforms.summary().output_rows,
2118            false,
2119            false,
2120            Highlights::default(),
2121        )
2122        .map(|chunk| chunk.text)
2123        .collect()
2124    }
2125
2126    #[ztracing::instrument(skip_all)]
2127    pub(crate) fn chunks<'a>(
2128        &'a self,
2129        rows: Range<BlockRow>,
2130        language_aware: bool,
2131        masked: bool,
2132        highlights: Highlights<'a>,
2133    ) -> BlockChunks<'a> {
2134        let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
2135
2136        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2137        cursor.seek(&rows.start, Bias::Right);
2138        let transform_output_start = cursor.start().0;
2139        let transform_input_start = cursor.start().1;
2140
2141        let mut input_start = transform_input_start;
2142        let mut input_end = transform_input_start;
2143        if let Some(transform) = cursor.item()
2144            && transform.block.is_none()
2145        {
2146            input_start += rows.start - transform_output_start;
2147            input_end += cmp::min(
2148                rows.end - transform_output_start,
2149                RowDelta(transform.summary.input_rows.0),
2150            );
2151        }
2152
2153        BlockChunks {
2154            input_chunks: self.wrap_snapshot.chunks(
2155                input_start..input_end,
2156                language_aware,
2157                highlights,
2158            ),
2159            input_chunk: Default::default(),
2160            transforms: cursor,
2161            output_row: rows.start,
2162            line_count_overflow: RowDelta(0),
2163            max_output_row,
2164            masked,
2165        }
2166    }
2167
2168    #[ztracing::instrument(skip_all)]
2169    pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
2170        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2171        cursor.seek(&start_row, Bias::Right);
2172        let Dimensions(output_start, input_start, _) = cursor.start();
2173        let overshoot = if cursor
2174            .item()
2175            .is_some_and(|transform| transform.block.is_none())
2176        {
2177            start_row - *output_start
2178        } else {
2179            RowDelta(0)
2180        };
2181        let input_start_row = *input_start + overshoot;
2182        BlockRows {
2183            transforms: cursor,
2184            input_rows: self.wrap_snapshot.row_infos(input_start_row),
2185            output_row: start_row,
2186            started: false,
2187        }
2188    }
2189
2190    #[ztracing::instrument(skip_all)]
2191    pub fn blocks_in_range(
2192        &self,
2193        rows: Range<BlockRow>,
2194    ) -> impl Iterator<Item = (BlockRow, &Block)> {
2195        let mut cursor = self.transforms.cursor::<BlockRow>(());
2196        cursor.seek(&rows.start, Bias::Left);
2197        while *cursor.start() < rows.start && cursor.end() <= rows.start {
2198            cursor.next();
2199        }
2200
2201        std::iter::from_fn(move || {
2202            while let Some(transform) = cursor.item() {
2203                let start_row = *cursor.start();
2204                if start_row > rows.end
2205                    || (start_row == rows.end
2206                        && transform
2207                            .block
2208                            .as_ref()
2209                            .is_some_and(|block| block.height() > 0))
2210                {
2211                    break;
2212                }
2213                if let Some(block) = &transform.block {
2214                    cursor.next();
2215                    return Some((start_row, block));
2216                } else {
2217                    cursor.next();
2218                }
2219            }
2220            None
2221        })
2222    }
2223
2224    #[ztracing::instrument(skip_all)]
2225    pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
2226        let top_row = position as u32;
2227        let mut cursor = self.transforms.cursor::<BlockRow>(());
2228        cursor.seek(&BlockRow(top_row), Bias::Right);
2229
2230        while let Some(transform) = cursor.item() {
2231            match &transform.block {
2232                Some(
2233                    Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
2234                ) => {
2235                    return Some(StickyHeaderExcerpt { excerpt });
2236                }
2237                Some(block) if block.is_buffer_header() => return None,
2238                _ => {
2239                    cursor.prev();
2240                    continue;
2241                }
2242            }
2243        }
2244
2245        None
2246    }
2247
2248    #[ztracing::instrument(skip_all)]
2249    pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
2250        let buffer = self.wrap_snapshot.buffer_snapshot();
2251        let wrap_point = match block_id {
2252            BlockId::Custom(custom_block_id) => {
2253                let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
2254                return Some(Block::Custom(custom_block.clone()));
2255            }
2256            BlockId::ExcerptBoundary(next_excerpt_id) => {
2257                let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
2258                self.wrap_snapshot
2259                    .make_wrap_point(excerpt_range.start, Bias::Left)
2260            }
2261            BlockId::FoldedBuffer(excerpt_id) => self
2262                .wrap_snapshot
2263                .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
2264            BlockId::Spacer(_) => return None,
2265        };
2266        let wrap_row = wrap_point.row();
2267
2268        let mut cursor = self.transforms.cursor::<WrapRow>(());
2269        cursor.seek(&wrap_row, Bias::Left);
2270
2271        while let Some(transform) = cursor.item() {
2272            if let Some(block) = transform.block.as_ref() {
2273                if block.id() == block_id {
2274                    return Some(block.clone());
2275                }
2276            } else if *cursor.start() > wrap_row {
2277                break;
2278            }
2279
2280            cursor.next();
2281        }
2282
2283        None
2284    }
2285
2286    #[ztracing::instrument(skip_all)]
2287    pub fn max_point(&self) -> BlockPoint {
2288        let row = self
2289            .transforms
2290            .summary()
2291            .output_rows
2292            .saturating_sub(RowDelta(1));
2293        BlockPoint::new(row, self.line_len(row))
2294    }
2295
2296    #[ztracing::instrument(skip_all)]
2297    pub fn longest_row(&self) -> BlockRow {
2298        self.transforms.summary().longest_row
2299    }
2300
2301    #[ztracing::instrument(skip_all)]
2302    pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
2303        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2304        cursor.seek(&range.start, Bias::Right);
2305
2306        let mut longest_row = range.start;
2307        let mut longest_row_chars = 0;
2308        if let Some(transform) = cursor.item() {
2309            if transform.block.is_none() {
2310                let &Dimensions(output_start, input_start, _) = cursor.start();
2311                let overshoot = range.start - output_start;
2312                let wrap_start_row = input_start + WrapRow(overshoot.0);
2313                let wrap_end_row = cmp::min(
2314                    input_start + WrapRow((range.end - output_start).0),
2315                    cursor.end().1,
2316                );
2317                let summary = self
2318                    .wrap_snapshot
2319                    .text_summary_for_range(wrap_start_row..wrap_end_row);
2320                longest_row = BlockRow(range.start.0 + summary.longest_row);
2321                longest_row_chars = summary.longest_row_chars;
2322            }
2323            cursor.next();
2324        }
2325
2326        let cursor_start_row = cursor.start().0;
2327        if range.end > cursor_start_row {
2328            let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
2329            if summary.longest_row_chars > longest_row_chars {
2330                longest_row = cursor_start_row + summary.longest_row;
2331                longest_row_chars = summary.longest_row_chars;
2332            }
2333
2334            if let Some(transform) = cursor.item()
2335                && transform.block.is_none()
2336            {
2337                let &Dimensions(output_start, input_start, _) = cursor.start();
2338                let overshoot = range.end - output_start;
2339                let wrap_start_row = input_start;
2340                let wrap_end_row = input_start + overshoot;
2341                let summary = self
2342                    .wrap_snapshot
2343                    .text_summary_for_range(wrap_start_row..wrap_end_row);
2344                if summary.longest_row_chars > longest_row_chars {
2345                    longest_row = output_start + RowDelta(summary.longest_row);
2346                }
2347            }
2348        }
2349
2350        longest_row
2351    }
2352
2353    #[ztracing::instrument(skip_all)]
2354    pub(super) fn line_len(&self, row: BlockRow) -> u32 {
2355        let (start, _, item) =
2356            self.transforms
2357                .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
2358        if let Some(transform) = item {
2359            let Dimensions(output_start, input_start, _) = start;
2360            let overshoot = row - output_start;
2361            if transform.block.is_some() {
2362                0
2363            } else {
2364                self.wrap_snapshot.line_len(input_start + overshoot)
2365            }
2366        } else if row == BlockRow(0) {
2367            0
2368        } else {
2369            panic!("row out of range");
2370        }
2371    }
2372
2373    #[ztracing::instrument(skip_all)]
2374    pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
2375        let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2376        item.is_some_and(|t| t.block.is_some())
2377    }
2378
2379    #[ztracing::instrument(skip_all)]
2380    pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
2381        let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2382        let Some(transform) = item else {
2383            return false;
2384        };
2385        matches!(transform.block, Some(Block::FoldedBuffer { .. }))
2386    }
2387
2388    #[ztracing::instrument(skip_all)]
2389    pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
2390        let wrap_point = self
2391            .wrap_snapshot
2392            .make_wrap_point(Point::new(row.0, 0), Bias::Left);
2393        let (_, _, item) = self
2394            .transforms
2395            .find::<WrapRow, _>((), &wrap_point.row(), Bias::Right);
2396        item.is_some_and(|transform| {
2397            transform
2398                .block
2399                .as_ref()
2400                .is_some_and(|block| block.is_replacement())
2401        })
2402    }
2403
2404    #[ztracing::instrument(skip_all)]
2405    pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
2406        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2407        cursor.seek(&BlockRow(point.row), Bias::Right);
2408
2409        let max_input_row = self.transforms.summary().input_rows;
2410        let mut search_left = (bias == Bias::Left && cursor.start().1 > WrapRow(0))
2411            || cursor.end().1 == max_input_row;
2412        let mut reversed = false;
2413
2414        loop {
2415            if let Some(transform) = cursor.item() {
2416                let Dimensions(output_start_row, input_start_row, _) = cursor.start();
2417                let Dimensions(output_end_row, input_end_row, _) = cursor.end();
2418                let output_start = Point::new(output_start_row.0, 0);
2419                let input_start = Point::new(input_start_row.0, 0);
2420                let input_end = Point::new(input_end_row.0, 0);
2421
2422                match transform.block.as_ref() {
2423                    Some(block) => {
2424                        if block.is_replacement()
2425                            && (((bias == Bias::Left || search_left) && output_start <= point.0)
2426                                || (!search_left && output_start >= point.0))
2427                        {
2428                            return BlockPoint(output_start);
2429                        }
2430                    }
2431                    None => {
2432                        let input_point = if point.row >= output_end_row.0 {
2433                            let line_len = self.wrap_snapshot.line_len(input_end_row - RowDelta(1));
2434                            self.wrap_snapshot.clip_point(
2435                                WrapPoint::new(input_end_row - RowDelta(1), line_len),
2436                                bias,
2437                            )
2438                        } else {
2439                            let output_overshoot = point.0.saturating_sub(output_start);
2440                            self.wrap_snapshot
2441                                .clip_point(WrapPoint(input_start + output_overshoot), bias)
2442                        };
2443
2444                        if (input_start..input_end).contains(&input_point.0) {
2445                            let input_overshoot = input_point.0.saturating_sub(input_start);
2446                            return BlockPoint(output_start + input_overshoot);
2447                        }
2448                    }
2449                }
2450
2451                if search_left {
2452                    cursor.prev();
2453                } else {
2454                    cursor.next();
2455                }
2456            } else if reversed {
2457                return self.max_point();
2458            } else {
2459                reversed = true;
2460                search_left = !search_left;
2461                cursor.seek(&BlockRow(point.row), Bias::Right);
2462            }
2463        }
2464    }
2465
2466    #[ztracing::instrument(skip_all)]
2467    pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
2468        let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
2469            (),
2470            &wrap_point.row(),
2471            Bias::Right,
2472        );
2473        if let Some(transform) = item {
2474            if transform.block.is_some() {
2475                BlockPoint::new(start.1, 0)
2476            } else {
2477                let Dimensions(input_start_row, output_start_row, _) = start;
2478                let input_start = Point::new(input_start_row.0, 0);
2479                let output_start = Point::new(output_start_row.0, 0);
2480                let input_overshoot = wrap_point.0 - input_start;
2481                BlockPoint(output_start + input_overshoot)
2482            }
2483        } else {
2484            self.max_point()
2485        }
2486    }
2487
2488    #[ztracing::instrument(skip_all)]
2489    pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
2490        let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
2491            (),
2492            &BlockRow(block_point.row),
2493            Bias::Right,
2494        );
2495        if let Some(transform) = item {
2496            match transform.block.as_ref() {
2497                Some(block) => {
2498                    if block.place_below() {
2499                        let wrap_row = start.1 - RowDelta(1);
2500                        WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2501                    } else if block.place_above() {
2502                        WrapPoint::new(start.1, 0)
2503                    } else if bias == Bias::Left {
2504                        WrapPoint::new(start.1, 0)
2505                    } else {
2506                        let wrap_row = end.1 - RowDelta(1);
2507                        WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2508                    }
2509                }
2510                None => {
2511                    let overshoot = block_point.row() - start.0;
2512                    let wrap_row = start.1 + RowDelta(overshoot.0);
2513                    WrapPoint::new(wrap_row, block_point.column)
2514                }
2515            }
2516        } else {
2517            self.wrap_snapshot.max_point()
2518        }
2519    }
2520}
2521
2522impl BlockChunks<'_> {
2523    /// Go to the next transform
2524    #[ztracing::instrument(skip_all)]
2525    fn advance(&mut self) {
2526        self.input_chunk = Chunk::default();
2527        self.transforms.next();
2528        while let Some(transform) = self.transforms.item() {
2529            if transform
2530                .block
2531                .as_ref()
2532                .is_some_and(|block| block.height() == 0)
2533            {
2534                self.transforms.next();
2535            } else {
2536                break;
2537            }
2538        }
2539
2540        if self
2541            .transforms
2542            .item()
2543            .is_some_and(|transform| transform.block.is_none())
2544        {
2545            let start_input_row = self.transforms.start().1;
2546            let start_output_row = self.transforms.start().0;
2547            if start_output_row < self.max_output_row {
2548                let end_input_row = cmp::min(
2549                    self.transforms.end().1,
2550                    start_input_row + (self.max_output_row - start_output_row),
2551                );
2552                self.input_chunks.seek(start_input_row..end_input_row);
2553            }
2554        }
2555    }
2556}
2557
2558pub struct StickyHeaderExcerpt<'a> {
2559    pub excerpt: &'a ExcerptInfo,
2560}
2561
2562impl<'a> Iterator for BlockChunks<'a> {
2563    type Item = Chunk<'a>;
2564
2565    #[ztracing::instrument(skip_all)]
2566    fn next(&mut self) -> Option<Self::Item> {
2567        if self.output_row >= self.max_output_row {
2568            return None;
2569        }
2570
2571        if self.line_count_overflow > RowDelta(0) {
2572            let lines = self.line_count_overflow.0.min(u128::BITS);
2573            self.line_count_overflow.0 -= lines;
2574            self.output_row += RowDelta(lines);
2575            return Some(Chunk {
2576                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines as usize]) },
2577                chars: 1u128.unbounded_shl(lines).wrapping_sub(1),
2578                ..Default::default()
2579            });
2580        }
2581
2582        let transform = self.transforms.item()?;
2583        if transform.block.is_some() {
2584            let block_start = self.transforms.start().0;
2585            let mut block_end = self.transforms.end().0;
2586            self.advance();
2587            if self.transforms.item().is_none() {
2588                block_end -= RowDelta(1);
2589            }
2590
2591            let start_in_block = self.output_row - block_start;
2592            let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
2593            let line_count = end_in_block - start_in_block;
2594            let lines = RowDelta(line_count.0.min(u128::BITS));
2595            self.line_count_overflow = line_count - lines;
2596            self.output_row += lines;
2597
2598            return Some(Chunk {
2599                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines.0 as usize]) },
2600                chars: 1u128.unbounded_shl(lines.0).wrapping_sub(1),
2601                ..Default::default()
2602            });
2603        }
2604
2605        if self.input_chunk.text.is_empty() {
2606            if let Some(input_chunk) = self.input_chunks.next() {
2607                self.input_chunk = input_chunk;
2608            } else {
2609                if self.output_row < self.max_output_row {
2610                    self.output_row.0 += 1;
2611                    self.advance();
2612                    if self.transforms.item().is_some() {
2613                        return Some(Chunk {
2614                            text: "\n",
2615                            chars: 1,
2616                            ..Default::default()
2617                        });
2618                    }
2619                }
2620                return None;
2621            }
2622        }
2623
2624        let transform_end = self.transforms.end().0;
2625        let (prefix_rows, prefix_bytes) =
2626            offset_for_row(self.input_chunk.text, transform_end - self.output_row);
2627        self.output_row += prefix_rows;
2628
2629        let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
2630        self.input_chunk.text = suffix;
2631        self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
2632        self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
2633        self.input_chunk.newlines >>= prefix_bytes.saturating_sub(1);
2634
2635        let mut tabs = self.input_chunk.tabs;
2636        let mut chars = self.input_chunk.chars;
2637        let mut newlines = self.input_chunk.newlines;
2638
2639        if self.masked {
2640            // Not great for multibyte text because to keep cursor math correct we
2641            // need to have the same number of chars in the input as output.
2642            let chars_count = prefix.chars().count();
2643            let bullet_len = chars_count;
2644            prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
2645            chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
2646            tabs = 0;
2647            newlines = 0;
2648        }
2649
2650        let chunk = Chunk {
2651            text: prefix,
2652            tabs,
2653            chars,
2654            newlines,
2655            ..self.input_chunk.clone()
2656        };
2657
2658        if self.output_row == transform_end {
2659            self.advance();
2660        }
2661
2662        Some(chunk)
2663    }
2664}
2665
2666impl Iterator for BlockRows<'_> {
2667    type Item = RowInfo;
2668
2669    #[ztracing::instrument(skip_all)]
2670    fn next(&mut self) -> Option<Self::Item> {
2671        if self.started {
2672            self.output_row.0 += 1;
2673        } else {
2674            self.started = true;
2675        }
2676
2677        if self.output_row >= self.transforms.end().0 {
2678            self.transforms.next();
2679            while let Some(transform) = self.transforms.item() {
2680                if transform
2681                    .block
2682                    .as_ref()
2683                    .is_some_and(|block| block.height() == 0)
2684                {
2685                    self.transforms.next();
2686                } else {
2687                    break;
2688                }
2689            }
2690
2691            let transform = self.transforms.item()?;
2692            if transform
2693                .block
2694                .as_ref()
2695                .is_none_or(|block| block.is_replacement())
2696            {
2697                self.input_rows.seek(self.transforms.start().1);
2698            }
2699        }
2700
2701        let transform = self.transforms.item()?;
2702        if transform.block.as_ref().is_none_or(|block| {
2703            block.is_replacement()
2704                && self.transforms.start().0 == self.output_row
2705                && matches!(block, Block::FoldedBuffer { .. }).not()
2706        }) {
2707            self.input_rows.next()
2708        } else {
2709            Some(RowInfo::default())
2710        }
2711    }
2712}
2713
2714impl sum_tree::Item for Transform {
2715    type Summary = TransformSummary;
2716
2717    fn summary(&self, _cx: ()) -> Self::Summary {
2718        self.summary.clone()
2719    }
2720}
2721
2722impl sum_tree::ContextLessSummary for TransformSummary {
2723    fn zero() -> Self {
2724        Default::default()
2725    }
2726
2727    fn add_summary(&mut self, summary: &Self) {
2728        if summary.longest_row_chars > self.longest_row_chars {
2729            self.longest_row = self.output_rows + summary.longest_row;
2730            self.longest_row_chars = summary.longest_row_chars;
2731        }
2732        self.input_rows += summary.input_rows;
2733        self.output_rows += summary.output_rows;
2734    }
2735}
2736
2737impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
2738    fn zero(_cx: ()) -> Self {
2739        Default::default()
2740    }
2741
2742    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2743        *self += summary.input_rows;
2744    }
2745}
2746
2747impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
2748    fn zero(_cx: ()) -> Self {
2749        Default::default()
2750    }
2751
2752    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2753        *self += summary.output_rows;
2754    }
2755}
2756
2757impl Deref for BlockContext<'_, '_> {
2758    type Target = App;
2759
2760    fn deref(&self) -> &Self::Target {
2761        self.app
2762    }
2763}
2764
2765impl DerefMut for BlockContext<'_, '_> {
2766    fn deref_mut(&mut self) -> &mut Self::Target {
2767        self.app
2768    }
2769}
2770
2771impl CustomBlock {
2772    #[ztracing::instrument(skip_all)]
2773    pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
2774        self.render.lock()(cx)
2775    }
2776
2777    #[ztracing::instrument(skip_all)]
2778    pub fn start(&self) -> Anchor {
2779        *self.placement.start()
2780    }
2781
2782    #[ztracing::instrument(skip_all)]
2783    pub fn end(&self) -> Anchor {
2784        *self.placement.end()
2785    }
2786
2787    pub fn style(&self) -> BlockStyle {
2788        self.style
2789    }
2790
2791    pub fn properties(&self) -> BlockProperties<Anchor> {
2792        BlockProperties {
2793            placement: self.placement.clone(),
2794            height: self.height,
2795            style: self.style,
2796            render: Arc::new(|_| {
2797                // Not used
2798                gpui::Empty.into_any_element()
2799            }),
2800            priority: self.priority,
2801        }
2802    }
2803}
2804
2805impl Debug for CustomBlock {
2806    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2807        f.debug_struct("Block")
2808            .field("id", &self.id)
2809            .field("placement", &self.placement)
2810            .field("height", &self.height)
2811            .field("style", &self.style)
2812            .field("priority", &self.priority)
2813            .finish_non_exhaustive()
2814    }
2815}
2816
2817// Count the number of bytes prior to a target point. If the string doesn't contain the target
2818// point, return its total extent. Otherwise return the target point itself.
2819fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) {
2820    let mut row = 0;
2821    let mut offset = 0;
2822    for (ix, line) in s.split('\n').enumerate() {
2823        if ix > 0 {
2824            row += 1;
2825            offset += 1;
2826        }
2827        if row >= target.0 {
2828            break;
2829        }
2830        offset += line.len();
2831    }
2832    (RowDelta(row), offset)
2833}
2834
2835#[cfg(test)]
2836mod tests {
2837    use super::*;
2838    use crate::{
2839        display_map::{
2840            Companion, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
2841        },
2842        split::{convert_lhs_rows_to_rhs, convert_rhs_rows_to_lhs},
2843        test::test_font,
2844    };
2845    use buffer_diff::BufferDiff;
2846    use gpui::{App, AppContext as _, Element, div, font, px};
2847    use itertools::Itertools;
2848    use language::{Buffer, Capability};
2849    use multi_buffer::{ExcerptRange, MultiBuffer};
2850    use rand::prelude::*;
2851    use settings::SettingsStore;
2852    use std::env;
2853    use util::RandomCharIter;
2854
2855    #[gpui::test]
2856    fn test_offset_for_row() {
2857        assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0));
2858        assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0));
2859        assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0));
2860        assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4));
2861        assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0));
2862        assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1));
2863        assert_eq!(
2864            offset_for_row("abc\ndef\nghi", RowDelta(0)),
2865            (RowDelta(0), 0)
2866        );
2867        assert_eq!(
2868            offset_for_row("abc\ndef\nghi", RowDelta(1)),
2869            (RowDelta(1), 4)
2870        );
2871        assert_eq!(
2872            offset_for_row("abc\ndef\nghi", RowDelta(2)),
2873            (RowDelta(2), 8)
2874        );
2875        assert_eq!(
2876            offset_for_row("abc\ndef\nghi", RowDelta(3)),
2877            (RowDelta(2), 11)
2878        );
2879    }
2880
2881    #[gpui::test]
2882    fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2883        cx.update(init_test);
2884
2885        let text = "aaa\nbbb\nccc\nddd";
2886
2887        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2888        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2889        let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2890        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2891        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2892        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2893        let (wrap_map, wraps_snapshot) =
2894            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2895        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2896
2897        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
2898        let block_ids = writer.insert(vec![
2899            BlockProperties {
2900                style: BlockStyle::Fixed,
2901                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2902                height: Some(1),
2903                render: Arc::new(|_| div().into_any()),
2904                priority: 0,
2905            },
2906            BlockProperties {
2907                style: BlockStyle::Fixed,
2908                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2909                height: Some(2),
2910                render: Arc::new(|_| div().into_any()),
2911                priority: 0,
2912            },
2913            BlockProperties {
2914                style: BlockStyle::Fixed,
2915                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2916                height: Some(3),
2917                render: Arc::new(|_| div().into_any()),
2918                priority: 0,
2919            },
2920        ]);
2921
2922        let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2923        assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2924
2925        let blocks = snapshot
2926            .blocks_in_range(BlockRow(0)..BlockRow(8))
2927            .map(|(start_row, block)| {
2928                let block = block.as_custom().unwrap();
2929                (start_row.0..start_row.0 + block.height.unwrap(), block.id)
2930            })
2931            .collect::<Vec<_>>();
2932
2933        // When multiple blocks are on the same line, the newer blocks appear first.
2934        assert_eq!(
2935            blocks,
2936            &[
2937                (1..2, block_ids[0]),
2938                (2..4, block_ids[1]),
2939                (7..10, block_ids[2]),
2940            ]
2941        );
2942
2943        assert_eq!(
2944            snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)),
2945            BlockPoint::new(BlockRow(0), 3)
2946        );
2947        assert_eq!(
2948            snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)),
2949            BlockPoint::new(BlockRow(4), 0)
2950        );
2951        assert_eq!(
2952            snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)),
2953            BlockPoint::new(BlockRow(6), 3)
2954        );
2955
2956        assert_eq!(
2957            snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left),
2958            WrapPoint::new(WrapRow(0), 3)
2959        );
2960        assert_eq!(
2961            snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2962            WrapPoint::new(WrapRow(1), 0)
2963        );
2964        assert_eq!(
2965            snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left),
2966            WrapPoint::new(WrapRow(1), 0)
2967        );
2968        assert_eq!(
2969            snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2970            WrapPoint::new(WrapRow(3), 3)
2971        );
2972
2973        assert_eq!(
2974            snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2975            BlockPoint::new(BlockRow(0), 3)
2976        );
2977        assert_eq!(
2978            snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right),
2979            BlockPoint::new(BlockRow(4), 0)
2980        );
2981        assert_eq!(
2982            snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left),
2983            BlockPoint::new(BlockRow(0), 3)
2984        );
2985        assert_eq!(
2986            snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right),
2987            BlockPoint::new(BlockRow(4), 0)
2988        );
2989        assert_eq!(
2990            snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left),
2991            BlockPoint::new(BlockRow(4), 0)
2992        );
2993        assert_eq!(
2994            snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right),
2995            BlockPoint::new(BlockRow(4), 0)
2996        );
2997        assert_eq!(
2998            snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left),
2999            BlockPoint::new(BlockRow(6), 3)
3000        );
3001        assert_eq!(
3002            snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right),
3003            BlockPoint::new(BlockRow(6), 3)
3004        );
3005        assert_eq!(
3006            snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
3007            BlockPoint::new(BlockRow(6), 3)
3008        );
3009        assert_eq!(
3010            snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right),
3011            BlockPoint::new(BlockRow(6), 3)
3012        );
3013
3014        assert_eq!(
3015            snapshot
3016                .row_infos(BlockRow(0))
3017                .map(|row_info| row_info.buffer_row)
3018                .collect::<Vec<_>>(),
3019            &[
3020                Some(0),
3021                None,
3022                None,
3023                None,
3024                Some(1),
3025                Some(2),
3026                Some(3),
3027                None,
3028                None,
3029                None
3030            ]
3031        );
3032
3033        // Insert a line break, separating two block decorations into separate lines.
3034        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3035            buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
3036            buffer.snapshot(cx)
3037        });
3038
3039        let (inlay_snapshot, inlay_edits) =
3040            inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
3041        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3042        let (tab_snapshot, tab_edits) =
3043            tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
3044        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3045            wrap_map.sync(tab_snapshot, tab_edits, cx)
3046        });
3047        let snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3048        assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
3049    }
3050
3051    #[gpui::test]
3052    fn test_multibuffer_headers_and_footers(cx: &mut App) {
3053        init_test(cx);
3054
3055        let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
3056        let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
3057        let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
3058
3059        let mut excerpt_ids = Vec::new();
3060        let multi_buffer = cx.new(|cx| {
3061            let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
3062            excerpt_ids.extend(multi_buffer.push_excerpts(
3063                buffer1.clone(),
3064                [ExcerptRange::new(0..buffer1.read(cx).len())],
3065                cx,
3066            ));
3067            excerpt_ids.extend(multi_buffer.push_excerpts(
3068                buffer2.clone(),
3069                [ExcerptRange::new(0..buffer2.read(cx).len())],
3070                cx,
3071            ));
3072            excerpt_ids.extend(multi_buffer.push_excerpts(
3073                buffer3.clone(),
3074                [ExcerptRange::new(0..buffer3.read(cx).len())],
3075                cx,
3076            ));
3077
3078            multi_buffer
3079        });
3080
3081        let font = test_font();
3082        let font_size = px(14.);
3083        let font_id = cx.text_system().resolve_font(&font);
3084        let mut wrap_width = px(0.);
3085        for c in "Buff".chars() {
3086            wrap_width += cx
3087                .text_system()
3088                .advance(font_id, font_size, c)
3089                .unwrap()
3090                .width;
3091        }
3092
3093        let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
3094        let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
3095        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3096        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3097        let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
3098
3099        let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3100        let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3101
3102        // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
3103        assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
3104
3105        let blocks: Vec<_> = snapshot
3106            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3107            .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
3108            .collect();
3109        assert_eq!(
3110            blocks,
3111            vec![
3112                (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
3113                (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
3114                (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
3115            ]
3116        );
3117    }
3118
3119    #[gpui::test]
3120    fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
3121        cx.update(init_test);
3122
3123        let text = "aaa\nbbb\nccc\nddd";
3124
3125        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3126        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3127        let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
3128        let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3129        let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3130        let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
3131        let (_wrap_map, wraps_snapshot) =
3132            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3133        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3134
3135        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3136        let block_ids = writer.insert(vec![
3137            BlockProperties {
3138                style: BlockStyle::Fixed,
3139                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3140                height: Some(1),
3141                render: Arc::new(|_| div().into_any()),
3142                priority: 0,
3143            },
3144            BlockProperties {
3145                style: BlockStyle::Fixed,
3146                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
3147                height: Some(2),
3148                render: Arc::new(|_| div().into_any()),
3149                priority: 0,
3150            },
3151            BlockProperties {
3152                style: BlockStyle::Fixed,
3153                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
3154                height: Some(3),
3155                render: Arc::new(|_| div().into_any()),
3156                priority: 0,
3157            },
3158        ]);
3159
3160        {
3161            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3162            assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3163
3164            let mut block_map_writer =
3165                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3166
3167            let mut new_heights = HashMap::default();
3168            new_heights.insert(block_ids[0], 2);
3169            block_map_writer.resize(new_heights);
3170            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3171            assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3172        }
3173
3174        {
3175            let mut block_map_writer =
3176                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3177
3178            let mut new_heights = HashMap::default();
3179            new_heights.insert(block_ids[0], 1);
3180            block_map_writer.resize(new_heights);
3181
3182            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3183            assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3184        }
3185
3186        {
3187            let mut block_map_writer =
3188                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3189
3190            let mut new_heights = HashMap::default();
3191            new_heights.insert(block_ids[0], 0);
3192            block_map_writer.resize(new_heights);
3193
3194            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3195            assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
3196        }
3197
3198        {
3199            let mut block_map_writer =
3200                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3201
3202            let mut new_heights = HashMap::default();
3203            new_heights.insert(block_ids[0], 3);
3204            block_map_writer.resize(new_heights);
3205
3206            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3207            assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3208        }
3209
3210        {
3211            let mut block_map_writer =
3212                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3213
3214            let mut new_heights = HashMap::default();
3215            new_heights.insert(block_ids[0], 3);
3216            block_map_writer.resize(new_heights);
3217
3218            let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3219            // Same height as before, should remain the same
3220            assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3221        }
3222    }
3223
3224    #[gpui::test]
3225    fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
3226        cx.update(init_test);
3227
3228        let text = "one two three\nfour five six\nseven eight";
3229
3230        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3231        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3232        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3233        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3234        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3235        let (_, wraps_snapshot) = cx.update(|cx| {
3236            WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
3237        });
3238        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3239
3240        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3241        writer.insert(vec![
3242            BlockProperties {
3243                style: BlockStyle::Fixed,
3244                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
3245                render: Arc::new(|_| div().into_any()),
3246                height: Some(1),
3247                priority: 0,
3248            },
3249            BlockProperties {
3250                style: BlockStyle::Fixed,
3251                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
3252                render: Arc::new(|_| div().into_any()),
3253                height: Some(1),
3254                priority: 0,
3255            },
3256        ]);
3257
3258        // Blocks with an 'above' disposition go above their corresponding buffer line.
3259        // Blocks with a 'below' disposition go below their corresponding buffer line.
3260        let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3261        assert_eq!(
3262            snapshot.text(),
3263            "one two \nthree\n\nfour five \nsix\n\nseven \neight"
3264        );
3265    }
3266
3267    #[gpui::test]
3268    fn test_replace_lines(cx: &mut gpui::TestAppContext) {
3269        cx.update(init_test);
3270
3271        let text = "line1\nline2\nline3\nline4\nline5";
3272
3273        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3274        let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
3275        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3276        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3277        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3278        let tab_size = 1.try_into().unwrap();
3279        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
3280        let (wrap_map, wraps_snapshot) =
3281            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3282        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3283
3284        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3285        let replace_block_id = writer.insert(vec![BlockProperties {
3286            style: BlockStyle::Fixed,
3287            placement: BlockPlacement::Replace(
3288                buffer_snapshot.anchor_after(Point::new(1, 3))
3289                    ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
3290            ),
3291            height: Some(4),
3292            render: Arc::new(|_| div().into_any()),
3293            priority: 0,
3294        }])[0];
3295
3296        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3297        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3298
3299        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3300            buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
3301            buffer.snapshot(cx)
3302        });
3303        let (inlay_snapshot, inlay_edits) =
3304            inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
3305        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3306        let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3307        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3308            wrap_map.sync(tab_snapshot, tab_edits, cx)
3309        });
3310        let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3311        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3312
3313        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3314            buffer.edit(
3315                [(
3316                    Point::new(1, 5)..Point::new(1, 5),
3317                    "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
3318                )],
3319                None,
3320                cx,
3321            );
3322            buffer.snapshot(cx)
3323        });
3324        let (inlay_snapshot, inlay_edits) = inlay_map.sync(
3325            buffer_snapshot.clone(),
3326            buffer_subscription.consume().into_inner(),
3327        );
3328        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3329        let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3330        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3331            wrap_map.sync(tab_snapshot, tab_edits, cx)
3332        });
3333        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3334        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3335
3336        // Blocks inserted right above the start or right below the end of the replaced region are hidden.
3337        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3338        writer.insert(vec![
3339            BlockProperties {
3340                style: BlockStyle::Fixed,
3341                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
3342                height: Some(1),
3343                render: Arc::new(|_| div().into_any()),
3344                priority: 0,
3345            },
3346            BlockProperties {
3347                style: BlockStyle::Fixed,
3348                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
3349                height: Some(1),
3350                render: Arc::new(|_| div().into_any()),
3351                priority: 0,
3352            },
3353            BlockProperties {
3354                style: BlockStyle::Fixed,
3355                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
3356                height: Some(1),
3357                render: Arc::new(|_| div().into_any()),
3358                priority: 0,
3359            },
3360        ]);
3361        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3362        assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3363
3364        // Ensure blocks inserted *inside* replaced region are hidden.
3365        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3366        writer.insert(vec![
3367            BlockProperties {
3368                style: BlockStyle::Fixed,
3369                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
3370                height: Some(1),
3371                render: Arc::new(|_| div().into_any()),
3372                priority: 0,
3373            },
3374            BlockProperties {
3375                style: BlockStyle::Fixed,
3376                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
3377                height: Some(1),
3378                render: Arc::new(|_| div().into_any()),
3379                priority: 0,
3380            },
3381            BlockProperties {
3382                style: BlockStyle::Fixed,
3383                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
3384                height: Some(1),
3385                render: Arc::new(|_| div().into_any()),
3386                priority: 0,
3387            },
3388        ]);
3389        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3390        assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3391
3392        // Removing the replace block shows all the hidden blocks again.
3393        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3394        writer.remove(HashSet::from_iter([replace_block_id]));
3395        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3396        assert_eq!(
3397            blocks_snapshot.text(),
3398            "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
3399        );
3400    }
3401
3402    #[gpui::test]
3403    fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
3404        cx.update(init_test);
3405
3406        let text = "111\n222\n333\n444\n555\n666";
3407
3408        let buffer = cx.update(|cx| {
3409            MultiBuffer::build_multi(
3410                [
3411                    (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
3412                    (
3413                        text,
3414                        vec![
3415                            Point::new(1, 0)..Point::new(1, 3),
3416                            Point::new(2, 0)..Point::new(2, 3),
3417                            Point::new(3, 0)..Point::new(3, 3),
3418                        ],
3419                    ),
3420                    (
3421                        text,
3422                        vec![
3423                            Point::new(4, 0)..Point::new(4, 3),
3424                            Point::new(5, 0)..Point::new(5, 3),
3425                        ],
3426                    ),
3427                ],
3428                cx,
3429            )
3430        });
3431        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3432        let buffer_ids = buffer_snapshot
3433            .excerpts()
3434            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3435            .dedup()
3436            .collect::<Vec<_>>();
3437        assert_eq!(buffer_ids.len(), 3);
3438        let buffer_id_1 = buffer_ids[0];
3439        let buffer_id_2 = buffer_ids[1];
3440        let buffer_id_3 = buffer_ids[2];
3441
3442        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3443        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3444        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3445        let (_, wrap_snapshot) =
3446            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3447        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3448        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3449
3450        assert_eq!(
3451            blocks_snapshot.text(),
3452            "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
3453        );
3454        assert_eq!(
3455            blocks_snapshot
3456                .row_infos(BlockRow(0))
3457                .map(|i| i.buffer_row)
3458                .collect::<Vec<_>>(),
3459            vec![
3460                None,
3461                None,
3462                Some(0),
3463                None,
3464                None,
3465                Some(1),
3466                None,
3467                Some(2),
3468                None,
3469                Some(3),
3470                None,
3471                None,
3472                Some(4),
3473                None,
3474                Some(5),
3475            ]
3476        );
3477
3478        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3479        let excerpt_blocks_2 = writer.insert(vec![
3480            BlockProperties {
3481                style: BlockStyle::Fixed,
3482                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3483                height: Some(1),
3484                render: Arc::new(|_| div().into_any()),
3485                priority: 0,
3486            },
3487            BlockProperties {
3488                style: BlockStyle::Fixed,
3489                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
3490                height: Some(1),
3491                render: Arc::new(|_| div().into_any()),
3492                priority: 0,
3493            },
3494            BlockProperties {
3495                style: BlockStyle::Fixed,
3496                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
3497                height: Some(1),
3498                render: Arc::new(|_| div().into_any()),
3499                priority: 0,
3500            },
3501        ]);
3502        let excerpt_blocks_3 = writer.insert(vec![
3503            BlockProperties {
3504                style: BlockStyle::Fixed,
3505                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
3506                height: Some(1),
3507                render: Arc::new(|_| div().into_any()),
3508                priority: 0,
3509            },
3510            BlockProperties {
3511                style: BlockStyle::Fixed,
3512                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
3513                height: Some(1),
3514                render: Arc::new(|_| div().into_any()),
3515                priority: 0,
3516            },
3517        ]);
3518
3519        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3520        assert_eq!(
3521            blocks_snapshot.text(),
3522            "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3523        );
3524        assert_eq!(
3525            blocks_snapshot
3526                .row_infos(BlockRow(0))
3527                .map(|i| i.buffer_row)
3528                .collect::<Vec<_>>(),
3529            vec![
3530                None,
3531                None,
3532                Some(0),
3533                None,
3534                None,
3535                None,
3536                Some(1),
3537                None,
3538                None,
3539                Some(2),
3540                None,
3541                Some(3),
3542                None,
3543                None,
3544                None,
3545                None,
3546                Some(4),
3547                None,
3548                Some(5),
3549                None,
3550            ]
3551        );
3552
3553        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3554        buffer.read_with(cx, |buffer, cx| {
3555            writer.fold_buffers([buffer_id_1], buffer, cx);
3556        });
3557        let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
3558            style: BlockStyle::Fixed,
3559            placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
3560            height: Some(1),
3561            render: Arc::new(|_| div().into_any()),
3562            priority: 0,
3563        }]);
3564        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3565        let blocks = blocks_snapshot
3566            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3567            .collect::<Vec<_>>();
3568        for (_, block) in &blocks {
3569            if let BlockId::Custom(custom_block_id) = block.id() {
3570                assert!(
3571                    !excerpt_blocks_1.contains(&custom_block_id),
3572                    "Should have no blocks from the folded buffer"
3573                );
3574                assert!(
3575                    excerpt_blocks_2.contains(&custom_block_id)
3576                        || excerpt_blocks_3.contains(&custom_block_id),
3577                    "Should have only blocks from unfolded buffers"
3578                );
3579            }
3580        }
3581        assert_eq!(
3582            1,
3583            blocks
3584                .iter()
3585                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3586                .count(),
3587            "Should have one folded block, producing a header of the second buffer"
3588        );
3589        assert_eq!(
3590            blocks_snapshot.text(),
3591            "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3592        );
3593        assert_eq!(
3594            blocks_snapshot
3595                .row_infos(BlockRow(0))
3596                .map(|i| i.buffer_row)
3597                .collect::<Vec<_>>(),
3598            vec![
3599                None,
3600                None,
3601                None,
3602                None,
3603                None,
3604                Some(1),
3605                None,
3606                None,
3607                Some(2),
3608                None,
3609                Some(3),
3610                None,
3611                None,
3612                None,
3613                None,
3614                Some(4),
3615                None,
3616                Some(5),
3617                None,
3618            ]
3619        );
3620
3621        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3622        buffer.read_with(cx, |buffer, cx| {
3623            writer.fold_buffers([buffer_id_2], buffer, cx);
3624        });
3625        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3626        let blocks = blocks_snapshot
3627            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3628            .collect::<Vec<_>>();
3629        for (_, block) in &blocks {
3630            if let BlockId::Custom(custom_block_id) = block.id() {
3631                assert!(
3632                    !excerpt_blocks_1.contains(&custom_block_id),
3633                    "Should have no blocks from the folded buffer_1"
3634                );
3635                assert!(
3636                    !excerpt_blocks_2.contains(&custom_block_id),
3637                    "Should have no blocks from the folded buffer_2"
3638                );
3639                assert!(
3640                    excerpt_blocks_3.contains(&custom_block_id),
3641                    "Should have only blocks from unfolded buffers"
3642                );
3643            }
3644        }
3645        assert_eq!(
3646            2,
3647            blocks
3648                .iter()
3649                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3650                .count(),
3651            "Should have two folded blocks, producing headers"
3652        );
3653        assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
3654        assert_eq!(
3655            blocks_snapshot
3656                .row_infos(BlockRow(0))
3657                .map(|i| i.buffer_row)
3658                .collect::<Vec<_>>(),
3659            vec![
3660                None,
3661                None,
3662                None,
3663                None,
3664                None,
3665                None,
3666                None,
3667                Some(4),
3668                None,
3669                Some(5),
3670                None,
3671            ]
3672        );
3673
3674        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3675        buffer.read_with(cx, |buffer, cx| {
3676            writer.unfold_buffers([buffer_id_1], buffer, cx);
3677        });
3678        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3679        let blocks = blocks_snapshot
3680            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3681            .collect::<Vec<_>>();
3682        for (_, block) in &blocks {
3683            if let BlockId::Custom(custom_block_id) = block.id() {
3684                assert!(
3685                    !excerpt_blocks_2.contains(&custom_block_id),
3686                    "Should have no blocks from the folded buffer_2"
3687                );
3688                assert!(
3689                    excerpt_blocks_1.contains(&custom_block_id)
3690                        || excerpt_blocks_3.contains(&custom_block_id),
3691                    "Should have only blocks from unfolded buffers"
3692                );
3693            }
3694        }
3695        assert_eq!(
3696            1,
3697            blocks
3698                .iter()
3699                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3700                .count(),
3701            "Should be back to a single folded buffer, producing a header for buffer_2"
3702        );
3703        assert_eq!(
3704            blocks_snapshot.text(),
3705            "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
3706            "Should have extra newline for 111 buffer, due to a new block added when it was folded"
3707        );
3708        assert_eq!(
3709            blocks_snapshot
3710                .row_infos(BlockRow(0))
3711                .map(|i| i.buffer_row)
3712                .collect::<Vec<_>>(),
3713            vec![
3714                None,
3715                None,
3716                None,
3717                Some(0),
3718                None,
3719                None,
3720                None,
3721                None,
3722                None,
3723                Some(4),
3724                None,
3725                Some(5),
3726                None,
3727            ]
3728        );
3729
3730        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3731        buffer.read_with(cx, |buffer, cx| {
3732            writer.fold_buffers([buffer_id_3], buffer, cx);
3733        });
3734        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3735        let blocks = blocks_snapshot
3736            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3737            .collect::<Vec<_>>();
3738        for (_, block) in &blocks {
3739            if let BlockId::Custom(custom_block_id) = block.id() {
3740                assert!(
3741                    excerpt_blocks_1.contains(&custom_block_id),
3742                    "Should have no blocks from the folded buffer_1"
3743                );
3744                assert!(
3745                    !excerpt_blocks_2.contains(&custom_block_id),
3746                    "Should have only blocks from unfolded buffers"
3747                );
3748                assert!(
3749                    !excerpt_blocks_3.contains(&custom_block_id),
3750                    "Should have only blocks from unfolded buffers"
3751                );
3752            }
3753        }
3754
3755        assert_eq!(
3756            blocks_snapshot.text(),
3757            "\n\n\n111\n\n\n\n",
3758            "Should have a single, first buffer left after folding"
3759        );
3760        assert_eq!(
3761            blocks_snapshot
3762                .row_infos(BlockRow(0))
3763                .map(|i| i.buffer_row)
3764                .collect::<Vec<_>>(),
3765            vec![None, None, None, Some(0), None, None, None, None,]
3766        );
3767    }
3768
3769    #[gpui::test]
3770    fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
3771        cx.update(init_test);
3772
3773        let text = "111";
3774
3775        let buffer = cx.update(|cx| {
3776            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
3777        });
3778        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3779        let buffer_ids = buffer_snapshot
3780            .excerpts()
3781            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3782            .dedup()
3783            .collect::<Vec<_>>();
3784        assert_eq!(buffer_ids.len(), 1);
3785        let buffer_id = buffer_ids[0];
3786
3787        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
3788        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3789        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3790        let (_, wrap_snapshot) =
3791            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3792        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3793        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3794
3795        assert_eq!(blocks_snapshot.text(), "\n\n111");
3796
3797        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3798        buffer.read_with(cx, |buffer, cx| {
3799            writer.fold_buffers([buffer_id], buffer, cx);
3800        });
3801        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3802        let blocks = blocks_snapshot
3803            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3804            .collect::<Vec<_>>();
3805        assert_eq!(
3806            1,
3807            blocks
3808                .iter()
3809                .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
3810                .count(),
3811            "Should have one folded block, producing a header of the second buffer"
3812        );
3813        assert_eq!(blocks_snapshot.text(), "\n");
3814        assert_eq!(
3815            blocks_snapshot
3816                .row_infos(BlockRow(0))
3817                .map(|i| i.buffer_row)
3818                .collect::<Vec<_>>(),
3819            vec![None, None],
3820            "When fully folded, should be no buffer rows"
3821        );
3822    }
3823
3824    #[gpui::test(iterations = 60)]
3825    fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3826        cx.update(init_test);
3827
3828        let operations = env::var("OPERATIONS")
3829            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3830            .unwrap_or(10);
3831
3832        let wrap_width = if rng.random_bool(0.2) {
3833            None
3834        } else {
3835            Some(px(rng.random_range(0.0..=100.0)))
3836        };
3837        let tab_size = 1.try_into().unwrap();
3838        let font_size = px(14.0);
3839        let buffer_start_header_height = rng.random_range(1..=5);
3840        let excerpt_header_height = rng.random_range(1..=5);
3841
3842        log::info!("Wrap width: {:?}", wrap_width);
3843        log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3844        let is_singleton = rng.random();
3845        let buffer = if is_singleton {
3846            let len = rng.random_range(0..10);
3847            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3848            log::info!("initial singleton buffer text: {:?}", text);
3849            cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3850        } else {
3851            cx.update(|cx| {
3852                let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3853                log::info!(
3854                    "initial multi-buffer text: {:?}",
3855                    multibuffer.read(cx).read(cx).text()
3856                );
3857                multibuffer
3858            })
3859        };
3860
3861        let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3862        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3863        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3864        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3865        let font = test_font();
3866        let (wrap_map, wraps_snapshot) =
3867            cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3868        let mut block_map = BlockMap::new(
3869            wraps_snapshot,
3870            buffer_start_header_height,
3871            excerpt_header_height,
3872        );
3873
3874        for _ in 0..operations {
3875            let mut buffer_edits = Vec::new();
3876            match rng.random_range(0..=100) {
3877                0..=19 => {
3878                    let wrap_width = if rng.random_bool(0.2) {
3879                        None
3880                    } else {
3881                        Some(px(rng.random_range(0.0..=100.0)))
3882                    };
3883                    log::info!("Setting wrap width to {:?}", wrap_width);
3884                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3885                }
3886                20..=39 => {
3887                    let block_count = rng.random_range(1..=5);
3888                    let block_properties = (0..block_count)
3889                        .map(|_| {
3890                            let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3891                            let offset = buffer.clip_offset(
3892                                rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3893                                Bias::Left,
3894                            );
3895                            let mut min_height = 0;
3896                            let placement = match rng.random_range(0..3) {
3897                                0 => {
3898                                    min_height = 1;
3899                                    let start = buffer.anchor_after(offset);
3900                                    let end = buffer.anchor_after(buffer.clip_offset(
3901                                        rng.random_range(offset..=buffer.len()),
3902                                        Bias::Left,
3903                                    ));
3904                                    BlockPlacement::Replace(start..=end)
3905                                }
3906                                1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3907                                _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3908                            };
3909
3910                            let height = rng.random_range(min_height..512);
3911                            BlockProperties {
3912                                style: BlockStyle::Fixed,
3913                                placement,
3914                                height: Some(height),
3915                                render: Arc::new(|_| div().into_any()),
3916                                priority: 0,
3917                            }
3918                        })
3919                        .collect::<Vec<_>>();
3920
3921                    let (inlay_snapshot, inlay_edits) =
3922                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3923                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3924                    let (tab_snapshot, tab_edits) =
3925                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3926                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3927                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3928                    });
3929                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3930                    let block_ids =
3931                        block_map.insert(block_properties.iter().map(|props| BlockProperties {
3932                            placement: props.placement.clone(),
3933                            height: props.height,
3934                            style: props.style,
3935                            render: Arc::new(|_| div().into_any()),
3936                            priority: 0,
3937                        }));
3938
3939                    for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3940                        log::info!(
3941                            "inserted block {:?} with height {:?} and id {:?}",
3942                            block_properties
3943                                .placement
3944                                .as_ref()
3945                                .map(|p| p.to_point(&buffer_snapshot)),
3946                            block_properties.height,
3947                            block_id
3948                        );
3949                    }
3950                }
3951                40..=59 if !block_map.custom_blocks.is_empty() => {
3952                    let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3953                    let block_ids_to_remove = block_map
3954                        .custom_blocks
3955                        .choose_multiple(&mut rng, block_count)
3956                        .map(|block| block.id)
3957                        .collect::<HashSet<_>>();
3958
3959                    let (inlay_snapshot, inlay_edits) =
3960                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3961                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3962                    let (tab_snapshot, tab_edits) =
3963                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3964                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3965                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3966                    });
3967                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3968                    log::info!(
3969                        "removing {} blocks: {:?}",
3970                        block_ids_to_remove.len(),
3971                        block_ids_to_remove
3972                    );
3973                    block_map.remove(block_ids_to_remove);
3974                }
3975                60..=79 => {
3976                    if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3977                        log::info!("Noop fold/unfold operation on a singleton buffer");
3978                        continue;
3979                    }
3980                    let (inlay_snapshot, inlay_edits) =
3981                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3982                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3983                    let (tab_snapshot, tab_edits) =
3984                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3985                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3986                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3987                    });
3988                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3989                    let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3990                        let folded_buffers: Vec<_> =
3991                            block_map.block_map.folded_buffers.iter().cloned().collect();
3992                        let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3993                        unfolded_buffers.dedup();
3994                        log::debug!("All buffers {unfolded_buffers:?}");
3995                        log::debug!("Folded buffers {folded_buffers:?}");
3996                        unfolded_buffers.retain(|buffer_id| {
3997                            !block_map.block_map.folded_buffers.contains(buffer_id)
3998                        });
3999                        (unfolded_buffers, folded_buffers)
4000                    });
4001                    let mut folded_count = folded_buffers.len();
4002                    let mut unfolded_count = unfolded_buffers.len();
4003
4004                    let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
4005                    let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
4006                    if !fold && !unfold {
4007                        log::info!(
4008                            "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4009                        );
4010                        continue;
4011                    }
4012
4013                    buffer.update(cx, |buffer, cx| {
4014                        if fold {
4015                            let buffer_to_fold =
4016                                unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
4017                            log::info!("Folding {buffer_to_fold:?}");
4018                            let related_excerpts = buffer_snapshot
4019                                .excerpts()
4020                                .filter_map(|(excerpt_id, buffer, range)| {
4021                                    if buffer.remote_id() == buffer_to_fold {
4022                                        Some((
4023                                            excerpt_id,
4024                                            buffer
4025                                                .text_for_range(range.context)
4026                                                .collect::<String>(),
4027                                        ))
4028                                    } else {
4029                                        None
4030                                    }
4031                                })
4032                                .collect::<Vec<_>>();
4033                            log::info!(
4034                                "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
4035                            );
4036                            folded_count += 1;
4037                            unfolded_count -= 1;
4038                            block_map.fold_buffers([buffer_to_fold], buffer, cx);
4039                        }
4040                        if unfold {
4041                            let buffer_to_unfold =
4042                                folded_buffers[rng.random_range(0..folded_buffers.len())];
4043                            log::info!("Unfolding {buffer_to_unfold:?}");
4044                            unfolded_count += 1;
4045                            folded_count -= 1;
4046                            block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
4047                        }
4048                        log::info!(
4049                            "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4050                        );
4051                    });
4052                }
4053                _ => {
4054                    buffer.update(cx, |buffer, cx| {
4055                        let mutation_count = rng.random_range(1..=5);
4056                        let subscription = buffer.subscribe();
4057                        buffer.randomly_mutate(&mut rng, mutation_count, cx);
4058                        buffer_snapshot = buffer.snapshot(cx);
4059                        buffer_edits.extend(subscription.consume());
4060                        log::info!("buffer text: {:?}", buffer_snapshot.text());
4061                    });
4062                }
4063            }
4064
4065            let (inlay_snapshot, inlay_edits) =
4066                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
4067            let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
4068            let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
4069            let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
4070                wrap_map.sync(tab_snapshot, tab_edits, cx)
4071            });
4072            let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
4073            assert_eq!(
4074                blocks_snapshot.transforms.summary().input_rows,
4075                wraps_snapshot.max_point().row() + RowDelta(1)
4076            );
4077            log::info!("wrapped text: {:?}", wraps_snapshot.text());
4078            log::info!("blocks text: {:?}", blocks_snapshot.text());
4079
4080            let mut expected_blocks = Vec::new();
4081            expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
4082                Some((
4083                    block.placement.to_wrap_row(&wraps_snapshot)?,
4084                    Block::Custom(block.clone()),
4085                ))
4086            }));
4087
4088            let mut inlay_point_cursor = wraps_snapshot.inlay_point_cursor();
4089            let mut tab_point_cursor = wraps_snapshot.tab_point_cursor();
4090            let mut fold_point_cursor = wraps_snapshot.fold_point_cursor();
4091            let mut wrap_point_cursor = wraps_snapshot.wrap_point_cursor();
4092
4093            // Note that this needs to be synced with the related section in BlockMap::sync
4094            expected_blocks.extend(block_map.header_and_footer_blocks(
4095                &buffer_snapshot,
4096                MultiBufferOffset(0)..,
4097                |point, bias| {
4098                    wrap_point_cursor
4099                        .map(
4100                            tab_point_cursor.map(
4101                                fold_point_cursor.map(inlay_point_cursor.map(point, bias), bias),
4102                            ),
4103                        )
4104                        .row()
4105                },
4106            ));
4107
4108            BlockMap::sort_blocks(&mut expected_blocks);
4109
4110            for (placement, block) in &expected_blocks {
4111                log::info!(
4112                    "Block {:?} placement: {:?} Height: {:?}",
4113                    block.id(),
4114                    placement,
4115                    block.height()
4116                );
4117            }
4118
4119            let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
4120
4121            let input_buffer_rows = buffer_snapshot
4122                .row_infos(MultiBufferRow(0))
4123                .map(|row| row.buffer_row)
4124                .collect::<Vec<_>>();
4125            let mut expected_buffer_rows = Vec::new();
4126            let mut expected_text = String::new();
4127            let mut expected_block_positions = Vec::new();
4128            let mut expected_replaced_buffer_rows = HashSet::default();
4129            let input_text = wraps_snapshot.text();
4130
4131            // Loop over the input lines, creating (N - 1) empty lines for
4132            // blocks of height N.
4133            //
4134            // It's important to note that output *starts* as one empty line,
4135            // so we special case row 0 to assume a leading '\n'.
4136            //
4137            // Linehood is the birthright of strings.
4138            let input_text_lines = input_text.split('\n').enumerate().peekable();
4139            let mut block_row = 0;
4140            for (wrap_row, input_line) in input_text_lines {
4141                let wrap_row = WrapRow(wrap_row as u32);
4142                let multibuffer_row = wraps_snapshot
4143                    .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
4144                    .row;
4145
4146                // Create empty lines for the above block
4147                while let Some((placement, block)) = sorted_blocks_iter.peek() {
4148                    if *placement.start() == wrap_row && block.place_above() {
4149                        let (_, block) = sorted_blocks_iter.next().unwrap();
4150                        expected_block_positions.push((block_row, block.id()));
4151                        if block.height() > 0 {
4152                            let text = "\n".repeat((block.height() - 1) as usize);
4153                            if block_row > 0 {
4154                                expected_text.push('\n')
4155                            }
4156                            expected_text.push_str(&text);
4157                            for _ in 0..block.height() {
4158                                expected_buffer_rows.push(None);
4159                            }
4160                            block_row += block.height();
4161                        }
4162                    } else {
4163                        break;
4164                    }
4165                }
4166
4167                // Skip lines within replace blocks, then create empty lines for the replace block's height
4168                let mut is_in_replace_block = false;
4169                if let Some((BlockPlacement::Replace(replace_range), block)) =
4170                    sorted_blocks_iter.peek()
4171                    && wrap_row >= *replace_range.start()
4172                {
4173                    is_in_replace_block = true;
4174
4175                    if wrap_row == *replace_range.start() {
4176                        if matches!(block, Block::FoldedBuffer { .. }) {
4177                            expected_buffer_rows.push(None);
4178                        } else {
4179                            expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
4180                        }
4181                    }
4182
4183                    if wrap_row == *replace_range.end() {
4184                        expected_block_positions.push((block_row, block.id()));
4185                        let text = "\n".repeat((block.height() - 1) as usize);
4186                        if block_row > 0 {
4187                            expected_text.push('\n');
4188                        }
4189                        expected_text.push_str(&text);
4190
4191                        for _ in 1..block.height() {
4192                            expected_buffer_rows.push(None);
4193                        }
4194                        block_row += block.height();
4195
4196                        sorted_blocks_iter.next();
4197                    }
4198                }
4199
4200                if is_in_replace_block {
4201                    expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
4202                } else {
4203                    let buffer_row = input_buffer_rows[multibuffer_row as usize];
4204                    let soft_wrapped = wraps_snapshot
4205                        .to_tab_point(WrapPoint::new(wrap_row, 0))
4206                        .column()
4207                        > 0;
4208                    expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
4209                    if block_row > 0 {
4210                        expected_text.push('\n');
4211                    }
4212                    expected_text.push_str(input_line);
4213                    block_row += 1;
4214                }
4215
4216                while let Some((placement, block)) = sorted_blocks_iter.peek() {
4217                    if *placement.end() == wrap_row && block.place_below() {
4218                        let (_, block) = sorted_blocks_iter.next().unwrap();
4219                        expected_block_positions.push((block_row, block.id()));
4220                        if block.height() > 0 {
4221                            let text = "\n".repeat((block.height() - 1) as usize);
4222                            if block_row > 0 {
4223                                expected_text.push('\n')
4224                            }
4225                            expected_text.push_str(&text);
4226                            for _ in 0..block.height() {
4227                                expected_buffer_rows.push(None);
4228                            }
4229                            block_row += block.height();
4230                        }
4231                    } else {
4232                        break;
4233                    }
4234                }
4235            }
4236
4237            let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
4238            let expected_row_count = expected_lines.len();
4239            log::info!("expected text: {expected_text:?}");
4240
4241            assert_eq!(
4242                blocks_snapshot.max_point().row + 1,
4243                expected_row_count as u32,
4244                "actual row count != expected row count",
4245            );
4246            assert_eq!(
4247                blocks_snapshot.text(),
4248                expected_text,
4249                "actual text != expected text",
4250            );
4251
4252            for start_row in 0..expected_row_count {
4253                let end_row = rng.random_range(start_row + 1..=expected_row_count);
4254                let mut expected_text = expected_lines[start_row..end_row].join("\n");
4255                if end_row < expected_row_count {
4256                    expected_text.push('\n');
4257                }
4258
4259                let actual_text = blocks_snapshot
4260                    .chunks(
4261                        BlockRow(start_row as u32)..BlockRow(end_row as u32),
4262                        false,
4263                        false,
4264                        Highlights::default(),
4265                    )
4266                    .map(|chunk| chunk.text)
4267                    .collect::<String>();
4268                assert_eq!(
4269                    actual_text,
4270                    expected_text,
4271                    "incorrect text starting row row range {:?}",
4272                    start_row..end_row
4273                );
4274                assert_eq!(
4275                    blocks_snapshot
4276                        .row_infos(BlockRow(start_row as u32))
4277                        .map(|row_info| row_info.buffer_row)
4278                        .collect::<Vec<_>>(),
4279                    &expected_buffer_rows[start_row..],
4280                    "incorrect buffer_rows starting at row {:?}",
4281                    start_row
4282                );
4283            }
4284
4285            assert_eq!(
4286                blocks_snapshot
4287                    .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4288                    .map(|(row, block)| (row.0, block.id()))
4289                    .collect::<Vec<_>>(),
4290                expected_block_positions,
4291                "invalid blocks_in_range({:?})",
4292                0..expected_row_count
4293            );
4294
4295            for (_, expected_block) in
4296                blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4297            {
4298                let actual_block = blocks_snapshot.block_for_id(expected_block.id());
4299                assert_eq!(
4300                    actual_block.map(|block| block.id()),
4301                    Some(expected_block.id())
4302                );
4303            }
4304
4305            for (block_row, block_id) in expected_block_positions {
4306                if let BlockId::Custom(block_id) = block_id {
4307                    assert_eq!(
4308                        blocks_snapshot.row_for_block(block_id),
4309                        Some(BlockRow(block_row))
4310                    );
4311                }
4312            }
4313
4314            let mut expected_longest_rows = Vec::new();
4315            let mut longest_line_len = -1_isize;
4316            for (row, line) in expected_lines.iter().enumerate() {
4317                let row = row as u32;
4318
4319                assert_eq!(
4320                    blocks_snapshot.line_len(BlockRow(row)),
4321                    line.len() as u32,
4322                    "invalid line len for row {}",
4323                    row
4324                );
4325
4326                let line_char_count = line.chars().count() as isize;
4327                match line_char_count.cmp(&longest_line_len) {
4328                    Ordering::Less => {}
4329                    Ordering::Equal => expected_longest_rows.push(row),
4330                    Ordering::Greater => {
4331                        longest_line_len = line_char_count;
4332                        expected_longest_rows.clear();
4333                        expected_longest_rows.push(row);
4334                    }
4335                }
4336            }
4337
4338            let longest_row = blocks_snapshot.longest_row();
4339            assert!(
4340                expected_longest_rows.contains(&longest_row.0),
4341                "incorrect longest row {}. expected {:?} with length {}",
4342                longest_row.0,
4343                expected_longest_rows,
4344                longest_line_len,
4345            );
4346
4347            for _ in 0..10 {
4348                let end_row = rng.random_range(1..=expected_lines.len());
4349                let start_row = rng.random_range(0..end_row);
4350
4351                let mut expected_longest_rows_in_range = vec![];
4352                let mut longest_line_len_in_range = 0;
4353
4354                let mut row = start_row as u32;
4355                for line in &expected_lines[start_row..end_row] {
4356                    let line_char_count = line.chars().count() as isize;
4357                    match line_char_count.cmp(&longest_line_len_in_range) {
4358                        Ordering::Less => {}
4359                        Ordering::Equal => expected_longest_rows_in_range.push(row),
4360                        Ordering::Greater => {
4361                            longest_line_len_in_range = line_char_count;
4362                            expected_longest_rows_in_range.clear();
4363                            expected_longest_rows_in_range.push(row);
4364                        }
4365                    }
4366                    row += 1;
4367                }
4368
4369                let longest_row_in_range = blocks_snapshot
4370                    .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
4371                assert!(
4372                    expected_longest_rows_in_range.contains(&longest_row_in_range.0),
4373                    "incorrect longest row {} in range {:?}. expected {:?} with length {}",
4374                    longest_row.0,
4375                    start_row..end_row,
4376                    expected_longest_rows_in_range,
4377                    longest_line_len_in_range,
4378                );
4379            }
4380
4381            // Ensure that conversion between block points and wrap points is stable.
4382            for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
4383                let wrap_point = WrapPoint::new(WrapRow(row), 0);
4384                let block_point = blocks_snapshot.to_block_point(wrap_point);
4385                let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
4386                let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
4387                assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
4388                assert_eq!(
4389                    blocks_snapshot.to_block_point(right_wrap_point),
4390                    block_point
4391                );
4392            }
4393
4394            let mut block_point = BlockPoint::new(BlockRow(0), 0);
4395            for c in expected_text.chars() {
4396                let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
4397                let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
4398                assert_eq!(
4399                    blocks_snapshot
4400                        .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
4401                    left_point,
4402                    "block point: {:?}, wrap point: {:?}",
4403                    block_point,
4404                    blocks_snapshot.to_wrap_point(left_point, Bias::Left)
4405                );
4406                assert_eq!(
4407                    left_buffer_point,
4408                    buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
4409                    "{:?} is not valid in buffer coordinates",
4410                    left_point
4411                );
4412
4413                let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
4414                let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
4415                assert_eq!(
4416                    blocks_snapshot
4417                        .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
4418                    right_point,
4419                    "block point: {:?}, wrap point: {:?}",
4420                    block_point,
4421                    blocks_snapshot.to_wrap_point(right_point, Bias::Right)
4422                );
4423                assert_eq!(
4424                    right_buffer_point,
4425                    buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
4426                    "{:?} is not valid in buffer coordinates",
4427                    right_point
4428                );
4429
4430                if c == '\n' {
4431                    block_point.0 += Point::new(1, 0);
4432                } else {
4433                    block_point.column += c.len_utf8() as u32;
4434                }
4435            }
4436
4437            for buffer_row in 0..=buffer_snapshot.max_point().row {
4438                let buffer_row = MultiBufferRow(buffer_row);
4439                assert_eq!(
4440                    blocks_snapshot.is_line_replaced(buffer_row),
4441                    expected_replaced_buffer_rows.contains(&buffer_row),
4442                    "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
4443                );
4444            }
4445        }
4446    }
4447
4448    #[gpui::test]
4449    fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
4450        cx.update(init_test);
4451
4452        let text = "abc\ndef\nghi\njkl\nmno";
4453        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4454        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4455        let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4456        let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
4457        let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4458        let (_wrap_map, wraps_snapshot) =
4459            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4460        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
4461
4462        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4463        let _block_id = writer.insert(vec![BlockProperties {
4464            style: BlockStyle::Fixed,
4465            placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
4466            height: Some(1),
4467            render: Arc::new(|_| div().into_any()),
4468            priority: 0,
4469        }])[0];
4470
4471        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
4472        assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4473
4474        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4475        writer.remove_intersecting_replace_blocks(
4476            [buffer_snapshot
4477                .anchor_after(Point::new(1, 0))
4478                .to_offset(&buffer_snapshot)
4479                ..buffer_snapshot
4480                    .anchor_after(Point::new(1, 0))
4481                    .to_offset(&buffer_snapshot)],
4482            false,
4483        );
4484        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
4485        assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4486    }
4487
4488    #[gpui::test]
4489    fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
4490        cx.update(init_test);
4491
4492        let text = "line 1\nline 2\nline 3";
4493        let buffer = cx.update(|cx| {
4494            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
4495        });
4496        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4497        let buffer_ids = buffer_snapshot
4498            .excerpts()
4499            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4500            .dedup()
4501            .collect::<Vec<_>>();
4502        assert_eq!(buffer_ids.len(), 1);
4503        let buffer_id = buffer_ids[0];
4504
4505        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4506        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4507        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4508        let (_, wrap_snapshot) =
4509            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4510        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4511
4512        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4513        writer.insert(vec![BlockProperties {
4514            style: BlockStyle::Fixed,
4515            placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
4516            height: Some(1),
4517            render: Arc::new(|_| div().into_any()),
4518            priority: 0,
4519        }]);
4520
4521        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4522        assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
4523
4524        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4525        buffer.read_with(cx, |buffer, cx| {
4526            writer.fold_buffers([buffer_id], buffer, cx);
4527        });
4528
4529        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4530        assert_eq!(blocks_snapshot.text(), "");
4531    }
4532
4533    #[gpui::test]
4534    fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
4535        cx.update(init_test);
4536
4537        let text = "line 1\nline 2\nline 3\nline 4";
4538        let buffer = cx.update(|cx| {
4539            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
4540        });
4541        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4542        let buffer_ids = buffer_snapshot
4543            .excerpts()
4544            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4545            .dedup()
4546            .collect::<Vec<_>>();
4547        assert_eq!(buffer_ids.len(), 1);
4548        let buffer_id = buffer_ids[0];
4549
4550        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4551        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4552        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4553        let (_, wrap_snapshot) =
4554            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4555        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4556
4557        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4558        writer.insert(vec![BlockProperties {
4559            style: BlockStyle::Fixed,
4560            placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
4561            height: Some(1),
4562            render: Arc::new(|_| div().into_any()),
4563            priority: 0,
4564        }]);
4565
4566        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4567        assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
4568
4569        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4570        buffer.read_with(cx, |buffer, cx| {
4571            writer.fold_buffers([buffer_id], buffer, cx);
4572        });
4573
4574        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4575        assert_eq!(blocks_snapshot.text(), "");
4576    }
4577
4578    #[gpui::test]
4579    fn test_companion_spacer_blocks(cx: &mut gpui::TestAppContext) {
4580        cx.update(init_test);
4581
4582        let base_text = "aaa\nbbb\nccc\nddd\nddd\nddd\neee\n";
4583        let main_text = "aaa\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n";
4584
4585        let rhs_buffer = cx.new(|cx| Buffer::local(main_text, cx));
4586        let diff = cx.new(|cx| {
4587            BufferDiff::new_with_base_text(base_text, &rhs_buffer.read(cx).text_snapshot(), cx)
4588        });
4589        let lhs_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer());
4590
4591        let lhs_multibuffer = cx.new(|cx| {
4592            let mut mb = MultiBuffer::new(Capability::ReadWrite);
4593            mb.push_excerpts(
4594                lhs_buffer.clone(),
4595                [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4596                cx,
4597            );
4598            mb.add_inverted_diff(diff.clone(), rhs_buffer.clone(), cx);
4599            mb
4600        });
4601        let rhs_multibuffer = cx.new(|cx| {
4602            let mut mb = MultiBuffer::new(Capability::ReadWrite);
4603            mb.push_excerpts(
4604                rhs_buffer.clone(),
4605                [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4606                cx,
4607            );
4608            mb.add_diff(diff.clone(), cx);
4609            mb
4610        });
4611        let subscription =
4612            rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
4613
4614        let lhs_excerpt_id =
4615            lhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4616        let rhs_excerpt_id =
4617            rhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4618
4619        let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
4620        let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
4621        let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
4622        let (mut _lhs_tab_map, lhs_tab_snapshot) =
4623            TabMap::new(lhs_fold_snapshot, 4.try_into().unwrap());
4624        let (_lhs_wrap_map, lhs_wrap_snapshot) =
4625            cx.update(|cx| WrapMap::new(lhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4626        let lhs_block_map = BlockMap::new(lhs_wrap_snapshot.clone(), 0, 0);
4627
4628        let rhs_buffer_snapshot = cx.update(|cx| rhs_multibuffer.read(cx).snapshot(cx));
4629        let (mut rhs_inlay_map, rhs_inlay_snapshot) = InlayMap::new(rhs_buffer_snapshot);
4630        let (mut rhs_fold_map, rhs_fold_snapshot) = FoldMap::new(rhs_inlay_snapshot);
4631        let (mut rhs_tab_map, rhs_tab_snapshot) =
4632            TabMap::new(rhs_fold_snapshot, 4.try_into().unwrap());
4633        let (_rhs_wrap_map, rhs_wrap_snapshot) =
4634            cx.update(|cx| WrapMap::new(rhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4635        let rhs_block_map = BlockMap::new(rhs_wrap_snapshot.clone(), 0, 0);
4636
4637        let rhs_entity_id = rhs_multibuffer.entity_id();
4638
4639        let companion = cx.new(|_| {
4640            let mut c = Companion::new(
4641                rhs_entity_id,
4642                convert_rhs_rows_to_lhs,
4643                convert_lhs_rows_to_rhs,
4644            );
4645            c.add_excerpt_mapping(lhs_excerpt_id, rhs_excerpt_id);
4646            c
4647        });
4648
4649        let rhs_edits = Patch::new(vec![text::Edit {
4650            old: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4651            new: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4652        }]);
4653        let lhs_edits = Patch::new(vec![text::Edit {
4654            old: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4655            new: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4656        }]);
4657
4658        let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4659            rhs_block_map.read(
4660                rhs_wrap_snapshot.clone(),
4661                rhs_edits.clone(),
4662                Some(CompanionView::new(
4663                    rhs_entity_id,
4664                    &lhs_wrap_snapshot,
4665                    &lhs_edits,
4666                    companion,
4667                )),
4668            )
4669        });
4670
4671        let lhs_entity_id = lhs_multibuffer.entity_id();
4672        let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4673            lhs_block_map.read(
4674                lhs_wrap_snapshot.clone(),
4675                lhs_edits.clone(),
4676                Some(CompanionView::new(
4677                    lhs_entity_id,
4678                    &rhs_wrap_snapshot,
4679                    &rhs_edits,
4680                    companion,
4681                )),
4682            )
4683        });
4684
4685        // LHS:
4686        //   aaa
4687        // - bbb
4688        // - ccc
4689        //   ddd
4690        //   ddd
4691        //   ddd
4692        //   <extra line>
4693        //   <extra line>
4694        //   <extra line>
4695        //   *eee
4696        //
4697        // RHS:
4698        //   aaa
4699        //   <extra line>
4700        //   <extra line>
4701        //   ddd
4702        //   ddd
4703        //   ddd
4704        // + XXX
4705        // + YYY
4706        // + ZZZ
4707        //   eee
4708
4709        assert_eq!(
4710            rhs_snapshot.snapshot.text(),
4711            "aaa\n\n\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4712            "RHS should have 2 spacer lines after 'aaa' to align with LHS's deleted lines"
4713        );
4714
4715        assert_eq!(
4716            lhs_snapshot.snapshot.text(),
4717            "aaa\nbbb\nccc\nddd\nddd\nddd\n\n\n\neee\n",
4718            "LHS should have 3 spacer lines in place of RHS's inserted lines"
4719        );
4720
4721        // LHS:
4722        //   aaa
4723        // - bbb
4724        // - ccc
4725        //   ddd
4726        //   ddd
4727        //   ddd
4728        //   <extra line>
4729        //   <extra line>
4730        //   <extra line>
4731        //   eee
4732        //
4733        // RHS:
4734        //   aaa
4735        //   <extra line>
4736        //   <extra line>
4737        //   ddd
4738        //   foo
4739        //   foo
4740        //   foo
4741        //   ddd
4742        //   ddd
4743        // + XXX
4744        // + YYY
4745        // + ZZZ
4746        //   eee
4747
4748        let rhs_buffer_snapshot = rhs_multibuffer.update(cx, |multibuffer, cx| {
4749            multibuffer.edit(
4750                [(Point::new(2, 0)..Point::new(2, 0), "foo\nfoo\nfoo\n")],
4751                None,
4752                cx,
4753            );
4754            multibuffer.snapshot(cx)
4755        });
4756
4757        let (rhs_inlay_snapshot, rhs_inlay_edits) =
4758            rhs_inlay_map.sync(rhs_buffer_snapshot, subscription.consume().into_inner());
4759        let (rhs_fold_snapshot, rhs_fold_edits) =
4760            rhs_fold_map.read(rhs_inlay_snapshot, rhs_inlay_edits);
4761        let (rhs_tab_snapshot, rhs_tab_edits) =
4762            rhs_tab_map.sync(rhs_fold_snapshot, rhs_fold_edits, 4.try_into().unwrap());
4763        let (rhs_wrap_snapshot, rhs_wrap_edits) = _rhs_wrap_map.update(cx, |wrap_map, cx| {
4764            wrap_map.sync(rhs_tab_snapshot, rhs_tab_edits, cx)
4765        });
4766
4767        let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4768            rhs_block_map.read(
4769                rhs_wrap_snapshot.clone(),
4770                rhs_wrap_edits.clone(),
4771                Some(CompanionView::new(
4772                    rhs_entity_id,
4773                    &lhs_wrap_snapshot,
4774                    &Default::default(),
4775                    companion,
4776                )),
4777            )
4778        });
4779
4780        let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4781            lhs_block_map.read(
4782                lhs_wrap_snapshot.clone(),
4783                Default::default(),
4784                Some(CompanionView::new(
4785                    lhs_entity_id,
4786                    &rhs_wrap_snapshot,
4787                    &rhs_wrap_edits,
4788                    companion,
4789                )),
4790            )
4791        });
4792
4793        assert_eq!(
4794            rhs_snapshot.snapshot.text(),
4795            "aaa\n\n\nddd\nfoo\nfoo\nfoo\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4796            "RHS should have the insertion"
4797        );
4798
4799        assert_eq!(
4800            lhs_snapshot.snapshot.text(),
4801            "aaa\nbbb\nccc\nddd\n\n\n\nddd\nddd\n\n\n\neee\n",
4802            "LHS should have 3 more spacer lines to balance the insertion"
4803        );
4804    }
4805
4806    fn init_test(cx: &mut gpui::App) {
4807        let settings = SettingsStore::test(cx);
4808        cx.set_global(settings);
4809        theme::init(theme::LoadThemes::JustBase, cx);
4810        assets::Assets.load_test_fonts(cx);
4811    }
4812
4813    impl Block {
4814        fn as_custom(&self) -> Option<&CustomBlock> {
4815            match self {
4816                Block::Custom(block) => Some(block),
4817                _ => None,
4818            }
4819        }
4820    }
4821
4822    impl BlockSnapshot {
4823        fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
4824            self.wrap_snapshot
4825                .to_point(self.to_wrap_point(point, bias), bias)
4826        }
4827    }
4828}