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