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 companion_inlay_point_cursor = companion_snapshot.inlay_point_cursor();
1256        let mut companion_fold_point_cursor = companion_snapshot.fold_point_cursor();
1257        let mut companion_tab_point_cursor = companion_snapshot.tab_point_cursor();
1258        let mut companion_wrap_point_cursor = companion_snapshot.wrap_point_cursor();
1259
1260        let mut our_wrapper = |our_point: Point, bias: Bias| {
1261            our_wrap_point_cursor
1262                .map(our_tab_point_cursor.map(
1263                    our_fold_point_cursor.map(our_inlay_point_cursor.map(our_point, bias), bias),
1264                ))
1265                .row()
1266        };
1267        let mut companion_wrapper = |their_point: Point, bias: Bias| {
1268            companion_wrap_point_cursor
1269                .map(
1270                    companion_tab_point_cursor.map(
1271                        companion_fold_point_cursor
1272                            .map(companion_inlay_point_cursor.map(their_point, bias), bias),
1273                    ),
1274                )
1275                .row()
1276        };
1277        fn determine_spacer(
1278            our_wrapper: &mut dyn FnMut(Point, Bias) -> WrapRow,
1279            companion_wrapper: &mut dyn FnMut(Point, Bias) -> WrapRow,
1280            our_point: Point,
1281            their_point: Point,
1282            delta: i32,
1283            bias: Bias,
1284        ) -> (i32, Option<(WrapRow, u32)>) {
1285            let our_wrap = our_wrapper(our_point, bias);
1286            let companion_wrap = companion_wrapper(their_point, bias);
1287            let new_delta = companion_wrap.0 as i32 - our_wrap.0 as i32;
1288
1289            let spacer = if new_delta > delta {
1290                let height = (new_delta - delta) as u32;
1291                Some((our_wrap, height))
1292            } else {
1293                None
1294            };
1295            (new_delta, spacer)
1296        }
1297
1298        let mut result = Vec::new();
1299
1300        for excerpt in patches {
1301            let mut source_points = (excerpt.edited_range.start.row..=excerpt.edited_range.end.row)
1302                .map(|row| MultiBufferPoint::new(row, 0))
1303                .chain(if excerpt.edited_range.end.column > 0 {
1304                    Some(excerpt.edited_range.end)
1305                } else {
1306                    None
1307                })
1308                .peekable();
1309            let last_source_point = if excerpt.edited_range.end.column > 0 {
1310                excerpt.edited_range.end
1311            } else {
1312                MultiBufferPoint::new(excerpt.edited_range.end.row, 0)
1313            };
1314
1315            let Some(first_point) = source_points.peek().copied() else {
1316                continue;
1317            };
1318            let edit_for_first_point = excerpt.patch.edit_for_old_position(first_point);
1319
1320            // Because we calculate spacers based on differences in wrap row
1321            // counts between the RHS and LHS for corresponding buffer points,
1322            // we need to calibrate our expectations based on the difference
1323            // in counts before the start of the edit. This difference in
1324            // counts should have been balanced already by spacers above this
1325            // edit, so we only need to insert spacers for when the difference
1326            // in counts diverges from that baseline value.
1327            let (our_baseline, their_baseline) = if edit_for_first_point.old.start < first_point {
1328                // Case 1: We are inside a hunk/group--take the start of the hunk/group on both sides as the baseline.
1329                (
1330                    edit_for_first_point.old.start,
1331                    edit_for_first_point.new.start,
1332                )
1333            } else if first_point.row > excerpt.source_excerpt_range.start.row {
1334                // Case 2: We are not inside a hunk/group--go back by one row to find the baseline.
1335                let prev_point = Point::new(first_point.row - 1, 0);
1336                let edit_for_prev_point = excerpt.patch.edit_for_old_position(prev_point);
1337                (prev_point, edit_for_prev_point.new.end)
1338            } else {
1339                // Case 3: We are at the start of the excerpt--no previous row to use as the baseline.
1340                (first_point, edit_for_first_point.new.start)
1341            };
1342            let our_baseline = our_wrapper(our_baseline, Bias::Left);
1343            let their_baseline = companion_wrapper(
1344                their_baseline.min(excerpt.target_excerpt_range.end),
1345                Bias::Left,
1346            );
1347
1348            let mut delta = their_baseline.0 as i32 - our_baseline.0 as i32;
1349
1350            // 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.
1351            if edit_for_first_point.old.start < first_point {
1352                let mut current_boundary = first_point;
1353                let current_range = edit_for_first_point.new;
1354                while let Some(next_point) = source_points.peek().cloned() {
1355                    let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point);
1356                    if edit_for_next_point.new.end > current_range.end {
1357                        break;
1358                    }
1359                    source_points.next();
1360                    current_boundary = next_point;
1361                }
1362
1363                let (new_delta, spacer) = determine_spacer(
1364                    &mut our_wrapper,
1365                    &mut companion_wrapper,
1366                    current_boundary,
1367                    current_range.end.min(excerpt.target_excerpt_range.end),
1368                    delta,
1369                    Bias::Left,
1370                );
1371
1372                delta = new_delta;
1373                if let Some((wrap_row, height)) = spacer {
1374                    result.push((
1375                        BlockPlacement::Above(wrap_row),
1376                        Block::Spacer {
1377                            id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1378                            height,
1379                            is_below: false,
1380                        },
1381                    ));
1382                }
1383            }
1384
1385            // Main loop: process one hunk/group at a time, possibly inserting spacers before and after.
1386            while let Some(source_point) = source_points.next() {
1387                let mut current_boundary = source_point;
1388                let current_range = excerpt.patch.edit_for_old_position(current_boundary).new;
1389
1390                // This can only occur at the end of an excerpt.
1391                if current_boundary.column > 0 {
1392                    debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
1393                    break;
1394                }
1395
1396                // Align the two sides at the start of this group.
1397                let (delta_at_start, mut spacer_at_start) = determine_spacer(
1398                    &mut our_wrapper,
1399                    &mut companion_wrapper,
1400                    current_boundary,
1401                    current_range.start.min(excerpt.target_excerpt_range.end),
1402                    delta,
1403                    Bias::Left,
1404                );
1405                delta = delta_at_start;
1406
1407                while let Some(next_point) = source_points.peek().copied() {
1408                    let edit_for_next_point = excerpt.patch.edit_for_old_position(next_point);
1409                    if edit_for_next_point.new.end > current_range.end {
1410                        break;
1411                    }
1412
1413                    if let Some((wrap_row, height)) = spacer_at_start.take() {
1414                        result.push((
1415                            BlockPlacement::Above(wrap_row),
1416                            Block::Spacer {
1417                                id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1418                                height,
1419                                is_below: false,
1420                            },
1421                        ));
1422                    }
1423
1424                    current_boundary = next_point;
1425                    source_points.next();
1426                }
1427
1428                // This can only occur at the end of an excerpt.
1429                if current_boundary.column > 0 {
1430                    debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
1431                    break;
1432                }
1433
1434                let edit_for_current_boundary =
1435                    excerpt.patch.edit_for_old_position(current_boundary);
1436
1437                let spacer_at_end = if current_boundary == edit_for_current_boundary.old.end {
1438                    let (delta_at_end, spacer_at_end) = determine_spacer(
1439                        &mut our_wrapper,
1440                        &mut companion_wrapper,
1441                        current_boundary,
1442                        current_range.end.min(excerpt.target_excerpt_range.end),
1443                        delta,
1444                        Bias::Left,
1445                    );
1446                    delta = delta_at_end;
1447                    spacer_at_end
1448                } else {
1449                    None
1450                };
1451
1452                if let Some((wrap_row, mut height)) = spacer_at_start {
1453                    if let Some((_, additional_height)) = spacer_at_end {
1454                        height += additional_height;
1455                    }
1456                    result.push((
1457                        BlockPlacement::Above(wrap_row),
1458                        Block::Spacer {
1459                            id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1460                            height,
1461                            is_below: false,
1462                        },
1463                    ));
1464                } else if let Some((wrap_row, height)) = spacer_at_end {
1465                    result.push((
1466                        BlockPlacement::Above(wrap_row),
1467                        Block::Spacer {
1468                            id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1469                            height,
1470                            is_below: false,
1471                        },
1472                    ));
1473                }
1474            }
1475
1476            if last_source_point == excerpt.source_excerpt_range.end {
1477                let (_new_delta, spacer) = determine_spacer(
1478                    &mut our_wrapper,
1479                    &mut companion_wrapper,
1480                    last_source_point,
1481                    excerpt.target_excerpt_range.end,
1482                    delta,
1483                    Bias::Right,
1484                );
1485                if let Some((wrap_row, height)) = spacer {
1486                    result.push((
1487                        BlockPlacement::Below(wrap_row),
1488                        Block::Spacer {
1489                            id: SpacerId(self.next_block_id.fetch_add(1, SeqCst)),
1490                            height,
1491                            is_below: true,
1492                        },
1493                    ));
1494                }
1495            }
1496        }
1497
1498        result
1499    }
1500
1501    #[ztracing::instrument(skip_all)]
1502    fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
1503        blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
1504            placement_a
1505                .start()
1506                .cmp(placement_b.start())
1507                .then_with(|| placement_b.end().cmp(placement_a.end()))
1508                .then_with(|| placement_a.tie_break().cmp(&placement_b.tie_break()))
1509                .then_with(|| {
1510                    if block_a.is_header() {
1511                        Ordering::Less
1512                    } else if block_b.is_header() {
1513                        Ordering::Greater
1514                    } else {
1515                        Ordering::Equal
1516                    }
1517                })
1518                .then_with(|| match (block_a, block_b) {
1519                    (
1520                        Block::ExcerptBoundary {
1521                            excerpt: excerpt_a, ..
1522                        }
1523                        | Block::BufferHeader {
1524                            excerpt: excerpt_a, ..
1525                        },
1526                        Block::ExcerptBoundary {
1527                            excerpt: excerpt_b, ..
1528                        }
1529                        | Block::BufferHeader {
1530                            excerpt: excerpt_b, ..
1531                        },
1532                    ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
1533                    (
1534                        Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
1535                        Block::Spacer { .. } | Block::Custom(_),
1536                    ) => Ordering::Less,
1537                    (
1538                        Block::Spacer { .. } | Block::Custom(_),
1539                        Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
1540                    ) => Ordering::Greater,
1541                    (Block::Spacer { .. }, Block::Custom(_)) => Ordering::Less,
1542                    (Block::Custom(_), Block::Spacer { .. }) => Ordering::Greater,
1543                    (Block::Custom(block_a), Block::Custom(block_b)) => block_a
1544                        .priority
1545                        .cmp(&block_b.priority)
1546                        .then_with(|| block_a.id.cmp(&block_b.id)),
1547                    _ => {
1548                        unreachable!("comparing blocks: {block_a:?} vs {block_b:?}")
1549                    }
1550                })
1551        });
1552        blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
1553            (BlockPlacement::Replace(range), BlockPlacement::Above(row))
1554            | (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
1555            (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
1556                if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
1557                    left.0 = BlockPlacement::Replace(
1558                        *range_a.start()..=*range_a.end().max(range_b.end()),
1559                    );
1560                    true
1561                } else {
1562                    false
1563                }
1564            }
1565            _ => false,
1566        });
1567    }
1568}
1569
1570#[ztracing::instrument(skip(tree, wrap_snapshot))]
1571fn push_isomorphic(tree: &mut SumTree<Transform>, rows: RowDelta, wrap_snapshot: &WrapSnapshot) {
1572    if rows == RowDelta(0) {
1573        return;
1574    }
1575
1576    let wrap_row_start = tree.summary().input_rows;
1577    let wrap_row_end = wrap_row_start + rows;
1578    let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
1579    let summary = TransformSummary {
1580        input_rows: WrapRow(rows.0),
1581        output_rows: BlockRow(rows.0),
1582        longest_row: BlockRow(wrap_summary.longest_row),
1583        longest_row_chars: wrap_summary.longest_row_chars,
1584    };
1585    let mut merged = false;
1586    tree.update_last(
1587        |last_transform| {
1588            if last_transform.block.is_none() {
1589                last_transform.summary.add_summary(&summary);
1590                merged = true;
1591            }
1592        },
1593        (),
1594    );
1595    if !merged {
1596        tree.push(
1597            Transform {
1598                summary,
1599                block: None,
1600            },
1601            (),
1602        );
1603    }
1604}
1605
1606impl BlockPoint {
1607    pub fn new(row: BlockRow, column: u32) -> Self {
1608        Self(Point::new(row.0, column))
1609    }
1610}
1611
1612impl Deref for BlockPoint {
1613    type Target = Point;
1614
1615    fn deref(&self) -> &Self::Target {
1616        &self.0
1617    }
1618}
1619
1620impl std::ops::DerefMut for BlockPoint {
1621    fn deref_mut(&mut self) -> &mut Self::Target {
1622        &mut self.0
1623    }
1624}
1625
1626impl Deref for BlockMapReader<'_> {
1627    type Target = BlockSnapshot;
1628
1629    fn deref(&self) -> &Self::Target {
1630        &self.snapshot
1631    }
1632}
1633
1634impl DerefMut for BlockMapReader<'_> {
1635    fn deref_mut(&mut self) -> &mut Self::Target {
1636        &mut self.snapshot
1637    }
1638}
1639
1640impl BlockMapReader<'_> {
1641    #[ztracing::instrument(skip_all)]
1642    pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
1643        let block = self.blocks.iter().find(|block| block.id == block_id)?;
1644        let buffer_row = block
1645            .start()
1646            .to_point(self.wrap_snapshot.buffer_snapshot())
1647            .row;
1648        let wrap_row = self
1649            .wrap_snapshot
1650            .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
1651            .row();
1652        let start_wrap_row = self
1653            .wrap_snapshot
1654            .prev_row_boundary(WrapPoint::new(wrap_row, 0));
1655        let end_wrap_row = self
1656            .wrap_snapshot
1657            .next_row_boundary(WrapPoint::new(wrap_row, 0))
1658            .unwrap_or(self.wrap_snapshot.max_point().row() + WrapRow(1));
1659
1660        let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
1661        cursor.seek(&start_wrap_row, Bias::Left);
1662        while let Some(transform) = cursor.item() {
1663            if cursor.start().0 > end_wrap_row {
1664                break;
1665            }
1666
1667            if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id())
1668                && id == block_id
1669            {
1670                return Some(cursor.start().1);
1671            }
1672            cursor.next();
1673        }
1674
1675        None
1676    }
1677}
1678
1679pub(crate) fn balancing_block(
1680    my_block: &BlockProperties<Anchor>,
1681    my_snapshot: &MultiBufferSnapshot,
1682    their_snapshot: &MultiBufferSnapshot,
1683    my_display_map_id: EntityId,
1684    companion: &Companion,
1685) -> Option<BlockProperties<Anchor>> {
1686    let my_anchor = my_block.placement.start();
1687    let my_point = my_anchor.to_point(&my_snapshot);
1688    let their_range = companion.convert_point_to_companion(
1689        my_display_map_id,
1690        my_snapshot,
1691        their_snapshot,
1692        my_point,
1693    );
1694    let their_anchor = their_snapshot.anchor_at(their_range.start, my_anchor.bias());
1695    let their_placement = match my_block.placement {
1696        BlockPlacement::Above(_) => BlockPlacement::Above(their_anchor),
1697        BlockPlacement::Below(_) => {
1698            if their_range.is_empty() {
1699                BlockPlacement::Above(their_anchor)
1700            } else {
1701                BlockPlacement::Below(their_anchor)
1702            }
1703        }
1704        // Not supported for balancing
1705        BlockPlacement::Near(_) | BlockPlacement::Replace(_) => return None,
1706    };
1707    Some(BlockProperties {
1708        placement: their_placement,
1709        height: my_block.height,
1710        style: BlockStyle::Sticky,
1711        render: Arc::new(move |cx| {
1712            crate::EditorElement::render_spacer_block(
1713                cx.block_id,
1714                cx.height,
1715                cx.line_height,
1716                cx.window,
1717                cx.app,
1718            )
1719        }),
1720        priority: my_block.priority,
1721    })
1722}
1723
1724impl BlockMapWriterCompanion<'_> {
1725    fn companion_view(&self) -> CompanionView<'_> {
1726        static EMPTY_PATCH: Patch<WrapRow> = Patch::empty();
1727        CompanionView {
1728            display_map_id: self.display_map_id,
1729            companion_wrap_snapshot: &self.companion_wrap_snapshot,
1730            companion_wrap_edits: &EMPTY_PATCH,
1731            companion: self.companion,
1732        }
1733    }
1734}
1735
1736impl BlockMapWriter<'_> {
1737    #[ztracing::instrument(skip_all)]
1738    pub fn insert(
1739        &mut self,
1740        blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1741    ) -> Vec<CustomBlockId> {
1742        let blocks = blocks.into_iter();
1743        let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
1744        let mut edits = Patch::default();
1745        let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
1746        let buffer = wrap_snapshot.buffer_snapshot();
1747
1748        let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1749        let mut companion_blocks = Vec::new();
1750        for block in blocks {
1751            if let BlockPlacement::Replace(_) = &block.placement {
1752                debug_assert!(block.height.unwrap() > 0);
1753            }
1754
1755            let id = self.block_map.insert_block_raw(block.clone(), &buffer);
1756            ids.push(id);
1757
1758            let start = block.placement.start().to_point(&buffer);
1759            let end = block.placement.end().to_point(&buffer);
1760            let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1761            let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1762
1763            let (start_row, end_row) = {
1764                previous_wrap_row_range.take_if(|range| {
1765                    !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1766                });
1767                let range = previous_wrap_row_range.get_or_insert_with(|| {
1768                    let start_row =
1769                        wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1770                    let end_row = wrap_snapshot
1771                        .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1772                        .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1773                    start_row..end_row
1774                });
1775                (range.start, range.end)
1776            };
1777
1778            // Insert a matching custom block in the companion (if any)
1779            if let Some(companion) = &mut self.companion
1780                && companion.inverse.is_some()
1781            {
1782                companion_blocks.extend(balancing_block(
1783                    &block,
1784                    &buffer,
1785                    companion.companion_wrap_snapshot.buffer(),
1786                    companion.display_map_id,
1787                    companion.companion,
1788                ));
1789            }
1790
1791            edits = edits.compose([Edit {
1792                old: start_row..end_row,
1793                new: start_row..end_row,
1794            }]);
1795        }
1796
1797        self.block_map.sync(
1798            &wrap_snapshot,
1799            edits,
1800            self.companion
1801                .as_ref()
1802                .map(BlockMapWriterCompanion::companion_view),
1803        );
1804
1805        if let Some(companion) = &mut self.companion
1806            && let Some(inverse) = &mut companion.inverse
1807        {
1808            let companion_ids = inverse.companion_writer.insert(companion_blocks);
1809            companion
1810                .companion
1811                .custom_block_to_balancing_block(companion.display_map_id)
1812                .borrow_mut()
1813                .extend(ids.iter().copied().zip(companion_ids));
1814        }
1815
1816        ids
1817    }
1818
1819    #[ztracing::instrument(skip_all)]
1820    pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
1821        let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
1822        let buffer = wrap_snapshot.buffer_snapshot();
1823        let mut edits = Patch::default();
1824        let mut last_block_buffer_row = None;
1825
1826        let mut companion_heights = HashMap::default();
1827        for block in &mut self.block_map.custom_blocks {
1828            if let Some(new_height) = heights.remove(&block.id) {
1829                if let BlockPlacement::Replace(_) = &block.placement {
1830                    debug_assert!(new_height > 0);
1831                }
1832
1833                if block.height != Some(new_height) {
1834                    let new_block = CustomBlock {
1835                        id: block.id,
1836                        placement: block.placement.clone(),
1837                        height: Some(new_height),
1838                        style: block.style,
1839                        render: block.render.clone(),
1840                        priority: block.priority,
1841                    };
1842                    let new_block = Arc::new(new_block);
1843                    *block = new_block.clone();
1844                    self.block_map
1845                        .custom_blocks_by_id
1846                        .insert(block.id, new_block);
1847
1848                    if let Some(companion) = &self.companion
1849                        && companion.inverse.is_some()
1850                        && let Some(companion_block_id) = companion
1851                            .companion
1852                            .custom_block_to_balancing_block(companion.display_map_id)
1853                            .borrow()
1854                            .get(&block.id)
1855                            .copied()
1856                    {
1857                        companion_heights.insert(companion_block_id, new_height);
1858                    }
1859
1860                    let start_row = block.placement.start().to_point(buffer).row;
1861                    let end_row = block.placement.end().to_point(buffer).row;
1862                    if last_block_buffer_row != Some(end_row) {
1863                        last_block_buffer_row = Some(end_row);
1864                        let start_wrap_row = wrap_snapshot
1865                            .make_wrap_point(Point::new(start_row, 0), Bias::Left)
1866                            .row();
1867                        let end_wrap_row = wrap_snapshot
1868                            .make_wrap_point(Point::new(end_row, 0), Bias::Left)
1869                            .row();
1870                        let start =
1871                            wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1872                        let end = wrap_snapshot
1873                            .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1874                            .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1875                        edits.push(Edit {
1876                            old: start..end,
1877                            new: start..end,
1878                        })
1879                    }
1880                }
1881            }
1882        }
1883
1884        self.block_map.sync(
1885            &wrap_snapshot,
1886            edits,
1887            self.companion
1888                .as_ref()
1889                .map(BlockMapWriterCompanion::companion_view),
1890        );
1891        if let Some(companion) = &mut self.companion
1892            && let Some(inverse) = &mut companion.inverse
1893        {
1894            inverse.companion_writer.resize(companion_heights);
1895        }
1896    }
1897
1898    #[ztracing::instrument(skip_all)]
1899    pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
1900        let wrap_snapshot = &*self.block_map.wrap_snapshot.borrow();
1901        let buffer = wrap_snapshot.buffer_snapshot();
1902        let mut edits = Patch::default();
1903        let mut last_block_buffer_row = None;
1904        let mut previous_wrap_row_range: Option<Range<WrapRow>> = None;
1905        let mut companion_block_ids: HashSet<CustomBlockId> = HashSet::default();
1906        self.block_map.custom_blocks.retain(|block| {
1907            if block_ids.contains(&block.id) {
1908                let start = block.placement.start().to_point(buffer);
1909                let end = block.placement.end().to_point(buffer);
1910                if last_block_buffer_row != Some(end.row) {
1911                    last_block_buffer_row = Some(end.row);
1912                    let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1913                    let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1914                    let (start_row, end_row) = {
1915                        previous_wrap_row_range.take_if(|range| {
1916                            !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1917                        });
1918                        let range = previous_wrap_row_range.get_or_insert_with(|| {
1919                            let start_row =
1920                                wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1921                            let end_row = wrap_snapshot
1922                                .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1923                                .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1));
1924                            start_row..end_row
1925                        });
1926                        (range.start, range.end)
1927                    };
1928
1929                    edits.push(Edit {
1930                        old: start_row..end_row,
1931                        new: start_row..end_row,
1932                    })
1933                }
1934                if let Some(companion) = &self.companion
1935                    && companion.inverse.is_some()
1936                {
1937                    companion_block_ids.extend(
1938                        companion
1939                            .companion
1940                            .custom_block_to_balancing_block(companion.display_map_id)
1941                            .borrow()
1942                            .get(&block.id)
1943                            .copied(),
1944                    );
1945                }
1946                false
1947            } else {
1948                true
1949            }
1950        });
1951        self.block_map
1952            .custom_blocks_by_id
1953            .retain(|id, _| !block_ids.contains(id));
1954
1955        self.block_map.sync(
1956            wrap_snapshot,
1957            edits,
1958            self.companion
1959                .as_ref()
1960                .map(BlockMapWriterCompanion::companion_view),
1961        );
1962        if let Some(companion) = &mut self.companion
1963            && let Some(inverse) = &mut companion.inverse
1964        {
1965            companion
1966                .companion
1967                .custom_block_to_balancing_block(companion.display_map_id)
1968                .borrow_mut()
1969                .retain(|id, _| !block_ids.contains(&id));
1970            inverse.companion_writer.remove(companion_block_ids);
1971        }
1972    }
1973
1974    #[ztracing::instrument(skip_all)]
1975    pub fn remove_intersecting_replace_blocks(
1976        &mut self,
1977        ranges: impl IntoIterator<Item = Range<MultiBufferOffset>>,
1978        inclusive: bool,
1979    ) {
1980        let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
1981        let mut blocks_to_remove = HashSet::default();
1982        for range in ranges {
1983            for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1984                if matches!(block.placement, BlockPlacement::Replace(_)) {
1985                    blocks_to_remove.insert(block.id);
1986                }
1987            }
1988        }
1989        drop(wrap_snapshot);
1990        self.remove(blocks_to_remove);
1991    }
1992
1993    pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1994        self.block_map
1995            .buffers_with_disabled_headers
1996            .insert(buffer_id);
1997    }
1998
1999    #[ztracing::instrument(skip_all)]
2000    pub fn fold_buffers(
2001        &mut self,
2002        buffer_ids: impl IntoIterator<Item = BufferId>,
2003        multi_buffer: &MultiBuffer,
2004        cx: &App,
2005    ) {
2006        self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
2007    }
2008
2009    #[ztracing::instrument(skip_all)]
2010    pub fn unfold_buffers(
2011        &mut self,
2012        buffer_ids: impl IntoIterator<Item = BufferId>,
2013        multi_buffer: &MultiBuffer,
2014        cx: &App,
2015    ) {
2016        self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
2017    }
2018
2019    #[ztracing::instrument(skip_all)]
2020    fn fold_or_unfold_buffers(
2021        &mut self,
2022        fold: bool,
2023        buffer_ids: impl IntoIterator<Item = BufferId>,
2024        multi_buffer: &MultiBuffer,
2025        cx: &App,
2026    ) {
2027        let mut ranges = Vec::new();
2028        let mut companion_buffer_ids = HashSet::default();
2029        for buffer_id in buffer_ids {
2030            if fold {
2031                self.block_map.folded_buffers.insert(buffer_id);
2032            } else {
2033                self.block_map.folded_buffers.remove(&buffer_id);
2034            }
2035            ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
2036            if let Some(companion) = &self.companion
2037                && companion.inverse.is_some()
2038            {
2039                companion_buffer_ids.extend(
2040                    companion
2041                        .companion
2042                        .buffer_to_companion_buffer(companion.display_map_id)
2043                        .get(&buffer_id)
2044                        .copied(),
2045                )
2046            }
2047        }
2048        ranges.sort_unstable_by_key(|range| range.start);
2049
2050        let mut edits = Patch::default();
2051        let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
2052        for range in ranges {
2053            let last_edit_row = cmp::min(
2054                wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + WrapRow(1),
2055                wrap_snapshot.max_point().row(),
2056            ) + WrapRow(1);
2057            let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
2058            edits.push(Edit {
2059                old: range.clone(),
2060                new: range,
2061            });
2062        }
2063
2064        self.block_map.sync(
2065            &wrap_snapshot,
2066            edits.clone(),
2067            self.companion
2068                .as_ref()
2069                .map(BlockMapWriterCompanion::companion_view),
2070        );
2071        if let Some(companion) = &mut self.companion
2072            && let Some(inverse) = &mut companion.inverse
2073        {
2074            inverse.companion_writer.fold_or_unfold_buffers(
2075                fold,
2076                companion_buffer_ids,
2077                inverse.companion_multibuffer,
2078                cx,
2079            );
2080        }
2081    }
2082
2083    #[ztracing::instrument(skip_all)]
2084    fn blocks_intersecting_buffer_range(
2085        &self,
2086        range: Range<MultiBufferOffset>,
2087        inclusive: bool,
2088    ) -> &[Arc<CustomBlock>] {
2089        if range.is_empty() && !inclusive {
2090            return &[];
2091        }
2092        let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
2093        let buffer = wrap_snapshot.buffer_snapshot();
2094
2095        let start_block_ix = match self.block_map.custom_blocks.binary_search_by(|block| {
2096            let block_end = block.end().to_offset(buffer);
2097            block_end.cmp(&range.start).then(Ordering::Greater)
2098        }) {
2099            Ok(ix) | Err(ix) => ix,
2100        };
2101        let end_block_ix =
2102            match self.block_map.custom_blocks[start_block_ix..].binary_search_by(|block| {
2103                let block_start = block.start().to_offset(buffer);
2104                block_start.cmp(&range.end).then(if inclusive {
2105                    Ordering::Less
2106                } else {
2107                    Ordering::Greater
2108                })
2109            }) {
2110                Ok(ix) | Err(ix) => ix,
2111            };
2112
2113        &self.block_map.custom_blocks[start_block_ix..][..end_block_ix]
2114    }
2115}
2116
2117impl BlockSnapshot {
2118    #[cfg(test)]
2119    #[ztracing::instrument(skip_all)]
2120    pub fn text(&self) -> String {
2121        self.chunks(
2122            BlockRow(0)..self.transforms.summary().output_rows,
2123            false,
2124            false,
2125            Highlights::default(),
2126        )
2127        .map(|chunk| chunk.text)
2128        .collect()
2129    }
2130
2131    #[ztracing::instrument(skip_all)]
2132    pub(crate) fn chunks<'a>(
2133        &'a self,
2134        rows: Range<BlockRow>,
2135        language_aware: bool,
2136        masked: bool,
2137        highlights: Highlights<'a>,
2138    ) -> BlockChunks<'a> {
2139        let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
2140
2141        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2142        cursor.seek(&rows.start, Bias::Right);
2143        let transform_output_start = cursor.start().0;
2144        let transform_input_start = cursor.start().1;
2145
2146        let mut input_start = transform_input_start;
2147        let mut input_end = transform_input_start;
2148        if let Some(transform) = cursor.item()
2149            && transform.block.is_none()
2150        {
2151            input_start += rows.start - transform_output_start;
2152            input_end += cmp::min(
2153                rows.end - transform_output_start,
2154                RowDelta(transform.summary.input_rows.0),
2155            );
2156        }
2157
2158        BlockChunks {
2159            input_chunks: self.wrap_snapshot.chunks(
2160                input_start..input_end,
2161                language_aware,
2162                highlights,
2163            ),
2164            input_chunk: Default::default(),
2165            transforms: cursor,
2166            output_row: rows.start,
2167            line_count_overflow: RowDelta(0),
2168            max_output_row,
2169            masked,
2170        }
2171    }
2172
2173    #[ztracing::instrument(skip_all)]
2174    pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
2175        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2176        cursor.seek(&start_row, Bias::Right);
2177        let Dimensions(output_start, input_start, _) = cursor.start();
2178        let overshoot = if cursor
2179            .item()
2180            .is_some_and(|transform| transform.block.is_none())
2181        {
2182            start_row - *output_start
2183        } else {
2184            RowDelta(0)
2185        };
2186        let input_start_row = *input_start + overshoot;
2187        BlockRows {
2188            transforms: cursor,
2189            input_rows: self.wrap_snapshot.row_infos(input_start_row),
2190            output_row: start_row,
2191            started: false,
2192        }
2193    }
2194
2195    #[ztracing::instrument(skip_all)]
2196    pub fn blocks_in_range(
2197        &self,
2198        rows: Range<BlockRow>,
2199    ) -> impl Iterator<Item = (BlockRow, &Block)> {
2200        let mut cursor = self.transforms.cursor::<BlockRow>(());
2201        cursor.seek(&rows.start, Bias::Left);
2202        while *cursor.start() < rows.start && cursor.end() <= rows.start {
2203            cursor.next();
2204        }
2205
2206        std::iter::from_fn(move || {
2207            while let Some(transform) = cursor.item() {
2208                let start_row = *cursor.start();
2209                if start_row > rows.end
2210                    || (start_row == rows.end
2211                        && transform
2212                            .block
2213                            .as_ref()
2214                            .is_some_and(|block| block.height() > 0))
2215                {
2216                    break;
2217                }
2218                if let Some(block) = &transform.block {
2219                    cursor.next();
2220                    return Some((start_row, block));
2221                } else {
2222                    cursor.next();
2223                }
2224            }
2225            None
2226        })
2227    }
2228
2229    #[ztracing::instrument(skip_all)]
2230    pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
2231        let top_row = position as u32;
2232        let mut cursor = self.transforms.cursor::<BlockRow>(());
2233        cursor.seek(&BlockRow(top_row), Bias::Right);
2234
2235        while let Some(transform) = cursor.item() {
2236            match &transform.block {
2237                Some(
2238                    Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
2239                ) => {
2240                    return Some(StickyHeaderExcerpt { excerpt });
2241                }
2242                Some(block) if block.is_buffer_header() => return None,
2243                _ => {
2244                    cursor.prev();
2245                    continue;
2246                }
2247            }
2248        }
2249
2250        None
2251    }
2252
2253    #[ztracing::instrument(skip_all)]
2254    pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
2255        let buffer = self.wrap_snapshot.buffer_snapshot();
2256        let wrap_point = match block_id {
2257            BlockId::Custom(custom_block_id) => {
2258                let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
2259                return Some(Block::Custom(custom_block.clone()));
2260            }
2261            BlockId::ExcerptBoundary(next_excerpt_id) => {
2262                let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
2263                self.wrap_snapshot
2264                    .make_wrap_point(excerpt_range.start, Bias::Left)
2265            }
2266            BlockId::FoldedBuffer(excerpt_id) => self
2267                .wrap_snapshot
2268                .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
2269            BlockId::Spacer(_) => return None,
2270        };
2271        let wrap_row = wrap_point.row();
2272
2273        let mut cursor = self.transforms.cursor::<WrapRow>(());
2274        cursor.seek(&wrap_row, Bias::Left);
2275
2276        while let Some(transform) = cursor.item() {
2277            if let Some(block) = transform.block.as_ref() {
2278                if block.id() == block_id {
2279                    return Some(block.clone());
2280                }
2281            } else if *cursor.start() > wrap_row {
2282                break;
2283            }
2284
2285            cursor.next();
2286        }
2287
2288        None
2289    }
2290
2291    #[ztracing::instrument(skip_all)]
2292    pub fn max_point(&self) -> BlockPoint {
2293        let row = self
2294            .transforms
2295            .summary()
2296            .output_rows
2297            .saturating_sub(RowDelta(1));
2298        BlockPoint::new(row, self.line_len(row))
2299    }
2300
2301    #[ztracing::instrument(skip_all)]
2302    pub fn longest_row(&self) -> BlockRow {
2303        self.transforms.summary().longest_row
2304    }
2305
2306    #[ztracing::instrument(skip_all)]
2307    pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
2308        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2309        cursor.seek(&range.start, Bias::Right);
2310
2311        let mut longest_row = range.start;
2312        let mut longest_row_chars = 0;
2313        if let Some(transform) = cursor.item() {
2314            if transform.block.is_none() {
2315                let &Dimensions(output_start, input_start, _) = cursor.start();
2316                let overshoot = range.start - output_start;
2317                let wrap_start_row = input_start + WrapRow(overshoot.0);
2318                let wrap_end_row = cmp::min(
2319                    input_start + WrapRow((range.end - output_start).0),
2320                    cursor.end().1,
2321                );
2322                let summary = self
2323                    .wrap_snapshot
2324                    .text_summary_for_range(wrap_start_row..wrap_end_row);
2325                longest_row = BlockRow(range.start.0 + summary.longest_row);
2326                longest_row_chars = summary.longest_row_chars;
2327            }
2328            cursor.next();
2329        }
2330
2331        let cursor_start_row = cursor.start().0;
2332        if range.end > cursor_start_row {
2333            let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
2334            if summary.longest_row_chars > longest_row_chars {
2335                longest_row = cursor_start_row + summary.longest_row;
2336                longest_row_chars = summary.longest_row_chars;
2337            }
2338
2339            if let Some(transform) = cursor.item()
2340                && transform.block.is_none()
2341            {
2342                let &Dimensions(output_start, input_start, _) = cursor.start();
2343                let overshoot = range.end - output_start;
2344                let wrap_start_row = input_start;
2345                let wrap_end_row = input_start + overshoot;
2346                let summary = self
2347                    .wrap_snapshot
2348                    .text_summary_for_range(wrap_start_row..wrap_end_row);
2349                if summary.longest_row_chars > longest_row_chars {
2350                    longest_row = output_start + RowDelta(summary.longest_row);
2351                }
2352            }
2353        }
2354
2355        longest_row
2356    }
2357
2358    #[ztracing::instrument(skip_all)]
2359    pub(super) fn line_len(&self, row: BlockRow) -> u32 {
2360        let (start, _, item) =
2361            self.transforms
2362                .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
2363        if let Some(transform) = item {
2364            let Dimensions(output_start, input_start, _) = start;
2365            let overshoot = row - output_start;
2366            if transform.block.is_some() {
2367                0
2368            } else {
2369                self.wrap_snapshot.line_len(input_start + overshoot)
2370            }
2371        } else if row == BlockRow(0) {
2372            0
2373        } else {
2374            panic!("row out of range");
2375        }
2376    }
2377
2378    #[ztracing::instrument(skip_all)]
2379    pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
2380        let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2381        item.is_some_and(|t| t.block.is_some())
2382    }
2383
2384    #[ztracing::instrument(skip_all)]
2385    pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
2386        let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2387        let Some(transform) = item else {
2388            return false;
2389        };
2390        matches!(transform.block, Some(Block::FoldedBuffer { .. }))
2391    }
2392
2393    #[ztracing::instrument(skip_all)]
2394    pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
2395        let wrap_point = self
2396            .wrap_snapshot
2397            .make_wrap_point(Point::new(row.0, 0), Bias::Left);
2398        let (_, _, item) = self
2399            .transforms
2400            .find::<WrapRow, _>((), &wrap_point.row(), Bias::Right);
2401        item.is_some_and(|transform| {
2402            transform
2403                .block
2404                .as_ref()
2405                .is_some_and(|block| block.is_replacement())
2406        })
2407    }
2408
2409    #[ztracing::instrument(skip_all)]
2410    pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
2411        let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2412        cursor.seek(&BlockRow(point.row), Bias::Right);
2413
2414        let max_input_row = self.transforms.summary().input_rows;
2415        let mut search_left = (bias == Bias::Left && cursor.start().1 > WrapRow(0))
2416            || cursor.end().1 == max_input_row;
2417        let mut reversed = false;
2418
2419        loop {
2420            if let Some(transform) = cursor.item() {
2421                let Dimensions(output_start_row, input_start_row, _) = cursor.start();
2422                let Dimensions(output_end_row, input_end_row, _) = cursor.end();
2423                let output_start = Point::new(output_start_row.0, 0);
2424                let input_start = Point::new(input_start_row.0, 0);
2425                let input_end = Point::new(input_end_row.0, 0);
2426
2427                match transform.block.as_ref() {
2428                    Some(block) => {
2429                        if block.is_replacement()
2430                            && (((bias == Bias::Left || search_left) && output_start <= point.0)
2431                                || (!search_left && output_start >= point.0))
2432                        {
2433                            return BlockPoint(output_start);
2434                        }
2435                    }
2436                    None => {
2437                        let input_point = if point.row >= output_end_row.0 {
2438                            let line_len = self.wrap_snapshot.line_len(input_end_row - RowDelta(1));
2439                            self.wrap_snapshot.clip_point(
2440                                WrapPoint::new(input_end_row - RowDelta(1), line_len),
2441                                bias,
2442                            )
2443                        } else {
2444                            let output_overshoot = point.0.saturating_sub(output_start);
2445                            self.wrap_snapshot
2446                                .clip_point(WrapPoint(input_start + output_overshoot), bias)
2447                        };
2448
2449                        if (input_start..input_end).contains(&input_point.0) {
2450                            let input_overshoot = input_point.0.saturating_sub(input_start);
2451                            return BlockPoint(output_start + input_overshoot);
2452                        }
2453                    }
2454                }
2455
2456                if search_left {
2457                    cursor.prev();
2458                } else {
2459                    cursor.next();
2460                }
2461            } else if reversed {
2462                return self.max_point();
2463            } else {
2464                reversed = true;
2465                search_left = !search_left;
2466                cursor.seek(&BlockRow(point.row), Bias::Right);
2467            }
2468        }
2469    }
2470
2471    #[ztracing::instrument(skip_all)]
2472    pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
2473        let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
2474            (),
2475            &wrap_point.row(),
2476            Bias::Right,
2477        );
2478        if let Some(transform) = item {
2479            if transform.block.is_some() {
2480                BlockPoint::new(start.1, 0)
2481            } else {
2482                let Dimensions(input_start_row, output_start_row, _) = start;
2483                let input_start = Point::new(input_start_row.0, 0);
2484                let output_start = Point::new(output_start_row.0, 0);
2485                let input_overshoot = wrap_point.0 - input_start;
2486                BlockPoint(output_start + input_overshoot)
2487            }
2488        } else {
2489            self.max_point()
2490        }
2491    }
2492
2493    #[ztracing::instrument(skip_all)]
2494    pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
2495        let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
2496            (),
2497            &BlockRow(block_point.row),
2498            Bias::Right,
2499        );
2500        if let Some(transform) = item {
2501            match transform.block.as_ref() {
2502                Some(block) => {
2503                    if block.place_below() {
2504                        let wrap_row = start.1 - RowDelta(1);
2505                        WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2506                    } else if block.place_above() {
2507                        WrapPoint::new(start.1, 0)
2508                    } else if bias == Bias::Left {
2509                        WrapPoint::new(start.1, 0)
2510                    } else {
2511                        let wrap_row = end.1 - RowDelta(1);
2512                        WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2513                    }
2514                }
2515                None => {
2516                    let overshoot = block_point.row() - start.0;
2517                    let wrap_row = start.1 + RowDelta(overshoot.0);
2518                    WrapPoint::new(wrap_row, block_point.column)
2519                }
2520            }
2521        } else {
2522            self.wrap_snapshot.max_point()
2523        }
2524    }
2525}
2526
2527impl BlockChunks<'_> {
2528    /// Go to the next transform
2529    #[ztracing::instrument(skip_all)]
2530    fn advance(&mut self) {
2531        self.input_chunk = Chunk::default();
2532        self.transforms.next();
2533        while let Some(transform) = self.transforms.item() {
2534            if transform
2535                .block
2536                .as_ref()
2537                .is_some_and(|block| block.height() == 0)
2538            {
2539                self.transforms.next();
2540            } else {
2541                break;
2542            }
2543        }
2544
2545        if self
2546            .transforms
2547            .item()
2548            .is_some_and(|transform| transform.block.is_none())
2549        {
2550            let start_input_row = self.transforms.start().1;
2551            let start_output_row = self.transforms.start().0;
2552            if start_output_row < self.max_output_row {
2553                let end_input_row = cmp::min(
2554                    self.transforms.end().1,
2555                    start_input_row + (self.max_output_row - start_output_row),
2556                );
2557                self.input_chunks.seek(start_input_row..end_input_row);
2558            }
2559        }
2560    }
2561}
2562
2563pub struct StickyHeaderExcerpt<'a> {
2564    pub excerpt: &'a ExcerptInfo,
2565}
2566
2567impl<'a> Iterator for BlockChunks<'a> {
2568    type Item = Chunk<'a>;
2569
2570    #[ztracing::instrument(skip_all)]
2571    fn next(&mut self) -> Option<Self::Item> {
2572        if self.output_row >= self.max_output_row {
2573            return None;
2574        }
2575
2576        if self.line_count_overflow > RowDelta(0) {
2577            let lines = self.line_count_overflow.0.min(u128::BITS);
2578            self.line_count_overflow.0 -= lines;
2579            self.output_row += RowDelta(lines);
2580            return Some(Chunk {
2581                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines as usize]) },
2582                chars: 1u128.unbounded_shl(lines).wrapping_sub(1),
2583                ..Default::default()
2584            });
2585        }
2586
2587        let transform = self.transforms.item()?;
2588        if transform.block.is_some() {
2589            let block_start = self.transforms.start().0;
2590            let mut block_end = self.transforms.end().0;
2591            self.advance();
2592            if self.transforms.item().is_none() {
2593                block_end -= RowDelta(1);
2594            }
2595
2596            let start_in_block = self.output_row - block_start;
2597            let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
2598            let line_count = end_in_block - start_in_block;
2599            let lines = RowDelta(line_count.0.min(u128::BITS));
2600            self.line_count_overflow = line_count - lines;
2601            self.output_row += lines;
2602
2603            return Some(Chunk {
2604                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines.0 as usize]) },
2605                chars: 1u128.unbounded_shl(lines.0).wrapping_sub(1),
2606                ..Default::default()
2607            });
2608        }
2609
2610        if self.input_chunk.text.is_empty() {
2611            if let Some(input_chunk) = self.input_chunks.next() {
2612                self.input_chunk = input_chunk;
2613            } else {
2614                if self.output_row < self.max_output_row {
2615                    self.output_row.0 += 1;
2616                    self.advance();
2617                    if self.transforms.item().is_some() {
2618                        return Some(Chunk {
2619                            text: "\n",
2620                            chars: 1,
2621                            ..Default::default()
2622                        });
2623                    }
2624                }
2625                return None;
2626            }
2627        }
2628
2629        let transform_end = self.transforms.end().0;
2630        let (prefix_rows, prefix_bytes) =
2631            offset_for_row(self.input_chunk.text, transform_end - self.output_row);
2632        self.output_row += prefix_rows;
2633
2634        let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
2635        self.input_chunk.text = suffix;
2636        self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
2637        self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
2638        self.input_chunk.newlines >>= prefix_bytes.saturating_sub(1);
2639
2640        let mut tabs = self.input_chunk.tabs;
2641        let mut chars = self.input_chunk.chars;
2642        let mut newlines = self.input_chunk.newlines;
2643
2644        if self.masked {
2645            // Not great for multibyte text because to keep cursor math correct we
2646            // need to have the same number of chars in the input as output.
2647            let chars_count = prefix.chars().count();
2648            let bullet_len = chars_count;
2649            prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
2650            chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
2651            tabs = 0;
2652            newlines = 0;
2653        }
2654
2655        let chunk = Chunk {
2656            text: prefix,
2657            tabs,
2658            chars,
2659            newlines,
2660            ..self.input_chunk.clone()
2661        };
2662
2663        if self.output_row == transform_end {
2664            self.advance();
2665        }
2666
2667        Some(chunk)
2668    }
2669}
2670
2671impl Iterator for BlockRows<'_> {
2672    type Item = RowInfo;
2673
2674    #[ztracing::instrument(skip_all)]
2675    fn next(&mut self) -> Option<Self::Item> {
2676        if self.started {
2677            self.output_row.0 += 1;
2678        } else {
2679            self.started = true;
2680        }
2681
2682        if self.output_row >= self.transforms.end().0 {
2683            self.transforms.next();
2684            while let Some(transform) = self.transforms.item() {
2685                if transform
2686                    .block
2687                    .as_ref()
2688                    .is_some_and(|block| block.height() == 0)
2689                {
2690                    self.transforms.next();
2691                } else {
2692                    break;
2693                }
2694            }
2695
2696            let transform = self.transforms.item()?;
2697            if transform
2698                .block
2699                .as_ref()
2700                .is_none_or(|block| block.is_replacement())
2701            {
2702                self.input_rows.seek(self.transforms.start().1);
2703            }
2704        }
2705
2706        let transform = self.transforms.item()?;
2707        if transform.block.as_ref().is_none_or(|block| {
2708            block.is_replacement()
2709                && self.transforms.start().0 == self.output_row
2710                && matches!(block, Block::FoldedBuffer { .. }).not()
2711        }) {
2712            self.input_rows.next()
2713        } else {
2714            Some(RowInfo::default())
2715        }
2716    }
2717}
2718
2719impl sum_tree::Item for Transform {
2720    type Summary = TransformSummary;
2721
2722    fn summary(&self, _cx: ()) -> Self::Summary {
2723        self.summary.clone()
2724    }
2725}
2726
2727impl sum_tree::ContextLessSummary for TransformSummary {
2728    fn zero() -> Self {
2729        Default::default()
2730    }
2731
2732    fn add_summary(&mut self, summary: &Self) {
2733        if summary.longest_row_chars > self.longest_row_chars {
2734            self.longest_row = self.output_rows + summary.longest_row;
2735            self.longest_row_chars = summary.longest_row_chars;
2736        }
2737        self.input_rows += summary.input_rows;
2738        self.output_rows += summary.output_rows;
2739    }
2740}
2741
2742impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
2743    fn zero(_cx: ()) -> Self {
2744        Default::default()
2745    }
2746
2747    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2748        *self += summary.input_rows;
2749    }
2750}
2751
2752impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
2753    fn zero(_cx: ()) -> Self {
2754        Default::default()
2755    }
2756
2757    fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2758        *self += summary.output_rows;
2759    }
2760}
2761
2762impl Deref for BlockContext<'_, '_> {
2763    type Target = App;
2764
2765    fn deref(&self) -> &Self::Target {
2766        self.app
2767    }
2768}
2769
2770impl DerefMut for BlockContext<'_, '_> {
2771    fn deref_mut(&mut self) -> &mut Self::Target {
2772        self.app
2773    }
2774}
2775
2776impl CustomBlock {
2777    #[ztracing::instrument(skip_all)]
2778    pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
2779        self.render.lock()(cx)
2780    }
2781
2782    #[ztracing::instrument(skip_all)]
2783    pub fn start(&self) -> Anchor {
2784        *self.placement.start()
2785    }
2786
2787    #[ztracing::instrument(skip_all)]
2788    pub fn end(&self) -> Anchor {
2789        *self.placement.end()
2790    }
2791
2792    pub fn style(&self) -> BlockStyle {
2793        self.style
2794    }
2795
2796    pub fn properties(&self) -> BlockProperties<Anchor> {
2797        BlockProperties {
2798            placement: self.placement.clone(),
2799            height: self.height,
2800            style: self.style,
2801            render: Arc::new(|_| {
2802                // Not used
2803                gpui::Empty.into_any_element()
2804            }),
2805            priority: self.priority,
2806        }
2807    }
2808}
2809
2810impl Debug for CustomBlock {
2811    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2812        f.debug_struct("Block")
2813            .field("id", &self.id)
2814            .field("placement", &self.placement)
2815            .field("height", &self.height)
2816            .field("style", &self.style)
2817            .field("priority", &self.priority)
2818            .finish_non_exhaustive()
2819    }
2820}
2821
2822// Count the number of bytes prior to a target point. If the string doesn't contain the target
2823// point, return its total extent. Otherwise return the target point itself.
2824fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) {
2825    let mut row = 0;
2826    let mut offset = 0;
2827    for (ix, line) in s.split('\n').enumerate() {
2828        if ix > 0 {
2829            row += 1;
2830            offset += 1;
2831        }
2832        if row >= target.0 {
2833            break;
2834        }
2835        offset += line.len();
2836    }
2837    (RowDelta(row), offset)
2838}
2839
2840#[cfg(test)]
2841mod tests {
2842    use super::*;
2843    use crate::{
2844        display_map::{
2845            Companion, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
2846        },
2847        split::{convert_lhs_rows_to_rhs, convert_rhs_rows_to_lhs},
2848        test::test_font,
2849    };
2850    use buffer_diff::BufferDiff;
2851    use gpui::{App, AppContext as _, Element, div, font, px};
2852    use itertools::Itertools;
2853    use language::{Buffer, Capability};
2854    use multi_buffer::{ExcerptRange, MultiBuffer};
2855    use rand::prelude::*;
2856    use settings::SettingsStore;
2857    use std::env;
2858    use util::RandomCharIter;
2859
2860    #[gpui::test]
2861    fn test_offset_for_row() {
2862        assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0));
2863        assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0));
2864        assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0));
2865        assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4));
2866        assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0));
2867        assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1));
2868        assert_eq!(
2869            offset_for_row("abc\ndef\nghi", RowDelta(0)),
2870            (RowDelta(0), 0)
2871        );
2872        assert_eq!(
2873            offset_for_row("abc\ndef\nghi", RowDelta(1)),
2874            (RowDelta(1), 4)
2875        );
2876        assert_eq!(
2877            offset_for_row("abc\ndef\nghi", RowDelta(2)),
2878            (RowDelta(2), 8)
2879        );
2880        assert_eq!(
2881            offset_for_row("abc\ndef\nghi", RowDelta(3)),
2882            (RowDelta(2), 11)
2883        );
2884    }
2885
2886    #[gpui::test]
2887    fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2888        cx.update(init_test);
2889
2890        let text = "aaa\nbbb\nccc\nddd";
2891
2892        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2893        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2894        let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2895        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2896        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2897        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2898        let (wrap_map, wraps_snapshot) =
2899            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2900        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2901
2902        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
2903        let block_ids = writer.insert(vec![
2904            BlockProperties {
2905                style: BlockStyle::Fixed,
2906                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2907                height: Some(1),
2908                render: Arc::new(|_| div().into_any()),
2909                priority: 0,
2910            },
2911            BlockProperties {
2912                style: BlockStyle::Fixed,
2913                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2914                height: Some(2),
2915                render: Arc::new(|_| div().into_any()),
2916                priority: 0,
2917            },
2918            BlockProperties {
2919                style: BlockStyle::Fixed,
2920                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2921                height: Some(3),
2922                render: Arc::new(|_| div().into_any()),
2923                priority: 0,
2924            },
2925        ]);
2926
2927        let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2928        assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2929
2930        let blocks = snapshot
2931            .blocks_in_range(BlockRow(0)..BlockRow(8))
2932            .map(|(start_row, block)| {
2933                let block = block.as_custom().unwrap();
2934                (start_row.0..start_row.0 + block.height.unwrap(), block.id)
2935            })
2936            .collect::<Vec<_>>();
2937
2938        // When multiple blocks are on the same line, the newer blocks appear first.
2939        assert_eq!(
2940            blocks,
2941            &[
2942                (1..2, block_ids[0]),
2943                (2..4, block_ids[1]),
2944                (7..10, block_ids[2]),
2945            ]
2946        );
2947
2948        assert_eq!(
2949            snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)),
2950            BlockPoint::new(BlockRow(0), 3)
2951        );
2952        assert_eq!(
2953            snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)),
2954            BlockPoint::new(BlockRow(4), 0)
2955        );
2956        assert_eq!(
2957            snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)),
2958            BlockPoint::new(BlockRow(6), 3)
2959        );
2960
2961        assert_eq!(
2962            snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left),
2963            WrapPoint::new(WrapRow(0), 3)
2964        );
2965        assert_eq!(
2966            snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2967            WrapPoint::new(WrapRow(1), 0)
2968        );
2969        assert_eq!(
2970            snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left),
2971            WrapPoint::new(WrapRow(1), 0)
2972        );
2973        assert_eq!(
2974            snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2975            WrapPoint::new(WrapRow(3), 3)
2976        );
2977
2978        assert_eq!(
2979            snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2980            BlockPoint::new(BlockRow(0), 3)
2981        );
2982        assert_eq!(
2983            snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right),
2984            BlockPoint::new(BlockRow(4), 0)
2985        );
2986        assert_eq!(
2987            snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left),
2988            BlockPoint::new(BlockRow(0), 3)
2989        );
2990        assert_eq!(
2991            snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right),
2992            BlockPoint::new(BlockRow(4), 0)
2993        );
2994        assert_eq!(
2995            snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left),
2996            BlockPoint::new(BlockRow(4), 0)
2997        );
2998        assert_eq!(
2999            snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right),
3000            BlockPoint::new(BlockRow(4), 0)
3001        );
3002        assert_eq!(
3003            snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left),
3004            BlockPoint::new(BlockRow(6), 3)
3005        );
3006        assert_eq!(
3007            snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right),
3008            BlockPoint::new(BlockRow(6), 3)
3009        );
3010        assert_eq!(
3011            snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
3012            BlockPoint::new(BlockRow(6), 3)
3013        );
3014        assert_eq!(
3015            snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right),
3016            BlockPoint::new(BlockRow(6), 3)
3017        );
3018
3019        assert_eq!(
3020            snapshot
3021                .row_infos(BlockRow(0))
3022                .map(|row_info| row_info.buffer_row)
3023                .collect::<Vec<_>>(),
3024            &[
3025                Some(0),
3026                None,
3027                None,
3028                None,
3029                Some(1),
3030                Some(2),
3031                Some(3),
3032                None,
3033                None,
3034                None
3035            ]
3036        );
3037
3038        // Insert a line break, separating two block decorations into separate lines.
3039        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3040            buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
3041            buffer.snapshot(cx)
3042        });
3043
3044        let (inlay_snapshot, inlay_edits) =
3045            inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
3046        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3047        let (tab_snapshot, tab_edits) =
3048            tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
3049        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3050            wrap_map.sync(tab_snapshot, tab_edits, cx)
3051        });
3052        let snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3053        assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
3054    }
3055
3056    #[gpui::test]
3057    fn test_multibuffer_headers_and_footers(cx: &mut App) {
3058        init_test(cx);
3059
3060        let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
3061        let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
3062        let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
3063
3064        let mut excerpt_ids = Vec::new();
3065        let multi_buffer = cx.new(|cx| {
3066            let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
3067            excerpt_ids.extend(multi_buffer.push_excerpts(
3068                buffer1.clone(),
3069                [ExcerptRange::new(0..buffer1.read(cx).len())],
3070                cx,
3071            ));
3072            excerpt_ids.extend(multi_buffer.push_excerpts(
3073                buffer2.clone(),
3074                [ExcerptRange::new(0..buffer2.read(cx).len())],
3075                cx,
3076            ));
3077            excerpt_ids.extend(multi_buffer.push_excerpts(
3078                buffer3.clone(),
3079                [ExcerptRange::new(0..buffer3.read(cx).len())],
3080                cx,
3081            ));
3082
3083            multi_buffer
3084        });
3085
3086        let font = test_font();
3087        let font_size = px(14.);
3088        let font_id = cx.text_system().resolve_font(&font);
3089        let mut wrap_width = px(0.);
3090        for c in "Buff".chars() {
3091            wrap_width += cx
3092                .text_system()
3093                .advance(font_id, font_size, c)
3094                .unwrap()
3095                .width;
3096        }
3097
3098        let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
3099        let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
3100        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3101        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3102        let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
3103
3104        let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3105        let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3106
3107        // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
3108        assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
3109
3110        let blocks: Vec<_> = snapshot
3111            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3112            .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
3113            .collect();
3114        assert_eq!(
3115            blocks,
3116            vec![
3117                (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
3118                (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
3119                (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
3120            ]
3121        );
3122    }
3123
3124    #[gpui::test]
3125    fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
3126        cx.update(init_test);
3127
3128        let text = "aaa\nbbb\nccc\nddd";
3129
3130        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3131        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3132        let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
3133        let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3134        let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3135        let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
3136        let (_wrap_map, wraps_snapshot) =
3137            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3138        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3139
3140        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3141        let block_ids = writer.insert(vec![
3142            BlockProperties {
3143                style: BlockStyle::Fixed,
3144                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3145                height: Some(1),
3146                render: Arc::new(|_| div().into_any()),
3147                priority: 0,
3148            },
3149            BlockProperties {
3150                style: BlockStyle::Fixed,
3151                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
3152                height: Some(2),
3153                render: Arc::new(|_| div().into_any()),
3154                priority: 0,
3155            },
3156            BlockProperties {
3157                style: BlockStyle::Fixed,
3158                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
3159                height: Some(3),
3160                render: Arc::new(|_| div().into_any()),
3161                priority: 0,
3162            },
3163        ]);
3164
3165        {
3166            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3167            assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3168
3169            let mut block_map_writer =
3170                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3171
3172            let mut new_heights = HashMap::default();
3173            new_heights.insert(block_ids[0], 2);
3174            block_map_writer.resize(new_heights);
3175            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3176            assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3177        }
3178
3179        {
3180            let mut block_map_writer =
3181                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3182
3183            let mut new_heights = HashMap::default();
3184            new_heights.insert(block_ids[0], 1);
3185            block_map_writer.resize(new_heights);
3186
3187            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3188            assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3189        }
3190
3191        {
3192            let mut block_map_writer =
3193                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3194
3195            let mut new_heights = HashMap::default();
3196            new_heights.insert(block_ids[0], 0);
3197            block_map_writer.resize(new_heights);
3198
3199            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3200            assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
3201        }
3202
3203        {
3204            let mut block_map_writer =
3205                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3206
3207            let mut new_heights = HashMap::default();
3208            new_heights.insert(block_ids[0], 3);
3209            block_map_writer.resize(new_heights);
3210
3211            let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3212            assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3213        }
3214
3215        {
3216            let mut block_map_writer =
3217                block_map.write(wraps_snapshot.clone(), Default::default(), None);
3218
3219            let mut new_heights = HashMap::default();
3220            new_heights.insert(block_ids[0], 3);
3221            block_map_writer.resize(new_heights);
3222
3223            let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3224            // Same height as before, should remain the same
3225            assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3226        }
3227    }
3228
3229    #[gpui::test]
3230    fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
3231        cx.update(init_test);
3232
3233        let text = "one two three\nfour five six\nseven eight";
3234
3235        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3236        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3237        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3238        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3239        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3240        let (_, wraps_snapshot) = cx.update(|cx| {
3241            WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
3242        });
3243        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3244
3245        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3246        writer.insert(vec![
3247            BlockProperties {
3248                style: BlockStyle::Fixed,
3249                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
3250                render: Arc::new(|_| div().into_any()),
3251                height: Some(1),
3252                priority: 0,
3253            },
3254            BlockProperties {
3255                style: BlockStyle::Fixed,
3256                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
3257                render: Arc::new(|_| div().into_any()),
3258                height: Some(1),
3259                priority: 0,
3260            },
3261        ]);
3262
3263        // Blocks with an 'above' disposition go above their corresponding buffer line.
3264        // Blocks with a 'below' disposition go below their corresponding buffer line.
3265        let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3266        assert_eq!(
3267            snapshot.text(),
3268            "one two \nthree\n\nfour five \nsix\n\nseven \neight"
3269        );
3270    }
3271
3272    #[gpui::test]
3273    fn test_replace_lines(cx: &mut gpui::TestAppContext) {
3274        cx.update(init_test);
3275
3276        let text = "line1\nline2\nline3\nline4\nline5";
3277
3278        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3279        let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
3280        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3281        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3282        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3283        let tab_size = 1.try_into().unwrap();
3284        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
3285        let (wrap_map, wraps_snapshot) =
3286            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3287        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3288
3289        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3290        let replace_block_id = writer.insert(vec![BlockProperties {
3291            style: BlockStyle::Fixed,
3292            placement: BlockPlacement::Replace(
3293                buffer_snapshot.anchor_after(Point::new(1, 3))
3294                    ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
3295            ),
3296            height: Some(4),
3297            render: Arc::new(|_| div().into_any()),
3298            priority: 0,
3299        }])[0];
3300
3301        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3302        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3303
3304        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3305            buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
3306            buffer.snapshot(cx)
3307        });
3308        let (inlay_snapshot, inlay_edits) =
3309            inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
3310        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3311        let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3312        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3313            wrap_map.sync(tab_snapshot, tab_edits, cx)
3314        });
3315        let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3316        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3317
3318        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3319            buffer.edit(
3320                [(
3321                    Point::new(1, 5)..Point::new(1, 5),
3322                    "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
3323                )],
3324                None,
3325                cx,
3326            );
3327            buffer.snapshot(cx)
3328        });
3329        let (inlay_snapshot, inlay_edits) = inlay_map.sync(
3330            buffer_snapshot.clone(),
3331            buffer_subscription.consume().into_inner(),
3332        );
3333        let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3334        let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3335        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3336            wrap_map.sync(tab_snapshot, tab_edits, cx)
3337        });
3338        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3339        assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3340
3341        // Blocks inserted right above the start or right below the end of the replaced region are hidden.
3342        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3343        writer.insert(vec![
3344            BlockProperties {
3345                style: BlockStyle::Fixed,
3346                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
3347                height: Some(1),
3348                render: Arc::new(|_| div().into_any()),
3349                priority: 0,
3350            },
3351            BlockProperties {
3352                style: BlockStyle::Fixed,
3353                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
3354                height: Some(1),
3355                render: Arc::new(|_| div().into_any()),
3356                priority: 0,
3357            },
3358            BlockProperties {
3359                style: BlockStyle::Fixed,
3360                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
3361                height: Some(1),
3362                render: Arc::new(|_| div().into_any()),
3363                priority: 0,
3364            },
3365        ]);
3366        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3367        assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3368
3369        // Ensure blocks inserted *inside* replaced region are hidden.
3370        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3371        writer.insert(vec![
3372            BlockProperties {
3373                style: BlockStyle::Fixed,
3374                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
3375                height: Some(1),
3376                render: Arc::new(|_| div().into_any()),
3377                priority: 0,
3378            },
3379            BlockProperties {
3380                style: BlockStyle::Fixed,
3381                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
3382                height: Some(1),
3383                render: Arc::new(|_| div().into_any()),
3384                priority: 0,
3385            },
3386            BlockProperties {
3387                style: BlockStyle::Fixed,
3388                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
3389                height: Some(1),
3390                render: Arc::new(|_| div().into_any()),
3391                priority: 0,
3392            },
3393        ]);
3394        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3395        assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3396
3397        // Removing the replace block shows all the hidden blocks again.
3398        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3399        writer.remove(HashSet::from_iter([replace_block_id]));
3400        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3401        assert_eq!(
3402            blocks_snapshot.text(),
3403            "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
3404        );
3405    }
3406
3407    #[gpui::test]
3408    fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
3409        cx.update(init_test);
3410
3411        let text = "111\n222\n333\n444\n555\n666";
3412
3413        let buffer = cx.update(|cx| {
3414            MultiBuffer::build_multi(
3415                [
3416                    (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
3417                    (
3418                        text,
3419                        vec![
3420                            Point::new(1, 0)..Point::new(1, 3),
3421                            Point::new(2, 0)..Point::new(2, 3),
3422                            Point::new(3, 0)..Point::new(3, 3),
3423                        ],
3424                    ),
3425                    (
3426                        text,
3427                        vec![
3428                            Point::new(4, 0)..Point::new(4, 3),
3429                            Point::new(5, 0)..Point::new(5, 3),
3430                        ],
3431                    ),
3432                ],
3433                cx,
3434            )
3435        });
3436        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3437        let buffer_ids = buffer_snapshot
3438            .excerpts()
3439            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3440            .dedup()
3441            .collect::<Vec<_>>();
3442        assert_eq!(buffer_ids.len(), 3);
3443        let buffer_id_1 = buffer_ids[0];
3444        let buffer_id_2 = buffer_ids[1];
3445        let buffer_id_3 = buffer_ids[2];
3446
3447        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3448        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3449        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3450        let (_, wrap_snapshot) =
3451            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3452        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3453        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3454
3455        assert_eq!(
3456            blocks_snapshot.text(),
3457            "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
3458        );
3459        assert_eq!(
3460            blocks_snapshot
3461                .row_infos(BlockRow(0))
3462                .map(|i| i.buffer_row)
3463                .collect::<Vec<_>>(),
3464            vec![
3465                None,
3466                None,
3467                Some(0),
3468                None,
3469                None,
3470                Some(1),
3471                None,
3472                Some(2),
3473                None,
3474                Some(3),
3475                None,
3476                None,
3477                Some(4),
3478                None,
3479                Some(5),
3480            ]
3481        );
3482
3483        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3484        let excerpt_blocks_2 = writer.insert(vec![
3485            BlockProperties {
3486                style: BlockStyle::Fixed,
3487                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3488                height: Some(1),
3489                render: Arc::new(|_| div().into_any()),
3490                priority: 0,
3491            },
3492            BlockProperties {
3493                style: BlockStyle::Fixed,
3494                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
3495                height: Some(1),
3496                render: Arc::new(|_| div().into_any()),
3497                priority: 0,
3498            },
3499            BlockProperties {
3500                style: BlockStyle::Fixed,
3501                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
3502                height: Some(1),
3503                render: Arc::new(|_| div().into_any()),
3504                priority: 0,
3505            },
3506        ]);
3507        let excerpt_blocks_3 = writer.insert(vec![
3508            BlockProperties {
3509                style: BlockStyle::Fixed,
3510                placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
3511                height: Some(1),
3512                render: Arc::new(|_| div().into_any()),
3513                priority: 0,
3514            },
3515            BlockProperties {
3516                style: BlockStyle::Fixed,
3517                placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
3518                height: Some(1),
3519                render: Arc::new(|_| div().into_any()),
3520                priority: 0,
3521            },
3522        ]);
3523
3524        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3525        assert_eq!(
3526            blocks_snapshot.text(),
3527            "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3528        );
3529        assert_eq!(
3530            blocks_snapshot
3531                .row_infos(BlockRow(0))
3532                .map(|i| i.buffer_row)
3533                .collect::<Vec<_>>(),
3534            vec![
3535                None,
3536                None,
3537                Some(0),
3538                None,
3539                None,
3540                None,
3541                Some(1),
3542                None,
3543                None,
3544                Some(2),
3545                None,
3546                Some(3),
3547                None,
3548                None,
3549                None,
3550                None,
3551                Some(4),
3552                None,
3553                Some(5),
3554                None,
3555            ]
3556        );
3557
3558        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3559        buffer.read_with(cx, |buffer, cx| {
3560            writer.fold_buffers([buffer_id_1], buffer, cx);
3561        });
3562        let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
3563            style: BlockStyle::Fixed,
3564            placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
3565            height: Some(1),
3566            render: Arc::new(|_| div().into_any()),
3567            priority: 0,
3568        }]);
3569        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3570        let blocks = blocks_snapshot
3571            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3572            .collect::<Vec<_>>();
3573        for (_, block) in &blocks {
3574            if let BlockId::Custom(custom_block_id) = block.id() {
3575                assert!(
3576                    !excerpt_blocks_1.contains(&custom_block_id),
3577                    "Should have no blocks from the folded buffer"
3578                );
3579                assert!(
3580                    excerpt_blocks_2.contains(&custom_block_id)
3581                        || excerpt_blocks_3.contains(&custom_block_id),
3582                    "Should have only blocks from unfolded buffers"
3583                );
3584            }
3585        }
3586        assert_eq!(
3587            1,
3588            blocks
3589                .iter()
3590                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3591                .count(),
3592            "Should have one folded block, producing a header of the second buffer"
3593        );
3594        assert_eq!(
3595            blocks_snapshot.text(),
3596            "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3597        );
3598        assert_eq!(
3599            blocks_snapshot
3600                .row_infos(BlockRow(0))
3601                .map(|i| i.buffer_row)
3602                .collect::<Vec<_>>(),
3603            vec![
3604                None,
3605                None,
3606                None,
3607                None,
3608                None,
3609                Some(1),
3610                None,
3611                None,
3612                Some(2),
3613                None,
3614                Some(3),
3615                None,
3616                None,
3617                None,
3618                None,
3619                Some(4),
3620                None,
3621                Some(5),
3622                None,
3623            ]
3624        );
3625
3626        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3627        buffer.read_with(cx, |buffer, cx| {
3628            writer.fold_buffers([buffer_id_2], buffer, cx);
3629        });
3630        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3631        let blocks = blocks_snapshot
3632            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3633            .collect::<Vec<_>>();
3634        for (_, block) in &blocks {
3635            if let BlockId::Custom(custom_block_id) = block.id() {
3636                assert!(
3637                    !excerpt_blocks_1.contains(&custom_block_id),
3638                    "Should have no blocks from the folded buffer_1"
3639                );
3640                assert!(
3641                    !excerpt_blocks_2.contains(&custom_block_id),
3642                    "Should have no blocks from the folded buffer_2"
3643                );
3644                assert!(
3645                    excerpt_blocks_3.contains(&custom_block_id),
3646                    "Should have only blocks from unfolded buffers"
3647                );
3648            }
3649        }
3650        assert_eq!(
3651            2,
3652            blocks
3653                .iter()
3654                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3655                .count(),
3656            "Should have two folded blocks, producing headers"
3657        );
3658        assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
3659        assert_eq!(
3660            blocks_snapshot
3661                .row_infos(BlockRow(0))
3662                .map(|i| i.buffer_row)
3663                .collect::<Vec<_>>(),
3664            vec![
3665                None,
3666                None,
3667                None,
3668                None,
3669                None,
3670                None,
3671                None,
3672                Some(4),
3673                None,
3674                Some(5),
3675                None,
3676            ]
3677        );
3678
3679        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3680        buffer.read_with(cx, |buffer, cx| {
3681            writer.unfold_buffers([buffer_id_1], buffer, cx);
3682        });
3683        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3684        let blocks = blocks_snapshot
3685            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3686            .collect::<Vec<_>>();
3687        for (_, block) in &blocks {
3688            if let BlockId::Custom(custom_block_id) = block.id() {
3689                assert!(
3690                    !excerpt_blocks_2.contains(&custom_block_id),
3691                    "Should have no blocks from the folded buffer_2"
3692                );
3693                assert!(
3694                    excerpt_blocks_1.contains(&custom_block_id)
3695                        || excerpt_blocks_3.contains(&custom_block_id),
3696                    "Should have only blocks from unfolded buffers"
3697                );
3698            }
3699        }
3700        assert_eq!(
3701            1,
3702            blocks
3703                .iter()
3704                .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3705                .count(),
3706            "Should be back to a single folded buffer, producing a header for buffer_2"
3707        );
3708        assert_eq!(
3709            blocks_snapshot.text(),
3710            "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
3711            "Should have extra newline for 111 buffer, due to a new block added when it was folded"
3712        );
3713        assert_eq!(
3714            blocks_snapshot
3715                .row_infos(BlockRow(0))
3716                .map(|i| i.buffer_row)
3717                .collect::<Vec<_>>(),
3718            vec![
3719                None,
3720                None,
3721                None,
3722                Some(0),
3723                None,
3724                None,
3725                None,
3726                None,
3727                None,
3728                Some(4),
3729                None,
3730                Some(5),
3731                None,
3732            ]
3733        );
3734
3735        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3736        buffer.read_with(cx, |buffer, cx| {
3737            writer.fold_buffers([buffer_id_3], buffer, cx);
3738        });
3739        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3740        let blocks = blocks_snapshot
3741            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3742            .collect::<Vec<_>>();
3743        for (_, block) in &blocks {
3744            if let BlockId::Custom(custom_block_id) = block.id() {
3745                assert!(
3746                    excerpt_blocks_1.contains(&custom_block_id),
3747                    "Should have no blocks from the folded buffer_1"
3748                );
3749                assert!(
3750                    !excerpt_blocks_2.contains(&custom_block_id),
3751                    "Should have only blocks from unfolded buffers"
3752                );
3753                assert!(
3754                    !excerpt_blocks_3.contains(&custom_block_id),
3755                    "Should have only blocks from unfolded buffers"
3756                );
3757            }
3758        }
3759
3760        assert_eq!(
3761            blocks_snapshot.text(),
3762            "\n\n\n111\n\n\n\n",
3763            "Should have a single, first buffer left after folding"
3764        );
3765        assert_eq!(
3766            blocks_snapshot
3767                .row_infos(BlockRow(0))
3768                .map(|i| i.buffer_row)
3769                .collect::<Vec<_>>(),
3770            vec![None, None, None, Some(0), None, None, None, None,]
3771        );
3772    }
3773
3774    #[gpui::test]
3775    fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
3776        cx.update(init_test);
3777
3778        let text = "111";
3779
3780        let buffer = cx.update(|cx| {
3781            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
3782        });
3783        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3784        let buffer_ids = buffer_snapshot
3785            .excerpts()
3786            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3787            .dedup()
3788            .collect::<Vec<_>>();
3789        assert_eq!(buffer_ids.len(), 1);
3790        let buffer_id = buffer_ids[0];
3791
3792        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
3793        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3794        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3795        let (_, wrap_snapshot) =
3796            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3797        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3798        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3799
3800        assert_eq!(blocks_snapshot.text(), "\n\n111");
3801
3802        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3803        buffer.read_with(cx, |buffer, cx| {
3804            writer.fold_buffers([buffer_id], buffer, cx);
3805        });
3806        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3807        let blocks = blocks_snapshot
3808            .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3809            .collect::<Vec<_>>();
3810        assert_eq!(
3811            1,
3812            blocks
3813                .iter()
3814                .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
3815                .count(),
3816            "Should have one folded block, producing a header of the second buffer"
3817        );
3818        assert_eq!(blocks_snapshot.text(), "\n");
3819        assert_eq!(
3820            blocks_snapshot
3821                .row_infos(BlockRow(0))
3822                .map(|i| i.buffer_row)
3823                .collect::<Vec<_>>(),
3824            vec![None, None],
3825            "When fully folded, should be no buffer rows"
3826        );
3827    }
3828
3829    #[gpui::test(iterations = 60)]
3830    fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3831        cx.update(init_test);
3832
3833        let operations = env::var("OPERATIONS")
3834            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3835            .unwrap_or(10);
3836
3837        let wrap_width = if rng.random_bool(0.2) {
3838            None
3839        } else {
3840            Some(px(rng.random_range(0.0..=100.0)))
3841        };
3842        let tab_size = 1.try_into().unwrap();
3843        let font_size = px(14.0);
3844        let buffer_start_header_height = rng.random_range(1..=5);
3845        let excerpt_header_height = rng.random_range(1..=5);
3846
3847        log::info!("Wrap width: {:?}", wrap_width);
3848        log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3849        let is_singleton = rng.random();
3850        let buffer = if is_singleton {
3851            let len = rng.random_range(0..10);
3852            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3853            log::info!("initial singleton buffer text: {:?}", text);
3854            cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3855        } else {
3856            cx.update(|cx| {
3857                let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3858                log::info!(
3859                    "initial multi-buffer text: {:?}",
3860                    multibuffer.read(cx).read(cx).text()
3861                );
3862                multibuffer
3863            })
3864        };
3865
3866        let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3867        let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3868        let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3869        let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3870        let font = test_font();
3871        let (wrap_map, wraps_snapshot) =
3872            cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3873        let mut block_map = BlockMap::new(
3874            wraps_snapshot,
3875            buffer_start_header_height,
3876            excerpt_header_height,
3877        );
3878
3879        for _ in 0..operations {
3880            let mut buffer_edits = Vec::new();
3881            match rng.random_range(0..=100) {
3882                0..=19 => {
3883                    let wrap_width = if rng.random_bool(0.2) {
3884                        None
3885                    } else {
3886                        Some(px(rng.random_range(0.0..=100.0)))
3887                    };
3888                    log::info!("Setting wrap width to {:?}", wrap_width);
3889                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3890                }
3891                20..=39 => {
3892                    let block_count = rng.random_range(1..=5);
3893                    let block_properties = (0..block_count)
3894                        .map(|_| {
3895                            let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3896                            let offset = buffer.clip_offset(
3897                                rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3898                                Bias::Left,
3899                            );
3900                            let mut min_height = 0;
3901                            let placement = match rng.random_range(0..3) {
3902                                0 => {
3903                                    min_height = 1;
3904                                    let start = buffer.anchor_after(offset);
3905                                    let end = buffer.anchor_after(buffer.clip_offset(
3906                                        rng.random_range(offset..=buffer.len()),
3907                                        Bias::Left,
3908                                    ));
3909                                    BlockPlacement::Replace(start..=end)
3910                                }
3911                                1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3912                                _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3913                            };
3914
3915                            let height = rng.random_range(min_height..512);
3916                            BlockProperties {
3917                                style: BlockStyle::Fixed,
3918                                placement,
3919                                height: Some(height),
3920                                render: Arc::new(|_| div().into_any()),
3921                                priority: 0,
3922                            }
3923                        })
3924                        .collect::<Vec<_>>();
3925
3926                    let (inlay_snapshot, inlay_edits) =
3927                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3928                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3929                    let (tab_snapshot, tab_edits) =
3930                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3931                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3932                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3933                    });
3934                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3935                    let block_ids =
3936                        block_map.insert(block_properties.iter().map(|props| BlockProperties {
3937                            placement: props.placement.clone(),
3938                            height: props.height,
3939                            style: props.style,
3940                            render: Arc::new(|_| div().into_any()),
3941                            priority: 0,
3942                        }));
3943
3944                    for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3945                        log::info!(
3946                            "inserted block {:?} with height {:?} and id {:?}",
3947                            block_properties
3948                                .placement
3949                                .as_ref()
3950                                .map(|p| p.to_point(&buffer_snapshot)),
3951                            block_properties.height,
3952                            block_id
3953                        );
3954                    }
3955                }
3956                40..=59 if !block_map.custom_blocks.is_empty() => {
3957                    let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3958                    let block_ids_to_remove = block_map
3959                        .custom_blocks
3960                        .choose_multiple(&mut rng, block_count)
3961                        .map(|block| block.id)
3962                        .collect::<HashSet<_>>();
3963
3964                    let (inlay_snapshot, inlay_edits) =
3965                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3966                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3967                    let (tab_snapshot, tab_edits) =
3968                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3969                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3970                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3971                    });
3972                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3973                    log::info!(
3974                        "removing {} blocks: {:?}",
3975                        block_ids_to_remove.len(),
3976                        block_ids_to_remove
3977                    );
3978                    block_map.remove(block_ids_to_remove);
3979                }
3980                60..=79 => {
3981                    if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3982                        log::info!("Noop fold/unfold operation on a singleton buffer");
3983                        continue;
3984                    }
3985                    let (inlay_snapshot, inlay_edits) =
3986                        inlay_map.sync(buffer_snapshot.clone(), vec![]);
3987                    let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3988                    let (tab_snapshot, tab_edits) =
3989                        tab_map.sync(fold_snapshot, fold_edits, tab_size);
3990                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3991                        wrap_map.sync(tab_snapshot, tab_edits, cx)
3992                    });
3993                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3994                    let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3995                        let folded_buffers: Vec<_> =
3996                            block_map.block_map.folded_buffers.iter().cloned().collect();
3997                        let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3998                        unfolded_buffers.dedup();
3999                        log::debug!("All buffers {unfolded_buffers:?}");
4000                        log::debug!("Folded buffers {folded_buffers:?}");
4001                        unfolded_buffers.retain(|buffer_id| {
4002                            !block_map.block_map.folded_buffers.contains(buffer_id)
4003                        });
4004                        (unfolded_buffers, folded_buffers)
4005                    });
4006                    let mut folded_count = folded_buffers.len();
4007                    let mut unfolded_count = unfolded_buffers.len();
4008
4009                    let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
4010                    let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
4011                    if !fold && !unfold {
4012                        log::info!(
4013                            "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4014                        );
4015                        continue;
4016                    }
4017
4018                    buffer.update(cx, |buffer, cx| {
4019                        if fold {
4020                            let buffer_to_fold =
4021                                unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
4022                            log::info!("Folding {buffer_to_fold:?}");
4023                            let related_excerpts = buffer_snapshot
4024                                .excerpts()
4025                                .filter_map(|(excerpt_id, buffer, range)| {
4026                                    if buffer.remote_id() == buffer_to_fold {
4027                                        Some((
4028                                            excerpt_id,
4029                                            buffer
4030                                                .text_for_range(range.context)
4031                                                .collect::<String>(),
4032                                        ))
4033                                    } else {
4034                                        None
4035                                    }
4036                                })
4037                                .collect::<Vec<_>>();
4038                            log::info!(
4039                                "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
4040                            );
4041                            folded_count += 1;
4042                            unfolded_count -= 1;
4043                            block_map.fold_buffers([buffer_to_fold], buffer, cx);
4044                        }
4045                        if unfold {
4046                            let buffer_to_unfold =
4047                                folded_buffers[rng.random_range(0..folded_buffers.len())];
4048                            log::info!("Unfolding {buffer_to_unfold:?}");
4049                            unfolded_count += 1;
4050                            folded_count -= 1;
4051                            block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
4052                        }
4053                        log::info!(
4054                            "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4055                        );
4056                    });
4057                }
4058                _ => {
4059                    buffer.update(cx, |buffer, cx| {
4060                        let mutation_count = rng.random_range(1..=5);
4061                        let subscription = buffer.subscribe();
4062                        buffer.randomly_mutate(&mut rng, mutation_count, cx);
4063                        buffer_snapshot = buffer.snapshot(cx);
4064                        buffer_edits.extend(subscription.consume());
4065                        log::info!("buffer text: {:?}", buffer_snapshot.text());
4066                    });
4067                }
4068            }
4069
4070            let (inlay_snapshot, inlay_edits) =
4071                inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
4072            let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
4073            let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
4074            let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
4075                wrap_map.sync(tab_snapshot, tab_edits, cx)
4076            });
4077            let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
4078            assert_eq!(
4079                blocks_snapshot.transforms.summary().input_rows,
4080                wraps_snapshot.max_point().row() + RowDelta(1)
4081            );
4082            log::info!("wrapped text: {:?}", wraps_snapshot.text());
4083            log::info!("blocks text: {:?}", blocks_snapshot.text());
4084
4085            let mut expected_blocks = Vec::new();
4086            expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
4087                Some((
4088                    block.placement.to_wrap_row(&wraps_snapshot)?,
4089                    Block::Custom(block.clone()),
4090                ))
4091            }));
4092
4093            let mut inlay_point_cursor = wraps_snapshot.inlay_point_cursor();
4094            let mut tab_point_cursor = wraps_snapshot.tab_point_cursor();
4095            let mut fold_point_cursor = wraps_snapshot.fold_point_cursor();
4096            let mut wrap_point_cursor = wraps_snapshot.wrap_point_cursor();
4097
4098            // Note that this needs to be synced with the related section in BlockMap::sync
4099            expected_blocks.extend(block_map.header_and_footer_blocks(
4100                &buffer_snapshot,
4101                MultiBufferOffset(0)..,
4102                |point, bias| {
4103                    wrap_point_cursor
4104                        .map(
4105                            tab_point_cursor.map(
4106                                fold_point_cursor.map(inlay_point_cursor.map(point, bias), bias),
4107                            ),
4108                        )
4109                        .row()
4110                },
4111            ));
4112
4113            BlockMap::sort_blocks(&mut expected_blocks);
4114
4115            for (placement, block) in &expected_blocks {
4116                log::info!(
4117                    "Block {:?} placement: {:?} Height: {:?}",
4118                    block.id(),
4119                    placement,
4120                    block.height()
4121                );
4122            }
4123
4124            let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
4125
4126            let input_buffer_rows = buffer_snapshot
4127                .row_infos(MultiBufferRow(0))
4128                .map(|row| row.buffer_row)
4129                .collect::<Vec<_>>();
4130            let mut expected_buffer_rows = Vec::new();
4131            let mut expected_text = String::new();
4132            let mut expected_block_positions = Vec::new();
4133            let mut expected_replaced_buffer_rows = HashSet::default();
4134            let input_text = wraps_snapshot.text();
4135
4136            // Loop over the input lines, creating (N - 1) empty lines for
4137            // blocks of height N.
4138            //
4139            // It's important to note that output *starts* as one empty line,
4140            // so we special case row 0 to assume a leading '\n'.
4141            //
4142            // Linehood is the birthright of strings.
4143            let input_text_lines = input_text.split('\n').enumerate().peekable();
4144            let mut block_row = 0;
4145            for (wrap_row, input_line) in input_text_lines {
4146                let wrap_row = WrapRow(wrap_row as u32);
4147                let multibuffer_row = wraps_snapshot
4148                    .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
4149                    .row;
4150
4151                // Create empty lines for the above block
4152                while let Some((placement, block)) = sorted_blocks_iter.peek() {
4153                    if *placement.start() == wrap_row && block.place_above() {
4154                        let (_, block) = sorted_blocks_iter.next().unwrap();
4155                        expected_block_positions.push((block_row, block.id()));
4156                        if block.height() > 0 {
4157                            let text = "\n".repeat((block.height() - 1) as usize);
4158                            if block_row > 0 {
4159                                expected_text.push('\n')
4160                            }
4161                            expected_text.push_str(&text);
4162                            for _ in 0..block.height() {
4163                                expected_buffer_rows.push(None);
4164                            }
4165                            block_row += block.height();
4166                        }
4167                    } else {
4168                        break;
4169                    }
4170                }
4171
4172                // Skip lines within replace blocks, then create empty lines for the replace block's height
4173                let mut is_in_replace_block = false;
4174                if let Some((BlockPlacement::Replace(replace_range), block)) =
4175                    sorted_blocks_iter.peek()
4176                    && wrap_row >= *replace_range.start()
4177                {
4178                    is_in_replace_block = true;
4179
4180                    if wrap_row == *replace_range.start() {
4181                        if matches!(block, Block::FoldedBuffer { .. }) {
4182                            expected_buffer_rows.push(None);
4183                        } else {
4184                            expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
4185                        }
4186                    }
4187
4188                    if wrap_row == *replace_range.end() {
4189                        expected_block_positions.push((block_row, block.id()));
4190                        let text = "\n".repeat((block.height() - 1) as usize);
4191                        if block_row > 0 {
4192                            expected_text.push('\n');
4193                        }
4194                        expected_text.push_str(&text);
4195
4196                        for _ in 1..block.height() {
4197                            expected_buffer_rows.push(None);
4198                        }
4199                        block_row += block.height();
4200
4201                        sorted_blocks_iter.next();
4202                    }
4203                }
4204
4205                if is_in_replace_block {
4206                    expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
4207                } else {
4208                    let buffer_row = input_buffer_rows[multibuffer_row as usize];
4209                    let soft_wrapped = wraps_snapshot
4210                        .to_tab_point(WrapPoint::new(wrap_row, 0))
4211                        .column()
4212                        > 0;
4213                    expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
4214                    if block_row > 0 {
4215                        expected_text.push('\n');
4216                    }
4217                    expected_text.push_str(input_line);
4218                    block_row += 1;
4219                }
4220
4221                while let Some((placement, block)) = sorted_blocks_iter.peek() {
4222                    if *placement.end() == wrap_row && block.place_below() {
4223                        let (_, block) = sorted_blocks_iter.next().unwrap();
4224                        expected_block_positions.push((block_row, block.id()));
4225                        if block.height() > 0 {
4226                            let text = "\n".repeat((block.height() - 1) as usize);
4227                            if block_row > 0 {
4228                                expected_text.push('\n')
4229                            }
4230                            expected_text.push_str(&text);
4231                            for _ in 0..block.height() {
4232                                expected_buffer_rows.push(None);
4233                            }
4234                            block_row += block.height();
4235                        }
4236                    } else {
4237                        break;
4238                    }
4239                }
4240            }
4241
4242            let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
4243            let expected_row_count = expected_lines.len();
4244            log::info!("expected text: {expected_text:?}");
4245
4246            assert_eq!(
4247                blocks_snapshot.max_point().row + 1,
4248                expected_row_count as u32,
4249                "actual row count != expected row count",
4250            );
4251            assert_eq!(
4252                blocks_snapshot.text(),
4253                expected_text,
4254                "actual text != expected text",
4255            );
4256
4257            for start_row in 0..expected_row_count {
4258                let end_row = rng.random_range(start_row + 1..=expected_row_count);
4259                let mut expected_text = expected_lines[start_row..end_row].join("\n");
4260                if end_row < expected_row_count {
4261                    expected_text.push('\n');
4262                }
4263
4264                let actual_text = blocks_snapshot
4265                    .chunks(
4266                        BlockRow(start_row as u32)..BlockRow(end_row as u32),
4267                        false,
4268                        false,
4269                        Highlights::default(),
4270                    )
4271                    .map(|chunk| chunk.text)
4272                    .collect::<String>();
4273                assert_eq!(
4274                    actual_text,
4275                    expected_text,
4276                    "incorrect text starting row row range {:?}",
4277                    start_row..end_row
4278                );
4279                assert_eq!(
4280                    blocks_snapshot
4281                        .row_infos(BlockRow(start_row as u32))
4282                        .map(|row_info| row_info.buffer_row)
4283                        .collect::<Vec<_>>(),
4284                    &expected_buffer_rows[start_row..],
4285                    "incorrect buffer_rows starting at row {:?}",
4286                    start_row
4287                );
4288            }
4289
4290            assert_eq!(
4291                blocks_snapshot
4292                    .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4293                    .map(|(row, block)| (row.0, block.id()))
4294                    .collect::<Vec<_>>(),
4295                expected_block_positions,
4296                "invalid blocks_in_range({:?})",
4297                0..expected_row_count
4298            );
4299
4300            for (_, expected_block) in
4301                blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4302            {
4303                let actual_block = blocks_snapshot.block_for_id(expected_block.id());
4304                assert_eq!(
4305                    actual_block.map(|block| block.id()),
4306                    Some(expected_block.id())
4307                );
4308            }
4309
4310            for (block_row, block_id) in expected_block_positions {
4311                if let BlockId::Custom(block_id) = block_id {
4312                    assert_eq!(
4313                        blocks_snapshot.row_for_block(block_id),
4314                        Some(BlockRow(block_row))
4315                    );
4316                }
4317            }
4318
4319            let mut expected_longest_rows = Vec::new();
4320            let mut longest_line_len = -1_isize;
4321            for (row, line) in expected_lines.iter().enumerate() {
4322                let row = row as u32;
4323
4324                assert_eq!(
4325                    blocks_snapshot.line_len(BlockRow(row)),
4326                    line.len() as u32,
4327                    "invalid line len for row {}",
4328                    row
4329                );
4330
4331                let line_char_count = line.chars().count() as isize;
4332                match line_char_count.cmp(&longest_line_len) {
4333                    Ordering::Less => {}
4334                    Ordering::Equal => expected_longest_rows.push(row),
4335                    Ordering::Greater => {
4336                        longest_line_len = line_char_count;
4337                        expected_longest_rows.clear();
4338                        expected_longest_rows.push(row);
4339                    }
4340                }
4341            }
4342
4343            let longest_row = blocks_snapshot.longest_row();
4344            assert!(
4345                expected_longest_rows.contains(&longest_row.0),
4346                "incorrect longest row {}. expected {:?} with length {}",
4347                longest_row.0,
4348                expected_longest_rows,
4349                longest_line_len,
4350            );
4351
4352            for _ in 0..10 {
4353                let end_row = rng.random_range(1..=expected_lines.len());
4354                let start_row = rng.random_range(0..end_row);
4355
4356                let mut expected_longest_rows_in_range = vec![];
4357                let mut longest_line_len_in_range = 0;
4358
4359                let mut row = start_row as u32;
4360                for line in &expected_lines[start_row..end_row] {
4361                    let line_char_count = line.chars().count() as isize;
4362                    match line_char_count.cmp(&longest_line_len_in_range) {
4363                        Ordering::Less => {}
4364                        Ordering::Equal => expected_longest_rows_in_range.push(row),
4365                        Ordering::Greater => {
4366                            longest_line_len_in_range = line_char_count;
4367                            expected_longest_rows_in_range.clear();
4368                            expected_longest_rows_in_range.push(row);
4369                        }
4370                    }
4371                    row += 1;
4372                }
4373
4374                let longest_row_in_range = blocks_snapshot
4375                    .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
4376                assert!(
4377                    expected_longest_rows_in_range.contains(&longest_row_in_range.0),
4378                    "incorrect longest row {} in range {:?}. expected {:?} with length {}",
4379                    longest_row.0,
4380                    start_row..end_row,
4381                    expected_longest_rows_in_range,
4382                    longest_line_len_in_range,
4383                );
4384            }
4385
4386            // Ensure that conversion between block points and wrap points is stable.
4387            for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
4388                let wrap_point = WrapPoint::new(WrapRow(row), 0);
4389                let block_point = blocks_snapshot.to_block_point(wrap_point);
4390                let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
4391                let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
4392                assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
4393                assert_eq!(
4394                    blocks_snapshot.to_block_point(right_wrap_point),
4395                    block_point
4396                );
4397            }
4398
4399            let mut block_point = BlockPoint::new(BlockRow(0), 0);
4400            for c in expected_text.chars() {
4401                let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
4402                let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
4403                assert_eq!(
4404                    blocks_snapshot
4405                        .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
4406                    left_point,
4407                    "block point: {:?}, wrap point: {:?}",
4408                    block_point,
4409                    blocks_snapshot.to_wrap_point(left_point, Bias::Left)
4410                );
4411                assert_eq!(
4412                    left_buffer_point,
4413                    buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
4414                    "{:?} is not valid in buffer coordinates",
4415                    left_point
4416                );
4417
4418                let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
4419                let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
4420                assert_eq!(
4421                    blocks_snapshot
4422                        .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
4423                    right_point,
4424                    "block point: {:?}, wrap point: {:?}",
4425                    block_point,
4426                    blocks_snapshot.to_wrap_point(right_point, Bias::Right)
4427                );
4428                assert_eq!(
4429                    right_buffer_point,
4430                    buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
4431                    "{:?} is not valid in buffer coordinates",
4432                    right_point
4433                );
4434
4435                if c == '\n' {
4436                    block_point.0 += Point::new(1, 0);
4437                } else {
4438                    block_point.column += c.len_utf8() as u32;
4439                }
4440            }
4441
4442            for buffer_row in 0..=buffer_snapshot.max_point().row {
4443                let buffer_row = MultiBufferRow(buffer_row);
4444                assert_eq!(
4445                    blocks_snapshot.is_line_replaced(buffer_row),
4446                    expected_replaced_buffer_rows.contains(&buffer_row),
4447                    "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
4448                );
4449            }
4450        }
4451    }
4452
4453    #[gpui::test]
4454    fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
4455        cx.update(init_test);
4456
4457        let text = "abc\ndef\nghi\njkl\nmno";
4458        let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4459        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4460        let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4461        let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
4462        let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4463        let (_wrap_map, wraps_snapshot) =
4464            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4465        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
4466
4467        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4468        let _block_id = writer.insert(vec![BlockProperties {
4469            style: BlockStyle::Fixed,
4470            placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
4471            height: Some(1),
4472            render: Arc::new(|_| div().into_any()),
4473            priority: 0,
4474        }])[0];
4475
4476        let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
4477        assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4478
4479        let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4480        writer.remove_intersecting_replace_blocks(
4481            [buffer_snapshot
4482                .anchor_after(Point::new(1, 0))
4483                .to_offset(&buffer_snapshot)
4484                ..buffer_snapshot
4485                    .anchor_after(Point::new(1, 0))
4486                    .to_offset(&buffer_snapshot)],
4487            false,
4488        );
4489        let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
4490        assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4491    }
4492
4493    #[gpui::test]
4494    fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
4495        cx.update(init_test);
4496
4497        let text = "line 1\nline 2\nline 3";
4498        let buffer = cx.update(|cx| {
4499            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
4500        });
4501        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4502        let buffer_ids = buffer_snapshot
4503            .excerpts()
4504            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4505            .dedup()
4506            .collect::<Vec<_>>();
4507        assert_eq!(buffer_ids.len(), 1);
4508        let buffer_id = buffer_ids[0];
4509
4510        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4511        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4512        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4513        let (_, wrap_snapshot) =
4514            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4515        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4516
4517        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4518        writer.insert(vec![BlockProperties {
4519            style: BlockStyle::Fixed,
4520            placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
4521            height: Some(1),
4522            render: Arc::new(|_| div().into_any()),
4523            priority: 0,
4524        }]);
4525
4526        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4527        assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
4528
4529        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4530        buffer.read_with(cx, |buffer, cx| {
4531            writer.fold_buffers([buffer_id], buffer, cx);
4532        });
4533
4534        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4535        assert_eq!(blocks_snapshot.text(), "");
4536    }
4537
4538    #[gpui::test]
4539    fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
4540        cx.update(init_test);
4541
4542        let text = "line 1\nline 2\nline 3\nline 4";
4543        let buffer = cx.update(|cx| {
4544            MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
4545        });
4546        let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4547        let buffer_ids = buffer_snapshot
4548            .excerpts()
4549            .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4550            .dedup()
4551            .collect::<Vec<_>>();
4552        assert_eq!(buffer_ids.len(), 1);
4553        let buffer_id = buffer_ids[0];
4554
4555        let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4556        let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4557        let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4558        let (_, wrap_snapshot) =
4559            cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4560        let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4561
4562        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4563        writer.insert(vec![BlockProperties {
4564            style: BlockStyle::Fixed,
4565            placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
4566            height: Some(1),
4567            render: Arc::new(|_| div().into_any()),
4568            priority: 0,
4569        }]);
4570
4571        let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4572        assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
4573
4574        let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4575        buffer.read_with(cx, |buffer, cx| {
4576            writer.fold_buffers([buffer_id], buffer, cx);
4577        });
4578
4579        let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4580        assert_eq!(blocks_snapshot.text(), "");
4581    }
4582
4583    #[gpui::test]
4584    fn test_companion_spacer_blocks(cx: &mut gpui::TestAppContext) {
4585        cx.update(init_test);
4586
4587        let base_text = "aaa\nbbb\nccc\nddd\nddd\nddd\neee\n";
4588        let main_text = "aaa\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n";
4589
4590        let rhs_buffer = cx.new(|cx| Buffer::local(main_text, cx));
4591        let diff = cx.new(|cx| {
4592            BufferDiff::new_with_base_text(base_text, &rhs_buffer.read(cx).text_snapshot(), cx)
4593        });
4594        let lhs_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer());
4595
4596        let lhs_multibuffer = cx.new(|cx| {
4597            let mut mb = MultiBuffer::new(Capability::ReadWrite);
4598            mb.push_excerpts(
4599                lhs_buffer.clone(),
4600                [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4601                cx,
4602            );
4603            mb.add_inverted_diff(diff.clone(), cx);
4604            mb
4605        });
4606        let rhs_multibuffer = cx.new(|cx| {
4607            let mut mb = MultiBuffer::new(Capability::ReadWrite);
4608            mb.push_excerpts(
4609                rhs_buffer.clone(),
4610                [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4611                cx,
4612            );
4613            mb.add_diff(diff.clone(), cx);
4614            mb
4615        });
4616        let subscription =
4617            rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
4618
4619        let lhs_excerpt_id =
4620            lhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4621        let rhs_excerpt_id =
4622            rhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4623
4624        let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
4625        let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
4626        let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
4627        let (mut _lhs_tab_map, lhs_tab_snapshot) =
4628            TabMap::new(lhs_fold_snapshot, 4.try_into().unwrap());
4629        let (_lhs_wrap_map, lhs_wrap_snapshot) =
4630            cx.update(|cx| WrapMap::new(lhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4631        let lhs_block_map = BlockMap::new(lhs_wrap_snapshot.clone(), 0, 0);
4632
4633        let rhs_buffer_snapshot = cx.update(|cx| rhs_multibuffer.read(cx).snapshot(cx));
4634        let (mut rhs_inlay_map, rhs_inlay_snapshot) = InlayMap::new(rhs_buffer_snapshot);
4635        let (mut rhs_fold_map, rhs_fold_snapshot) = FoldMap::new(rhs_inlay_snapshot);
4636        let (mut rhs_tab_map, rhs_tab_snapshot) =
4637            TabMap::new(rhs_fold_snapshot, 4.try_into().unwrap());
4638        let (_rhs_wrap_map, rhs_wrap_snapshot) =
4639            cx.update(|cx| WrapMap::new(rhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4640        let rhs_block_map = BlockMap::new(rhs_wrap_snapshot.clone(), 0, 0);
4641
4642        let rhs_entity_id = rhs_multibuffer.entity_id();
4643
4644        let companion = cx.new(|_| {
4645            let mut c = Companion::new(
4646                rhs_entity_id,
4647                convert_rhs_rows_to_lhs,
4648                convert_lhs_rows_to_rhs,
4649            );
4650            c.add_excerpt_mapping(lhs_excerpt_id, rhs_excerpt_id);
4651            c
4652        });
4653
4654        let rhs_edits = Patch::new(vec![text::Edit {
4655            old: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4656            new: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4657        }]);
4658        let lhs_edits = Patch::new(vec![text::Edit {
4659            old: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4660            new: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4661        }]);
4662
4663        let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4664            rhs_block_map.read(
4665                rhs_wrap_snapshot.clone(),
4666                rhs_edits.clone(),
4667                Some(CompanionView::new(
4668                    rhs_entity_id,
4669                    &lhs_wrap_snapshot,
4670                    &lhs_edits,
4671                    companion,
4672                )),
4673            )
4674        });
4675
4676        let lhs_entity_id = lhs_multibuffer.entity_id();
4677        let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4678            lhs_block_map.read(
4679                lhs_wrap_snapshot.clone(),
4680                lhs_edits.clone(),
4681                Some(CompanionView::new(
4682                    lhs_entity_id,
4683                    &rhs_wrap_snapshot,
4684                    &rhs_edits,
4685                    companion,
4686                )),
4687            )
4688        });
4689
4690        // LHS:
4691        //   aaa
4692        // - bbb
4693        // - ccc
4694        //   ddd
4695        //   ddd
4696        //   ddd
4697        //   <extra line>
4698        //   <extra line>
4699        //   <extra line>
4700        //   *eee
4701        //
4702        // RHS:
4703        //   aaa
4704        //   <extra line>
4705        //   <extra line>
4706        //   ddd
4707        //   ddd
4708        //   ddd
4709        // + XXX
4710        // + YYY
4711        // + ZZZ
4712        //   eee
4713
4714        assert_eq!(
4715            rhs_snapshot.snapshot.text(),
4716            "aaa\n\n\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4717            "RHS should have 2 spacer lines after 'aaa' to align with LHS's deleted lines"
4718        );
4719
4720        assert_eq!(
4721            lhs_snapshot.snapshot.text(),
4722            "aaa\nbbb\nccc\nddd\nddd\nddd\n\n\n\neee\n",
4723            "LHS should have 3 spacer lines in place of RHS's inserted lines"
4724        );
4725
4726        // LHS:
4727        //   aaa
4728        // - bbb
4729        // - ccc
4730        //   ddd
4731        //   ddd
4732        //   ddd
4733        //   <extra line>
4734        //   <extra line>
4735        //   <extra line>
4736        //   eee
4737        //
4738        // RHS:
4739        //   aaa
4740        //   <extra line>
4741        //   <extra line>
4742        //   ddd
4743        //   foo
4744        //   foo
4745        //   foo
4746        //   ddd
4747        //   ddd
4748        // + XXX
4749        // + YYY
4750        // + ZZZ
4751        //   eee
4752
4753        let rhs_buffer_snapshot = rhs_multibuffer.update(cx, |multibuffer, cx| {
4754            multibuffer.edit(
4755                [(Point::new(2, 0)..Point::new(2, 0), "foo\nfoo\nfoo\n")],
4756                None,
4757                cx,
4758            );
4759            multibuffer.snapshot(cx)
4760        });
4761
4762        let (rhs_inlay_snapshot, rhs_inlay_edits) =
4763            rhs_inlay_map.sync(rhs_buffer_snapshot, subscription.consume().into_inner());
4764        let (rhs_fold_snapshot, rhs_fold_edits) =
4765            rhs_fold_map.read(rhs_inlay_snapshot, rhs_inlay_edits);
4766        let (rhs_tab_snapshot, rhs_tab_edits) =
4767            rhs_tab_map.sync(rhs_fold_snapshot, rhs_fold_edits, 4.try_into().unwrap());
4768        let (rhs_wrap_snapshot, rhs_wrap_edits) = _rhs_wrap_map.update(cx, |wrap_map, cx| {
4769            wrap_map.sync(rhs_tab_snapshot, rhs_tab_edits, cx)
4770        });
4771
4772        let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4773            rhs_block_map.read(
4774                rhs_wrap_snapshot.clone(),
4775                rhs_wrap_edits.clone(),
4776                Some(CompanionView::new(
4777                    rhs_entity_id,
4778                    &lhs_wrap_snapshot,
4779                    &Default::default(),
4780                    companion,
4781                )),
4782            )
4783        });
4784
4785        let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4786            lhs_block_map.read(
4787                lhs_wrap_snapshot.clone(),
4788                Default::default(),
4789                Some(CompanionView::new(
4790                    lhs_entity_id,
4791                    &rhs_wrap_snapshot,
4792                    &rhs_wrap_edits,
4793                    companion,
4794                )),
4795            )
4796        });
4797
4798        assert_eq!(
4799            rhs_snapshot.snapshot.text(),
4800            "aaa\n\n\nddd\nfoo\nfoo\nfoo\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4801            "RHS should have the insertion"
4802        );
4803
4804        assert_eq!(
4805            lhs_snapshot.snapshot.text(),
4806            "aaa\nbbb\nccc\nddd\n\n\n\nddd\nddd\n\n\n\neee\n",
4807            "LHS should have 3 more spacer lines to balance the insertion"
4808        );
4809    }
4810
4811    fn init_test(cx: &mut gpui::App) {
4812        let settings = SettingsStore::test(cx);
4813        cx.set_global(settings);
4814        theme::init(theme::LoadThemes::JustBase, cx);
4815        assets::Assets.load_test_fonts(cx);
4816    }
4817
4818    impl Block {
4819        fn as_custom(&self) -> Option<&CustomBlock> {
4820            match self {
4821                Block::Custom(block) => Some(block),
4822                _ => None,
4823            }
4824        }
4825    }
4826
4827    impl BlockSnapshot {
4828        fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
4829            self.wrap_snapshot
4830                .to_point(self.to_wrap_point(point, bias), bias)
4831        }
4832    }
4833}