block_map.rs

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