block_map.rs

   1use super::wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot};
   2use gpui::{AppContext, ElementBox, ModelHandle};
   3use language::{
   4    multi_buffer::{Anchor, MultiBuffer, ToOffset, ToPoint as _},
   5    Chunk,
   6};
   7use parking_lot::Mutex;
   8use std::{
   9    cmp::{self, Ordering},
  10    collections::{HashMap, HashSet},
  11    fmt::Debug,
  12    ops::{Deref, Range},
  13    sync::{
  14        atomic::{AtomicUsize, Ordering::SeqCst},
  15        Arc,
  16    },
  17};
  18use sum_tree::{Bias, SumTree};
  19use text::{Edit, Point};
  20use theme::SyntaxTheme;
  21
  22const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
  23
  24pub struct BlockMap {
  25    buffer: ModelHandle<MultiBuffer>,
  26    next_block_id: AtomicUsize,
  27    wrap_snapshot: Mutex<WrapSnapshot>,
  28    blocks: Vec<Arc<Block>>,
  29    transforms: Mutex<SumTree<Transform>>,
  30}
  31
  32pub struct BlockMapWriter<'a>(&'a mut BlockMap);
  33
  34pub struct BlockSnapshot {
  35    wrap_snapshot: WrapSnapshot,
  36    transforms: SumTree<Transform>,
  37}
  38
  39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
  40pub struct BlockId(usize);
  41
  42#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
  43pub struct BlockPoint(pub super::Point);
  44
  45#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
  46struct BlockRow(u32);
  47
  48#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
  49struct WrapRow(u32);
  50
  51pub struct Block {
  52    id: BlockId,
  53    position: Anchor,
  54    height: u8,
  55    render: Mutex<Arc<dyn Fn(&BlockContext) -> ElementBox>>,
  56    disposition: BlockDisposition,
  57}
  58
  59#[derive(Clone)]
  60pub struct BlockProperties<P>
  61where
  62    P: Clone,
  63{
  64    pub position: P,
  65    pub height: u8,
  66    pub render: Arc<dyn Fn(&BlockContext) -> ElementBox>,
  67    pub disposition: BlockDisposition,
  68}
  69
  70pub struct BlockContext<'a> {
  71    pub cx: &'a AppContext,
  72    pub anchor_x: f32,
  73}
  74
  75#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
  76pub enum BlockDisposition {
  77    Above,
  78    Below,
  79}
  80
  81#[derive(Clone, Debug)]
  82struct Transform {
  83    summary: TransformSummary,
  84    block: Option<AlignedBlock>,
  85}
  86
  87#[derive(Clone, Debug)]
  88pub struct AlignedBlock {
  89    block: Arc<Block>,
  90    column: u32,
  91}
  92
  93#[derive(Clone, Debug, Default)]
  94struct TransformSummary {
  95    input_rows: u32,
  96    output_rows: u32,
  97}
  98
  99pub struct BlockChunks<'a> {
 100    transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
 101    input_chunks: wrap_map::WrapChunks<'a>,
 102    input_chunk: Chunk<'a>,
 103    output_row: u32,
 104    max_output_row: u32,
 105}
 106
 107pub struct BlockBufferRows<'a> {
 108    transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
 109    input_buffer_rows: wrap_map::WrapBufferRows<'a>,
 110    output_row: u32,
 111    started: bool,
 112}
 113
 114impl BlockMap {
 115    pub fn new(buffer: ModelHandle<MultiBuffer>, wrap_snapshot: WrapSnapshot) -> Self {
 116        Self {
 117            buffer,
 118            next_block_id: AtomicUsize::new(0),
 119            blocks: Vec::new(),
 120            transforms: Mutex::new(SumTree::from_item(
 121                Transform::isomorphic(wrap_snapshot.text_summary().lines.row + 1),
 122                &(),
 123            )),
 124            wrap_snapshot: Mutex::new(wrap_snapshot),
 125        }
 126    }
 127
 128    pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockSnapshot {
 129        self.sync(&wrap_snapshot, edits);
 130        *self.wrap_snapshot.lock() = wrap_snapshot.clone();
 131        BlockSnapshot {
 132            wrap_snapshot,
 133            transforms: self.transforms.lock().clone(),
 134        }
 135    }
 136
 137    pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockMapWriter {
 138        self.sync(&wrap_snapshot, edits);
 139        *self.wrap_snapshot.lock() = wrap_snapshot;
 140        BlockMapWriter(self)
 141    }
 142
 143    fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec<WrapEdit>) {
 144        if edits.is_empty() {
 145            return;
 146        }
 147
 148        let buffer = wrap_snapshot.buffer_snapshot();
 149        let mut transforms = self.transforms.lock();
 150        let mut new_transforms = SumTree::new();
 151        let old_row_count = transforms.summary().input_rows;
 152        let new_row_count = wrap_snapshot.max_point().row() + 1;
 153        let mut cursor = transforms.cursor::<WrapRow>();
 154        let mut last_block_ix = 0;
 155        let mut blocks_in_edit = Vec::new();
 156        let mut edits = edits.into_iter().peekable();
 157
 158        while let Some(edit) = edits.next() {
 159            // Preserve any old transforms that precede this edit.
 160            let old_start = WrapRow(edit.old.start);
 161            let new_start = WrapRow(edit.new.start);
 162            new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &());
 163            if let Some(transform) = cursor.item() {
 164                if transform.is_isomorphic() && old_start == cursor.end(&()) {
 165                    new_transforms.push(transform.clone(), &());
 166                    cursor.next(&());
 167                    while let Some(transform) = cursor.item() {
 168                        if transform
 169                            .block
 170                            .as_ref()
 171                            .map_or(false, |b| b.disposition.is_below())
 172                        {
 173                            new_transforms.push(transform.clone(), &());
 174                            cursor.next(&());
 175                        } else {
 176                            break;
 177                        }
 178                    }
 179                }
 180            }
 181
 182            // Preserve any portion of an old transform that precedes this edit.
 183            let extent_before_edit = old_start.0 - cursor.start().0;
 184            push_isomorphic(&mut new_transforms, extent_before_edit);
 185
 186            // Skip over any old transforms that intersect this edit.
 187            let mut old_end = WrapRow(edit.old.end);
 188            let mut new_end = WrapRow(edit.new.end);
 189            cursor.seek(&old_end, Bias::Left, &());
 190            cursor.next(&());
 191            if old_end == *cursor.start() {
 192                while let Some(transform) = cursor.item() {
 193                    if transform
 194                        .block
 195                        .as_ref()
 196                        .map_or(false, |b| b.disposition.is_below())
 197                    {
 198                        cursor.next(&());
 199                    } else {
 200                        break;
 201                    }
 202                }
 203            }
 204
 205            // Combine this edit with any subsequent edits that intersect the same transform.
 206            while let Some(next_edit) = edits.peek() {
 207                if next_edit.old.start <= cursor.start().0 {
 208                    old_end = WrapRow(next_edit.old.end);
 209                    new_end = WrapRow(next_edit.new.end);
 210                    cursor.seek(&old_end, Bias::Left, &());
 211                    cursor.next(&());
 212                    if old_end == *cursor.start() {
 213                        while let Some(transform) = cursor.item() {
 214                            if transform
 215                                .block
 216                                .as_ref()
 217                                .map_or(false, |b| b.disposition.is_below())
 218                            {
 219                                cursor.next(&());
 220                            } else {
 221                                break;
 222                            }
 223                        }
 224                    }
 225                    edits.next();
 226                } else {
 227                    break;
 228                }
 229            }
 230
 231            // Find the blocks within this edited region.
 232            let new_start = wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
 233            let start_anchor = buffer.anchor_before(new_start);
 234            let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| {
 235                probe
 236                    .position
 237                    .cmp(&start_anchor, &buffer)
 238                    .unwrap()
 239                    .then(Ordering::Greater)
 240            }) {
 241                Ok(ix) | Err(ix) => last_block_ix + ix,
 242            };
 243            let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
 244                self.blocks.len()
 245            } else {
 246                let new_end = wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
 247                let end_anchor = buffer.anchor_before(new_end);
 248                match self.blocks[start_block_ix..].binary_search_by(|probe| {
 249                    probe
 250                        .position
 251                        .cmp(&end_anchor, &buffer)
 252                        .unwrap()
 253                        .then(Ordering::Greater)
 254                }) {
 255                    Ok(ix) | Err(ix) => start_block_ix + ix,
 256                }
 257            };
 258            last_block_ix = end_block_ix;
 259            blocks_in_edit.clear();
 260            blocks_in_edit.extend(
 261                self.blocks[start_block_ix..end_block_ix]
 262                    .iter()
 263                    .map(|block| {
 264                        let mut position = block.position.to_point(&buffer);
 265                        let column = wrap_snapshot.from_point(position, Bias::Left).column();
 266                        match block.disposition {
 267                            BlockDisposition::Above => position.column = 0,
 268                            BlockDisposition::Below => {
 269                                position.column = buffer.line_len(position.row)
 270                            }
 271                        }
 272                        let position = wrap_snapshot.from_point(position, Bias::Left);
 273                        (position.row(), column, block)
 274                    }),
 275            );
 276            blocks_in_edit
 277                .sort_unstable_by_key(|(row, _, block)| (*row, block.disposition, block.id));
 278
 279            // For each of these blocks, insert a new isomorphic transform preceding the block,
 280            // and then insert the block itself.
 281            for (block_row, column, block) in blocks_in_edit.iter().copied() {
 282                let insertion_row = match block.disposition {
 283                    BlockDisposition::Above => block_row,
 284                    BlockDisposition::Below => block_row + 1,
 285                };
 286                let extent_before_block = insertion_row - new_transforms.summary().input_rows;
 287                push_isomorphic(&mut new_transforms, extent_before_block);
 288                new_transforms.push(Transform::block(block.clone(), column), &());
 289            }
 290
 291            old_end = WrapRow(old_end.0.min(old_row_count));
 292            new_end = WrapRow(new_end.0.min(new_row_count));
 293
 294            // Insert an isomorphic transform after the final block.
 295            let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows;
 296            push_isomorphic(&mut new_transforms, extent_after_last_block);
 297
 298            // Preserve any portion of the old transform after this edit.
 299            let extent_after_edit = cursor.start().0 - old_end.0;
 300            push_isomorphic(&mut new_transforms, extent_after_edit);
 301        }
 302
 303        new_transforms.push_tree(cursor.suffix(&()), &());
 304        debug_assert_eq!(
 305            new_transforms.summary().input_rows,
 306            wrap_snapshot.max_point().row() + 1
 307        );
 308
 309        drop(cursor);
 310        *transforms = new_transforms;
 311    }
 312
 313    pub fn replace<F>(&mut self, mut element_builders: HashMap<BlockId, F>)
 314    where
 315        F: 'static + Fn(&BlockContext) -> ElementBox,
 316    {
 317        for block in &self.blocks {
 318            if let Some(build_element) = element_builders.remove(&block.id) {
 319                *block.render.lock() = Arc::new(build_element);
 320            }
 321        }
 322    }
 323}
 324
 325fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32) {
 326    if rows == 0 {
 327        return;
 328    }
 329
 330    let mut extent = Some(rows);
 331    tree.update_last(
 332        |last_transform| {
 333            if last_transform.is_isomorphic() {
 334                let extent = extent.take().unwrap();
 335                last_transform.summary.input_rows += extent;
 336                last_transform.summary.output_rows += extent;
 337            }
 338        },
 339        &(),
 340    );
 341    if let Some(extent) = extent {
 342        tree.push(Transform::isomorphic(extent), &());
 343    }
 344}
 345
 346impl BlockPoint {
 347    pub fn new(row: u32, column: u32) -> Self {
 348        Self(Point::new(row, column))
 349    }
 350}
 351
 352impl Deref for BlockPoint {
 353    type Target = Point;
 354
 355    fn deref(&self) -> &Self::Target {
 356        &self.0
 357    }
 358}
 359
 360impl std::ops::DerefMut for BlockPoint {
 361    fn deref_mut(&mut self) -> &mut Self::Target {
 362        &mut self.0
 363    }
 364}
 365
 366impl<'a> BlockMapWriter<'a> {
 367    pub fn insert<P>(
 368        &mut self,
 369        blocks: impl IntoIterator<Item = BlockProperties<P>>,
 370    ) -> Vec<BlockId>
 371    where
 372        P: ToOffset + Clone,
 373    {
 374        let mut ids = Vec::new();
 375        let mut edits = Vec::<Edit<u32>>::new();
 376        let wrap_snapshot = &*self.0.wrap_snapshot.lock();
 377        let buffer = wrap_snapshot.buffer_snapshot();
 378
 379        for block in blocks {
 380            let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst));
 381            ids.push(id);
 382
 383            let position = buffer.anchor_after(block.position);
 384            let point = position.to_point(&buffer);
 385            let start_row = wrap_snapshot
 386                .from_point(Point::new(point.row, 0), Bias::Left)
 387                .row();
 388            let end_row = if point.row == buffer.max_point().row {
 389                wrap_snapshot.max_point().row() + 1
 390            } else {
 391                wrap_snapshot
 392                    .from_point(Point::new(point.row + 1, 0), Bias::Left)
 393                    .row()
 394            };
 395
 396            let block_ix = match self
 397                .0
 398                .blocks
 399                .binary_search_by(|probe| probe.position.cmp(&position, &buffer).unwrap())
 400            {
 401                Ok(ix) | Err(ix) => ix,
 402            };
 403            self.0.blocks.insert(
 404                block_ix,
 405                Arc::new(Block {
 406                    id,
 407                    position,
 408                    height: block.height,
 409                    render: Mutex::new(block.render),
 410                    disposition: block.disposition,
 411                }),
 412            );
 413
 414            if let Err(edit_ix) = edits.binary_search_by_key(&start_row, |edit| edit.old.start) {
 415                edits.insert(
 416                    edit_ix,
 417                    Edit {
 418                        old: start_row..end_row,
 419                        new: start_row..end_row,
 420                    },
 421                );
 422            }
 423        }
 424
 425        self.0.sync(wrap_snapshot, edits);
 426        ids
 427    }
 428
 429    pub fn remove(&mut self, block_ids: HashSet<BlockId>) {
 430        let wrap_snapshot = &*self.0.wrap_snapshot.lock();
 431        let buffer = wrap_snapshot.buffer_snapshot();
 432        let mut edits = Vec::new();
 433        let mut last_block_buffer_row = None;
 434        self.0.blocks.retain(|block| {
 435            if block_ids.contains(&block.id) {
 436                let buffer_row = block.position.to_point(&buffer).row;
 437                if last_block_buffer_row != Some(buffer_row) {
 438                    last_block_buffer_row = Some(buffer_row);
 439                    let start_row = wrap_snapshot
 440                        .from_point(Point::new(buffer_row, 0), Bias::Left)
 441                        .row();
 442                    let end_row = wrap_snapshot
 443                        .from_point(
 444                            Point::new(buffer_row, buffer.line_len(buffer_row)),
 445                            Bias::Left,
 446                        )
 447                        .row()
 448                        + 1;
 449                    edits.push(Edit {
 450                        old: start_row..end_row,
 451                        new: start_row..end_row,
 452                    })
 453                }
 454                false
 455            } else {
 456                true
 457            }
 458        });
 459        self.0.sync(wrap_snapshot, edits);
 460    }
 461}
 462
 463impl BlockSnapshot {
 464    #[cfg(test)]
 465    fn text(&mut self) -> String {
 466        self.chunks(0..self.transforms.summary().output_rows, None)
 467            .map(|chunk| chunk.text)
 468            .collect()
 469    }
 470
 471    pub fn chunks<'a>(
 472        &'a self,
 473        rows: Range<u32>,
 474        theme: Option<&'a SyntaxTheme>,
 475    ) -> BlockChunks<'a> {
 476        let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
 477        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
 478        let input_end = {
 479            cursor.seek(&BlockRow(rows.end), Bias::Right, &());
 480            let overshoot = if cursor
 481                .item()
 482                .map_or(false, |transform| transform.is_isomorphic())
 483            {
 484                rows.end - cursor.start().0 .0
 485            } else {
 486                0
 487            };
 488            cursor.start().1 .0 + overshoot
 489        };
 490        let input_start = {
 491            cursor.seek(&BlockRow(rows.start), Bias::Right, &());
 492            let overshoot = if cursor
 493                .item()
 494                .map_or(false, |transform| transform.is_isomorphic())
 495            {
 496                rows.start - cursor.start().0 .0
 497            } else {
 498                0
 499            };
 500            cursor.start().1 .0 + overshoot
 501        };
 502        BlockChunks {
 503            input_chunks: self.wrap_snapshot.chunks(input_start..input_end, theme),
 504            input_chunk: Default::default(),
 505            transforms: cursor,
 506            output_row: rows.start,
 507            max_output_row,
 508        }
 509    }
 510
 511    pub fn buffer_rows<'a>(&'a self, start_row: u32) -> BlockBufferRows<'a> {
 512        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
 513        cursor.seek(&BlockRow(start_row), Bias::Right, &());
 514        let (output_start, input_start) = cursor.start();
 515        let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
 516            start_row - output_start.0
 517        } else {
 518            0
 519        };
 520        let input_start_row = input_start.0 + overshoot;
 521        BlockBufferRows {
 522            transforms: cursor,
 523            input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
 524            output_row: start_row,
 525            started: false,
 526        }
 527    }
 528
 529    pub fn blocks_in_range<'a>(
 530        &'a self,
 531        rows: Range<u32>,
 532    ) -> impl Iterator<Item = (u32, &'a AlignedBlock)> {
 533        let mut cursor = self.transforms.cursor::<BlockRow>();
 534        cursor.seek(&BlockRow(rows.start), Bias::Right, &());
 535        std::iter::from_fn(move || {
 536            while let Some(transform) = cursor.item() {
 537                let start_row = cursor.start().0;
 538                if start_row >= rows.end {
 539                    break;
 540                }
 541                if let Some(block) = &transform.block {
 542                    cursor.next(&());
 543                    return Some((start_row, block));
 544                } else {
 545                    cursor.next(&());
 546                }
 547            }
 548            None
 549        })
 550    }
 551
 552    pub fn max_point(&self) -> BlockPoint {
 553        let row = self.transforms.summary().output_rows - 1;
 554        BlockPoint::new(row, self.line_len(row))
 555    }
 556
 557    pub fn longest_row(&self) -> u32 {
 558        let input_row = self.wrap_snapshot.longest_row();
 559        self.to_block_point(WrapPoint::new(input_row, 0)).row
 560    }
 561
 562    pub fn line_len(&self, row: u32) -> u32 {
 563        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
 564        cursor.seek(&BlockRow(row), Bias::Right, &());
 565        if let Some(transform) = cursor.item() {
 566            let (output_start, input_start) = cursor.start();
 567            let overshoot = row - output_start.0;
 568            if transform.block.is_some() {
 569                0
 570            } else {
 571                self.wrap_snapshot.line_len(input_start.0 + overshoot)
 572            }
 573        } else {
 574            panic!("row out of range");
 575        }
 576    }
 577
 578    pub fn is_block_line(&self, row: u32) -> bool {
 579        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
 580        cursor.seek(&BlockRow(row), Bias::Right, &());
 581        cursor.item().map_or(false, |t| t.block.is_some())
 582    }
 583
 584    pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
 585        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
 586        cursor.seek(&BlockRow(point.row), Bias::Right, &());
 587
 588        let max_input_row = WrapRow(self.transforms.summary().input_rows);
 589        let search_left =
 590            (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
 591
 592        loop {
 593            if let Some(transform) = cursor.item() {
 594                if transform.is_isomorphic() {
 595                    let (output_start_row, input_start_row) = cursor.start();
 596                    let (output_end_row, input_end_row) = cursor.end(&());
 597
 598                    if point.row >= output_end_row.0 {
 599                        return BlockPoint::new(
 600                            output_end_row.0 - 1,
 601                            self.wrap_snapshot.line_len(input_end_row.0 - 1),
 602                        );
 603                    }
 604
 605                    let output_start = Point::new(output_start_row.0, 0);
 606                    if point.0 > output_start {
 607                        let output_overshoot = point.0 - output_start;
 608                        let input_start = Point::new(input_start_row.0, 0);
 609                        let input_point = self
 610                            .wrap_snapshot
 611                            .clip_point(WrapPoint(input_start + output_overshoot), bias);
 612                        let input_overshoot = input_point.0 - input_start;
 613                        return BlockPoint(output_start + input_overshoot);
 614                    } else {
 615                        return BlockPoint(output_start);
 616                    }
 617                } else if search_left {
 618                    cursor.prev(&());
 619                } else {
 620                    cursor.next(&());
 621                }
 622            } else {
 623                return self.max_point();
 624            }
 625        }
 626    }
 627
 628    pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
 629        let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
 630        cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
 631        if let Some(transform) = cursor.item() {
 632            debug_assert!(transform.is_isomorphic());
 633        } else {
 634            return self.max_point();
 635        }
 636
 637        let (input_start_row, output_start_row) = cursor.start();
 638        let input_start = Point::new(input_start_row.0, 0);
 639        let output_start = Point::new(output_start_row.0, 0);
 640        let input_overshoot = wrap_point.0 - input_start;
 641        BlockPoint(output_start + input_overshoot)
 642    }
 643
 644    pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
 645        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
 646        cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
 647        if let Some(transform) = cursor.item() {
 648            match transform.block.as_ref().map(|b| b.disposition) {
 649                Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
 650                Some(BlockDisposition::Below) => {
 651                    let wrap_row = cursor.start().1 .0 - 1;
 652                    WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
 653                }
 654                None => {
 655                    let overshoot = block_point.row - cursor.start().0 .0;
 656                    let wrap_row = cursor.start().1 .0 + overshoot;
 657                    WrapPoint::new(wrap_row, block_point.column)
 658                }
 659            }
 660        } else {
 661            self.wrap_snapshot.max_point()
 662        }
 663    }
 664}
 665
 666impl Transform {
 667    fn isomorphic(rows: u32) -> Self {
 668        Self {
 669            summary: TransformSummary {
 670                input_rows: rows,
 671                output_rows: rows,
 672            },
 673            block: None,
 674        }
 675    }
 676
 677    fn block(block: Arc<Block>, column: u32) -> Self {
 678        Self {
 679            summary: TransformSummary {
 680                input_rows: 0,
 681                output_rows: block.height as u32,
 682            },
 683            block: Some(AlignedBlock { block, column }),
 684        }
 685    }
 686
 687    fn is_isomorphic(&self) -> bool {
 688        self.block.is_none()
 689    }
 690}
 691
 692impl<'a> Iterator for BlockChunks<'a> {
 693    type Item = Chunk<'a>;
 694
 695    fn next(&mut self) -> Option<Self::Item> {
 696        if self.output_row >= self.max_output_row {
 697            return None;
 698        }
 699
 700        let transform = self.transforms.item()?;
 701        if transform.block.is_some() {
 702            let block_start = self.transforms.start().0 .0;
 703            let mut block_end = self.transforms.end(&()).0 .0;
 704            self.transforms.next(&());
 705            if self.transforms.item().is_none() {
 706                block_end -= 1;
 707            }
 708
 709            let start_in_block = self.output_row - block_start;
 710            let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
 711            let line_count = end_in_block - start_in_block;
 712            self.output_row += line_count;
 713
 714            return Some(Chunk {
 715                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
 716                highlight_style: None,
 717                diagnostic: None,
 718            });
 719        }
 720
 721        if self.input_chunk.text.is_empty() {
 722            if let Some(input_chunk) = self.input_chunks.next() {
 723                self.input_chunk = input_chunk;
 724            } else {
 725                self.output_row += 1;
 726                if self.output_row < self.max_output_row {
 727                    self.transforms.next(&());
 728                    return Some(Chunk {
 729                        text: "\n",
 730                        ..Default::default()
 731                    });
 732                } else {
 733                    return None;
 734                }
 735            }
 736        }
 737
 738        let transform_end = self.transforms.end(&()).0 .0;
 739        let (prefix_rows, prefix_bytes) =
 740            offset_for_row(self.input_chunk.text, transform_end - self.output_row);
 741        self.output_row += prefix_rows;
 742        let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
 743        self.input_chunk.text = suffix;
 744        if self.output_row == transform_end {
 745            self.transforms.next(&());
 746        }
 747
 748        Some(Chunk {
 749            text: prefix,
 750            ..self.input_chunk
 751        })
 752    }
 753}
 754
 755impl<'a> Iterator for BlockBufferRows<'a> {
 756    type Item = Option<u32>;
 757
 758    fn next(&mut self) -> Option<Self::Item> {
 759        if self.started {
 760            self.output_row += 1;
 761        } else {
 762            self.started = true;
 763        }
 764
 765        if self.output_row >= self.transforms.end(&()).0 .0 {
 766            self.transforms.next(&());
 767        }
 768
 769        let transform = self.transforms.item()?;
 770        if transform.block.is_some() {
 771            Some(None)
 772        } else {
 773            Some(self.input_buffer_rows.next().unwrap())
 774        }
 775    }
 776}
 777
 778impl sum_tree::Item for Transform {
 779    type Summary = TransformSummary;
 780
 781    fn summary(&self) -> Self::Summary {
 782        self.summary.clone()
 783    }
 784}
 785
 786impl sum_tree::Summary for TransformSummary {
 787    type Context = ();
 788
 789    fn add_summary(&mut self, summary: &Self, _: &()) {
 790        self.input_rows += summary.input_rows;
 791        self.output_rows += summary.output_rows;
 792    }
 793}
 794
 795impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
 796    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 797        self.0 += summary.input_rows;
 798    }
 799}
 800
 801impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
 802    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
 803        self.0 += summary.output_rows;
 804    }
 805}
 806
 807impl BlockDisposition {
 808    fn is_below(&self) -> bool {
 809        matches!(self, BlockDisposition::Below)
 810    }
 811}
 812
 813impl AlignedBlock {
 814    pub fn height(&self) -> u32 {
 815        self.height as u32
 816    }
 817
 818    pub fn column(&self) -> u32 {
 819        self.column
 820    }
 821
 822    pub fn render(&self, cx: &BlockContext) -> ElementBox {
 823        self.render.lock()(cx)
 824    }
 825
 826    pub fn position(&self) -> &Anchor {
 827        &self.block.position
 828    }
 829}
 830
 831impl Deref for AlignedBlock {
 832    type Target = Block;
 833
 834    fn deref(&self) -> &Self::Target {
 835        self.block.as_ref()
 836    }
 837}
 838
 839impl Debug for Block {
 840    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 841        f.debug_struct("Block")
 842            .field("id", &self.id)
 843            .field("position", &self.position)
 844            .field("disposition", &self.disposition)
 845            .finish()
 846    }
 847}
 848
 849// Count the number of bytes prior to a target point. If the string doesn't contain the target
 850// point, return its total extent. Otherwise return the target point itself.
 851fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
 852    let mut row = 0;
 853    let mut offset = 0;
 854    for (ix, line) in s.split('\n').enumerate() {
 855        if ix > 0 {
 856            row += 1;
 857            offset += 1;
 858        }
 859        if row >= target {
 860            break;
 861        }
 862        offset += line.len() as usize;
 863    }
 864    (row, offset)
 865}
 866
 867#[cfg(test)]
 868mod tests {
 869    use super::*;
 870    use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
 871    use gpui::{elements::Empty, Element};
 872    use rand::prelude::*;
 873    use std::env;
 874    use text::RandomCharIter;
 875
 876    #[gpui::test]
 877    fn test_offset_for_row() {
 878        assert_eq!(offset_for_row("", 0), (0, 0));
 879        assert_eq!(offset_for_row("", 1), (0, 0));
 880        assert_eq!(offset_for_row("abcd", 0), (0, 0));
 881        assert_eq!(offset_for_row("abcd", 1), (0, 4));
 882        assert_eq!(offset_for_row("\n", 0), (0, 0));
 883        assert_eq!(offset_for_row("\n", 1), (1, 1));
 884        assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
 885        assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
 886        assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
 887        assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
 888    }
 889
 890    #[gpui::test]
 891    fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
 892        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
 893        let font_id = cx
 894            .font_cache()
 895            .select_font(family_id, &Default::default())
 896            .unwrap();
 897
 898        let text = "aaa\nbbb\nccc\nddd";
 899
 900        let buffer = MultiBuffer::build_simple(text, cx);
 901        let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
 902        let (fold_map, folds_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
 903        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
 904        let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx);
 905        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
 906
 907        let mut writer = block_map.write(wraps_snapshot.clone(), vec![]);
 908        writer.insert(vec![
 909            BlockProperties {
 910                position: Point::new(1, 0),
 911                height: 1,
 912                disposition: BlockDisposition::Above,
 913                render: Arc::new(|_| Empty::new().named("block 1")),
 914            },
 915            BlockProperties {
 916                position: Point::new(1, 2),
 917                height: 2,
 918                disposition: BlockDisposition::Above,
 919                render: Arc::new(|_| Empty::new().named("block 2")),
 920            },
 921            BlockProperties {
 922                position: Point::new(3, 3),
 923                height: 3,
 924                disposition: BlockDisposition::Below,
 925                render: Arc::new(|_| Empty::new().named("block 3")),
 926            },
 927        ]);
 928
 929        let mut snapshot = block_map.read(wraps_snapshot, vec![]);
 930        assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
 931
 932        let blocks = snapshot
 933            .blocks_in_range(0..8)
 934            .map(|(start_row, block)| {
 935                (
 936                    start_row..start_row + block.height(),
 937                    block.column(),
 938                    block
 939                        .render(&BlockContext { cx, anchor_x: 0. })
 940                        .name()
 941                        .unwrap()
 942                        .to_string(),
 943                )
 944            })
 945            .collect::<Vec<_>>();
 946        assert_eq!(
 947            blocks,
 948            &[
 949                (1..2, 0, "block 1".to_string()),
 950                (2..4, 2, "block 2".to_string()),
 951                (7..10, 3, "block 3".to_string()),
 952            ]
 953        );
 954
 955        assert_eq!(
 956            snapshot.to_block_point(WrapPoint::new(0, 3)),
 957            BlockPoint::new(0, 3)
 958        );
 959        assert_eq!(
 960            snapshot.to_block_point(WrapPoint::new(1, 0)),
 961            BlockPoint::new(4, 0)
 962        );
 963        assert_eq!(
 964            snapshot.to_block_point(WrapPoint::new(3, 3)),
 965            BlockPoint::new(6, 3)
 966        );
 967
 968        assert_eq!(
 969            snapshot.to_wrap_point(BlockPoint::new(0, 3)),
 970            WrapPoint::new(0, 3)
 971        );
 972        assert_eq!(
 973            snapshot.to_wrap_point(BlockPoint::new(1, 0)),
 974            WrapPoint::new(1, 0)
 975        );
 976        assert_eq!(
 977            snapshot.to_wrap_point(BlockPoint::new(3, 0)),
 978            WrapPoint::new(1, 0)
 979        );
 980        assert_eq!(
 981            snapshot.to_wrap_point(BlockPoint::new(7, 0)),
 982            WrapPoint::new(3, 3)
 983        );
 984
 985        assert_eq!(
 986            snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
 987            BlockPoint::new(0, 3)
 988        );
 989        assert_eq!(
 990            snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
 991            BlockPoint::new(4, 0)
 992        );
 993        assert_eq!(
 994            snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
 995            BlockPoint::new(0, 3)
 996        );
 997        assert_eq!(
 998            snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
 999            BlockPoint::new(4, 0)
1000        );
1001        assert_eq!(
1002            snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
1003            BlockPoint::new(4, 0)
1004        );
1005        assert_eq!(
1006            snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
1007            BlockPoint::new(4, 0)
1008        );
1009        assert_eq!(
1010            snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
1011            BlockPoint::new(6, 3)
1012        );
1013        assert_eq!(
1014            snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
1015            BlockPoint::new(6, 3)
1016        );
1017        assert_eq!(
1018            snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
1019            BlockPoint::new(6, 3)
1020        );
1021        assert_eq!(
1022            snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
1023            BlockPoint::new(6, 3)
1024        );
1025
1026        assert_eq!(
1027            snapshot.buffer_rows(0).collect::<Vec<_>>(),
1028            &[
1029                Some(0),
1030                None,
1031                None,
1032                None,
1033                Some(1),
1034                Some(2),
1035                Some(3),
1036                None,
1037                None,
1038                None
1039            ]
1040        );
1041
1042        // Insert a line break, separating two block decorations into separate lines.
1043        let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1044            buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx);
1045            buffer.snapshot(cx)
1046        });
1047
1048        let (folds_snapshot, fold_edits) =
1049            fold_map.read(buffer_snapshot, subscription.consume().into_inner());
1050        let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1051        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1052            wrap_map.sync(tabs_snapshot, tab_edits, cx)
1053        });
1054        let mut snapshot = block_map.read(wraps_snapshot, wrap_edits);
1055        assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
1056    }
1057
1058    #[gpui::test]
1059    fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) {
1060        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
1061        let font_id = cx
1062            .font_cache()
1063            .select_font(family_id, &Default::default())
1064            .unwrap();
1065
1066        let text = "one two three\nfour five six\nseven eight";
1067
1068        let buffer = MultiBuffer::build_simple(text, cx);
1069        let (_, folds_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
1070        let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
1071        let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx);
1072        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
1073
1074        let mut writer = block_map.write(wraps_snapshot.clone(), vec![]);
1075        writer.insert(vec![
1076            BlockProperties {
1077                position: Point::new(1, 12),
1078                disposition: BlockDisposition::Above,
1079                render: Arc::new(|_| Empty::new().named("block 1")),
1080                height: 1,
1081            },
1082            BlockProperties {
1083                position: Point::new(1, 1),
1084                disposition: BlockDisposition::Below,
1085                render: Arc::new(|_| Empty::new().named("block 2")),
1086                height: 1,
1087            },
1088        ]);
1089
1090        // Blocks with an 'above' disposition go above their corresponding buffer line.
1091        // Blocks with a 'below' disposition go below their corresponding buffer line.
1092        let mut snapshot = block_map.read(wraps_snapshot, vec![]);
1093        assert_eq!(
1094            snapshot.text(),
1095            "one two \nthree\n\nfour five \nsix\n\nseven \neight"
1096        );
1097    }
1098
1099    #[gpui::test(iterations = 100)]
1100    fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
1101        let operations = env::var("OPERATIONS")
1102            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1103            .unwrap_or(10);
1104
1105        let wrap_width = if rng.gen_bool(0.2) {
1106            None
1107        } else {
1108            Some(rng.gen_range(0.0..=100.0))
1109        };
1110        let tab_size = 1;
1111        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
1112        let font_id = cx
1113            .font_cache()
1114            .select_font(family_id, &Default::default())
1115            .unwrap();
1116        let font_size = 14.0;
1117
1118        log::info!("Wrap width: {:?}", wrap_width);
1119
1120        let len = rng.gen_range(0..10);
1121        let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1122        log::info!("initial buffer text: {:?}", text);
1123        let buffer = MultiBuffer::build_simple(&text, cx);
1124
1125        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1126        let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone());
1127        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
1128        let (wrap_map, wraps_snapshot) =
1129            WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx);
1130        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot);
1131        let mut expected_blocks = Vec::new();
1132
1133        for _ in 0..operations {
1134            let mut buffer_edits = Vec::new();
1135            match rng.gen_range(0..=100) {
1136                0..=19 => {
1137                    let wrap_width = if rng.gen_bool(0.2) {
1138                        None
1139                    } else {
1140                        Some(rng.gen_range(0.0..=100.0))
1141                    };
1142                    log::info!("Setting wrap width to {:?}", wrap_width);
1143                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1144                }
1145                20..=39 => {
1146                    let block_count = rng.gen_range(1..=1);
1147                    let block_properties = (0..block_count)
1148                        .map(|_| {
1149                            let buffer = buffer.read(cx).read(cx);
1150                            let position = buffer.anchor_after(
1151                                buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
1152                            );
1153
1154                            let disposition = if rng.gen() {
1155                                BlockDisposition::Above
1156                            } else {
1157                                BlockDisposition::Below
1158                            };
1159                            let height = rng.gen_range(1..5);
1160                            log::info!(
1161                                "inserting block {:?} {:?} with height {}",
1162                                disposition,
1163                                position.to_point(&buffer),
1164                                height
1165                            );
1166                            BlockProperties {
1167                                position,
1168                                height,
1169                                disposition,
1170                                render: Arc::new(|_| Empty::new().boxed()),
1171                            }
1172                        })
1173                        .collect::<Vec<_>>();
1174
1175                    let (folds_snapshot, fold_edits) =
1176                        fold_map.read(buffer_snapshot.clone(), vec![]);
1177                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1178                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1179                        wrap_map.sync(tabs_snapshot, tab_edits, cx)
1180                    });
1181                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1182                    let block_ids = block_map.insert(block_properties.clone());
1183                    for (block_id, props) in block_ids.into_iter().zip(block_properties) {
1184                        expected_blocks.push((block_id, props));
1185                    }
1186                }
1187                40..=59 if !expected_blocks.is_empty() => {
1188                    let block_count = rng.gen_range(1..=4.min(expected_blocks.len()));
1189                    let block_ids_to_remove = (0..block_count)
1190                        .map(|_| {
1191                            expected_blocks
1192                                .remove(rng.gen_range(0..expected_blocks.len()))
1193                                .0
1194                        })
1195                        .collect();
1196
1197                    let (folds_snapshot, fold_edits) =
1198                        fold_map.read(buffer_snapshot.clone(), vec![]);
1199                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1200                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1201                        wrap_map.sync(tabs_snapshot, tab_edits, cx)
1202                    });
1203                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1204                    block_map.remove(block_ids_to_remove);
1205                }
1206                _ => {
1207                    buffer.update(cx, |buffer, cx| {
1208                        let edit_count = rng.gen_range(1..=5);
1209                        let subscription = buffer.subscribe();
1210                        buffer.randomly_edit(&mut rng, edit_count, cx);
1211                        buffer_edits.extend(subscription.consume());
1212                        buffer_snapshot = buffer.snapshot(cx);
1213                        log::info!("buffer text: {:?}", buffer_snapshot.text());
1214                    });
1215                }
1216            }
1217
1218            let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits);
1219            let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1220            let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1221                wrap_map.sync(tabs_snapshot, tab_edits, cx)
1222            });
1223            let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
1224            assert_eq!(
1225                blocks_snapshot.transforms.summary().input_rows,
1226                wraps_snapshot.max_point().row() + 1
1227            );
1228            log::info!("blocks text: {:?}", blocks_snapshot.text());
1229
1230            let mut sorted_blocks = expected_blocks
1231                .iter()
1232                .cloned()
1233                .map(|(id, block)| {
1234                    let mut position = block.position.to_point(&buffer_snapshot);
1235                    let column = wraps_snapshot.from_point(position, Bias::Left).column();
1236                    match block.disposition {
1237                        BlockDisposition::Above => {
1238                            position.column = 0;
1239                        }
1240                        BlockDisposition::Below => {
1241                            position.column = buffer_snapshot.line_len(position.row);
1242                        }
1243                    };
1244                    let row = wraps_snapshot.from_point(position, Bias::Left).row();
1245                    (
1246                        id,
1247                        BlockProperties {
1248                            position: BlockPoint::new(row, column),
1249                            height: block.height,
1250                            disposition: block.disposition,
1251                            render: block.render.clone(),
1252                        },
1253                    )
1254                })
1255                .collect::<Vec<_>>();
1256            sorted_blocks
1257                .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id));
1258            let mut sorted_blocks = sorted_blocks.into_iter().peekable();
1259
1260            let mut expected_buffer_rows = Vec::new();
1261            let mut expected_text = String::new();
1262            let input_text = wraps_snapshot.text();
1263            for (row, input_line) in input_text.split('\n').enumerate() {
1264                let row = row as u32;
1265                if row > 0 {
1266                    expected_text.push('\n');
1267                }
1268
1269                let buffer_row = wraps_snapshot
1270                    .to_point(WrapPoint::new(row, 0), Bias::Left)
1271                    .row;
1272
1273                while let Some((_, block)) = sorted_blocks.peek() {
1274                    if block.position.row == row && block.disposition == BlockDisposition::Above {
1275                        let text = "\n".repeat(block.height as usize);
1276                        expected_text.push_str(&text);
1277                        for _ in 0..block.height {
1278                            expected_buffer_rows.push(None);
1279                        }
1280                        sorted_blocks.next();
1281                    } else {
1282                        break;
1283                    }
1284                }
1285
1286                let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
1287                expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
1288                expected_text.push_str(input_line);
1289
1290                while let Some((_, block)) = sorted_blocks.peek() {
1291                    if block.position.row == row && block.disposition == BlockDisposition::Below {
1292                        let text = "\n".repeat(block.height as usize);
1293                        expected_text.push_str(&text);
1294                        for _ in 0..block.height {
1295                            expected_buffer_rows.push(None);
1296                        }
1297                        sorted_blocks.next();
1298                    } else {
1299                        break;
1300                    }
1301                }
1302            }
1303
1304            let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
1305            let expected_row_count = expected_lines.len();
1306            for start_row in 0..expected_row_count {
1307                let expected_text = expected_lines[start_row..].join("\n");
1308                let actual_text = blocks_snapshot
1309                    .chunks(start_row as u32..expected_row_count as u32, None)
1310                    .map(|chunk| chunk.text)
1311                    .collect::<String>();
1312                assert_eq!(
1313                    actual_text, expected_text,
1314                    "incorrect text starting from row {}",
1315                    start_row
1316                );
1317                assert_eq!(
1318                    blocks_snapshot
1319                        .buffer_rows(start_row as u32)
1320                        .collect::<Vec<_>>(),
1321                    &expected_buffer_rows[start_row..]
1322                );
1323            }
1324
1325            let mut expected_longest_rows = Vec::new();
1326            let mut longest_line_len = -1_isize;
1327            for (row, line) in expected_lines.iter().enumerate() {
1328                let row = row as u32;
1329
1330                assert_eq!(
1331                    blocks_snapshot.line_len(row),
1332                    line.len() as u32,
1333                    "invalid line len for row {}",
1334                    row
1335                );
1336
1337                let line_char_count = line.chars().count() as isize;
1338                match line_char_count.cmp(&longest_line_len) {
1339                    Ordering::Less => {}
1340                    Ordering::Equal => expected_longest_rows.push(row),
1341                    Ordering::Greater => {
1342                        longest_line_len = line_char_count;
1343                        expected_longest_rows.clear();
1344                        expected_longest_rows.push(row);
1345                    }
1346                }
1347            }
1348
1349            let longest_row = blocks_snapshot.longest_row();
1350            assert!(
1351                expected_longest_rows.contains(&longest_row),
1352                "incorrect longest row {}. expected {:?} with length {}",
1353                longest_row,
1354                expected_longest_rows,
1355                longest_line_len,
1356            );
1357
1358            for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
1359                let wrap_point = WrapPoint::new(row, 0);
1360                let block_point = blocks_snapshot.to_block_point(wrap_point);
1361                assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
1362            }
1363
1364            let mut block_point = BlockPoint::new(0, 0);
1365            for c in expected_text.chars() {
1366                let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
1367                let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
1368
1369                assert_eq!(
1370                    blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
1371                    left_point
1372                );
1373                assert_eq!(
1374                    blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
1375                    right_point
1376                );
1377
1378                if c == '\n' {
1379                    block_point.0 += Point::new(1, 0);
1380                } else {
1381                    block_point.column += c.len_utf8() as u32;
1382                }
1383            }
1384        }
1385    }
1386}