block_map.rs

  1use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint};
  2use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _};
  3use gpui::{fonts::HighlightStyle, AppContext, ModelHandle};
  4use language::{Buffer, HighlightedChunk};
  5use parking_lot::Mutex;
  6use std::{
  7    cmp,
  8    collections::HashSet,
  9    iter,
 10    ops::Range,
 11    slice,
 12    sync::{
 13        atomic::{AtomicUsize, Ordering::SeqCst},
 14        Arc,
 15    },
 16};
 17use sum_tree::SumTree;
 18
 19pub struct BlockMap {
 20    buffer: ModelHandle<Buffer>,
 21    next_block_id: AtomicUsize,
 22    wrap_snapshot: Mutex<WrapSnapshot>,
 23    blocks: Vec<Arc<Block>>,
 24    transforms: Mutex<SumTree<Transform>>,
 25}
 26
 27pub struct BlockMapWriter<'a>(&'a mut BlockMap);
 28
 29pub struct BlockSnapshot {
 30    wrap_snapshot: WrapSnapshot,
 31    transforms: SumTree<Transform>,
 32}
 33
 34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
 35pub struct BlockId(usize);
 36
 37#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 38pub struct BlockPoint(pub super::Point);
 39
 40#[derive(Debug)]
 41struct Block {
 42    id: BlockId,
 43    position: Anchor,
 44    text: Rope,
 45    runs: Vec<(usize, HighlightStyle)>,
 46    disposition: BlockDisposition,
 47}
 48
 49#[derive(Clone)]
 50pub struct BlockProperties<P, T>
 51where
 52    P: Clone,
 53    T: Clone,
 54{
 55    position: P,
 56    text: T,
 57    runs: Vec<(usize, HighlightStyle)>,
 58    disposition: BlockDisposition,
 59}
 60
 61#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 62enum BlockDisposition {
 63    Above,
 64    Below,
 65}
 66
 67#[derive(Clone, Debug)]
 68struct Transform {
 69    summary: TransformSummary,
 70    block: Option<Arc<Block>>,
 71}
 72
 73#[derive(Copy, Clone, Debug, Default)]
 74struct TransformSummary {
 75    input_rows: u32,
 76    output_rows: u32,
 77}
 78
 79pub struct HighlightedChunks<'a> {
 80    transforms: sum_tree::Cursor<'a, Transform, (OutputRow, InputRow)>,
 81    input_chunks: wrap_map::HighlightedChunks<'a>,
 82    input_chunk: HighlightedChunk<'a>,
 83    block_chunks: Option<BlockChunks<'a>>,
 84    output_row: u32,
 85    max_output_row: u32,
 86}
 87
 88struct BlockChunks<'a> {
 89    chunks: rope::Chunks<'a>,
 90    runs: iter::Peekable<slice::Iter<'a, (usize, HighlightStyle)>>,
 91    chunk: Option<&'a str>,
 92    run_start: usize,
 93    offset: usize,
 94}
 95
 96#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
 97struct InputRow(u32);
 98
 99#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
100struct OutputRow(u32);
101
102impl BlockMap {
103    pub fn new(buffer: ModelHandle<Buffer>, wrap_snapshot: WrapSnapshot) -> Self {
104        Self {
105            buffer,
106            next_block_id: AtomicUsize::new(0),
107            blocks: Vec::new(),
108            transforms: Mutex::new(SumTree::from_item(
109                Transform::isomorphic(wrap_snapshot.max_point().row() + 1),
110                &(),
111            )),
112            wrap_snapshot: Mutex::new(wrap_snapshot),
113        }
114    }
115
116    pub fn read(
117        &self,
118        wrap_snapshot: WrapSnapshot,
119        edits: Vec<WrapEdit>,
120        cx: &AppContext,
121    ) -> BlockSnapshot {
122        self.apply_edits(&wrap_snapshot, edits, cx);
123        *self.wrap_snapshot.lock() = wrap_snapshot.clone();
124        BlockSnapshot {
125            wrap_snapshot,
126            transforms: self.transforms.lock().clone(),
127        }
128    }
129
130    pub fn write(
131        &mut self,
132        wrap_snapshot: WrapSnapshot,
133        edits: Vec<WrapEdit>,
134        cx: &AppContext,
135    ) -> BlockMapWriter {
136        self.apply_edits(&wrap_snapshot, edits, cx);
137        *self.wrap_snapshot.lock() = wrap_snapshot;
138        BlockMapWriter(self)
139    }
140
141    fn apply_edits(&self, wrap_snapshot: &WrapSnapshot, edits: Vec<WrapEdit>, cx: &AppContext) {
142        let buffer = self.buffer.read(cx);
143        let mut transforms = self.transforms.lock();
144        let mut new_transforms = SumTree::new();
145        let mut cursor = transforms.cursor::<InputRow>();
146        let mut edits = edits.into_iter().peekable();
147        let mut last_block_ix = 0;
148        let mut blocks_in_edit = Vec::new();
149
150        while let Some(mut edit) = edits.next() {
151            new_transforms.push_tree(
152                cursor.slice(&InputRow(edit.old.start), Bias::Left, &()),
153                &(),
154            );
155
156            let transform_start = cursor.start().0;
157            edit.new.start -= edit.old.start - transform_start;
158            edit.old.start = transform_start;
159
160            loop {
161                if edit.old.end > cursor.start().0 {
162                    cursor.seek(&InputRow(edit.old.end), Bias::Left, &());
163                    cursor.next(&());
164                    let transform_end = cursor.start().0;
165                    edit.new.end += transform_end - edit.old.end;
166                    edit.old.end = transform_end;
167                }
168
169                if let Some(next_edit) = edits.peek() {
170                    if edit.old.end >= next_edit.old.start {
171                        let delta = next_edit.new.len() as i32 - next_edit.old.len() as i32;
172                        edit.old.end = cmp::max(next_edit.old.end, edit.old.end);
173                        edit.new.end = (edit.new.end as i32 + delta) as u32;
174                        edits.next();
175                    } else {
176                        break;
177                    }
178                } else {
179                    break;
180                }
181            }
182
183            let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0));
184            let end_anchor = buffer.anchor_after(Point::new(edit.new.end, 0));
185            let start_block_ix = match self.blocks[last_block_ix..]
186                .binary_search_by(|probe| probe.position.cmp(&start_anchor, buffer).unwrap())
187            {
188                Ok(ix) | Err(ix) => last_block_ix + ix,
189            };
190            let end_block_ix = match self.blocks[start_block_ix..]
191                .binary_search_by(|probe| probe.position.cmp(&end_anchor, buffer).unwrap())
192            {
193                Ok(ix) | Err(ix) => start_block_ix + ix,
194            };
195            last_block_ix = end_block_ix;
196
197            blocks_in_edit.clear();
198            blocks_in_edit.extend(
199                self.blocks[start_block_ix..end_block_ix]
200                    .iter()
201                    .map(|block| (block.position.to_point(buffer).row, block)),
202            );
203            blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition));
204
205            for (row, block) in blocks_in_edit.iter().copied() {
206                let insertion_row = if block.disposition.is_above() {
207                    row
208                } else {
209                    row + 1
210                };
211
212                let new_transforms_end = new_transforms.summary().input_rows;
213                if new_transforms_end < insertion_row {
214                    new_transforms.push(
215                        Transform::isomorphic(insertion_row - new_transforms_end),
216                        &(),
217                    );
218                }
219
220                new_transforms.push(Transform::block(block.clone()), &());
221            }
222
223            let new_transforms_end = new_transforms.summary().input_rows;
224            if new_transforms_end < edit.new.end {
225                new_transforms.push(
226                    Transform::isomorphic(edit.new.end - new_transforms_end),
227                    &(),
228                );
229            }
230        }
231        new_transforms.push_tree(cursor.suffix(&()), &());
232        drop(cursor);
233        *transforms = new_transforms;
234    }
235}
236
237impl std::ops::Deref for BlockPoint {
238    type Target = Point;
239
240    fn deref(&self) -> &Self::Target {
241        &self.0
242    }
243}
244
245impl std::ops::DerefMut for BlockPoint {
246    fn deref_mut(&mut self) -> &mut Self::Target {
247        &mut self.0
248    }
249}
250
251impl<'a> BlockMapWriter<'a> {
252    pub fn insert<P, T>(
253        &mut self,
254        blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
255        cx: &AppContext,
256    ) -> Vec<BlockId>
257    where
258        P: ToOffset + Clone,
259        T: Into<Rope> + Clone,
260    {
261        let buffer = self.0.buffer.read(cx);
262        let mut ids = Vec::new();
263        let mut edits = Vec::<Edit<u32>>::new();
264
265        for block in blocks {
266            let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst));
267            ids.push(id);
268
269            let position = buffer.anchor_before(block.position);
270            let row = position.to_point(buffer).row;
271
272            let block_ix = match self
273                .0
274                .blocks
275                .binary_search_by(|probe| probe.position.cmp(&position, buffer).unwrap())
276            {
277                Ok(ix) | Err(ix) => ix,
278            };
279            let mut text = block.text.into();
280            text.push("\n");
281            self.0.blocks.insert(
282                block_ix,
283                Arc::new(Block {
284                    id,
285                    position,
286                    text,
287                    runs: block.runs,
288                    disposition: block.disposition,
289                }),
290            );
291
292            if let Err(edit_ix) = edits.binary_search_by_key(&row, |edit| edit.old.start) {
293                edits.insert(
294                    edit_ix,
295                    Edit {
296                        old: row..(row + 1),
297                        new: row..(row + 1),
298                    },
299                );
300            }
301        }
302
303        self.0.apply_edits(&*self.0.wrap_snapshot.lock(), edits, cx);
304        ids
305    }
306
307    pub fn remove(&mut self, _: HashSet<BlockId>, _: &AppContext) {
308        todo!()
309    }
310}
311
312impl BlockSnapshot {
313    #[cfg(test)]
314    fn text(&mut self) -> String {
315        self.highlighted_chunks_for_rows(0..(self.max_point().0.row + 1))
316            .map(|chunk| chunk.text)
317            .collect()
318    }
319
320    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
321        let mut cursor = self.transforms.cursor::<(OutputRow, InputRow)>();
322        cursor.seek(&OutputRow(rows.start), Bias::Right, &());
323        let (input_start, output_start) = cursor.start();
324        let row_overshoot = rows.start - output_start.0;
325        let input_start_row = input_start.0 + row_overshoot;
326        let input_end_row = self
327            .to_wrap_point(BlockPoint(Point::new(rows.end, 0)))
328            .row();
329        let input_chunks = self
330            .wrap_snapshot
331            .highlighted_chunks_for_rows(input_start_row..input_end_row);
332        HighlightedChunks {
333            input_chunks,
334            input_chunk: Default::default(),
335            block_chunks: None,
336            transforms: cursor,
337            output_row: rows.start,
338            max_output_row: rows.end,
339        }
340    }
341
342    pub fn max_point(&self) -> BlockPoint {
343        self.to_block_point(self.wrap_snapshot.max_point())
344    }
345
346    pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
347        todo!()
348    }
349
350    pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
351        let mut cursor = self.transforms.cursor::<(InputRow, OutputRow)>();
352        cursor.seek(&InputRow(wrap_point.row()), Bias::Right, &());
353        while let Some(item) = cursor.item() {
354            if item.is_isomorphic() {
355                break;
356            }
357            cursor.next(&());
358        }
359        let (input_start, output_start) = cursor.start();
360        let row_overshoot = wrap_point.row() - input_start.0;
361        BlockPoint(Point::new(
362            output_start.0 + row_overshoot,
363            wrap_point.column(),
364        ))
365    }
366
367    pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
368        let mut cursor = self.transforms.cursor::<(OutputRow, InputRow)>();
369        cursor.seek(&OutputRow(block_point.0.row), Bias::Right, &());
370        let (output_start, input_start) = cursor.start();
371        let row_overshoot = block_point.0.row - output_start.0;
372        WrapPoint::new(input_start.0 + row_overshoot, block_point.0.column)
373    }
374}
375
376impl Transform {
377    fn isomorphic(rows: u32) -> Self {
378        Self {
379            summary: TransformSummary {
380                input_rows: rows,
381                output_rows: rows,
382            },
383            block: None,
384        }
385    }
386
387    fn block(block: Arc<Block>) -> Self {
388        Self {
389            summary: TransformSummary {
390                input_rows: 0,
391                output_rows: block.text.summary().lines.row,
392            },
393            block: Some(block),
394        }
395    }
396
397    fn is_isomorphic(&self) -> bool {
398        self.block.is_none()
399    }
400}
401
402impl<'a> Iterator for HighlightedChunks<'a> {
403    type Item = HighlightedChunk<'a>;
404
405    fn next(&mut self) -> Option<Self::Item> {
406        if self.output_row >= self.max_output_row {
407            return None;
408        }
409
410        if let Some(block_chunks) = self.block_chunks.as_mut() {
411            if let Some(block_chunk) = block_chunks.next() {
412                self.output_row += block_chunk.text.matches('\n').count() as u32;
413                return Some(block_chunk);
414            } else {
415                self.block_chunks.take();
416            }
417        }
418
419        let transform = self.transforms.item()?;
420        if let Some(block) = transform.block.as_ref() {
421            let block_start = self.transforms.start().0 .0;
422            let block_end = self.transforms.end(&()).0 .0;
423            let start_row_in_block = self.output_row - block_start;
424            let end_row_in_block = cmp::min(self.max_output_row, block_end) - block_start;
425            self.transforms.next(&());
426            let mut block_chunks = BlockChunks::new(block, start_row_in_block..end_row_in_block);
427            if let Some(block_chunk) = block_chunks.next() {
428                self.output_row += block_chunk.text.matches('\n').count() as u32;
429                return Some(block_chunk);
430            }
431        }
432
433        if self.input_chunk.text.is_empty() {
434            self.input_chunk = self.input_chunks.next()?;
435        }
436
437        let transform_end = self.transforms.end(&()).0 .0;
438        let (prefix_rows, prefix_bytes) =
439            offset_for_row(self.input_chunk.text, transform_end - self.output_row);
440        self.output_row += prefix_rows;
441        let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
442        self.input_chunk.text = suffix;
443        if self.output_row == transform_end {
444            self.transforms.next(&());
445        }
446
447        Some(HighlightedChunk {
448            text: prefix,
449            ..self.input_chunk
450        })
451    }
452}
453
454impl<'a> BlockChunks<'a> {
455    fn new(block: &'a Block, row_range: Range<u32>) -> Self {
456        let point_range = Point::new(row_range.start, 0)..Point::new(row_range.end, 0);
457        let offset_range = block.text.point_to_offset(point_range.start)
458            ..block.text.point_to_offset(point_range.end);
459
460        let mut runs = block.runs.iter().peekable();
461        let mut run_start = 0;
462        while let Some((run_len, _)) = runs.peek() {
463            let run_end = run_start + run_len;
464            if run_end <= offset_range.start {
465                run_start = run_end;
466                runs.next();
467            } else {
468                break;
469            }
470        }
471
472        Self {
473            chunk: None,
474            run_start,
475            chunks: block.text.chunks_in_range(offset_range.clone()),
476            runs,
477            offset: offset_range.start,
478        }
479    }
480}
481
482impl<'a> Iterator for BlockChunks<'a> {
483    type Item = HighlightedChunk<'a>;
484
485    fn next(&mut self) -> Option<Self::Item> {
486        if self.chunk.is_none() {
487            self.chunk = self.chunks.next();
488        }
489
490        let chunk = self.chunk?;
491        let mut chunk_len = chunk.len();
492        let mut highlight_style = None;
493        if let Some((run_len, style)) = self.runs.peek() {
494            highlight_style = Some(style.clone());
495            let run_end_in_chunk = self.run_start + run_len - self.offset;
496            if run_end_in_chunk <= chunk_len {
497                chunk_len = run_end_in_chunk;
498                self.run_start += run_len;
499                self.runs.next();
500            }
501        }
502
503        self.offset += chunk_len;
504        let (chunk, suffix) = chunk.split_at(chunk_len);
505        self.chunk = if suffix.is_empty() {
506            None
507        } else {
508            Some(suffix)
509        };
510
511        Some(HighlightedChunk {
512            text: chunk,
513            highlight_id: Default::default(),
514            diagnostic: None,
515        })
516    }
517}
518
519impl sum_tree::Item for Transform {
520    type Summary = TransformSummary;
521
522    fn summary(&self) -> Self::Summary {
523        self.summary
524    }
525}
526
527impl sum_tree::Summary for TransformSummary {
528    type Context = ();
529
530    fn add_summary(&mut self, summary: &Self, _: &()) {
531        self.input_rows += summary.input_rows;
532        self.output_rows += summary.output_rows;
533    }
534}
535
536impl<'a> sum_tree::Dimension<'a, TransformSummary> for InputRow {
537    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
538        self.0 += summary.input_rows;
539    }
540}
541
542impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow {
543    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
544        self.0 += summary.output_rows;
545    }
546}
547
548impl BlockDisposition {
549    fn is_above(&self) -> bool {
550        matches!(self, BlockDisposition::Above)
551    }
552}
553
554// Count the number of bytes prior to a target row.
555// If the string doesn't contain the target row, return the total number of rows it does contain.
556// Otherwise return the target row itself.
557fn offset_for_row(s: &str, target_row: u32) -> (u32, usize) {
558    let mut row = 0;
559    let mut offset = 0;
560    for (ix, line) in s.split('\n').enumerate() {
561        if ix > 0 {
562            row += 1;
563            offset += 1;
564        }
565        if row as u32 >= target_row {
566            break;
567        }
568        offset += line.len();
569    }
570    (row, offset)
571}
572
573#[cfg(test)]
574mod tests {
575    use super::*;
576    use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
577    use buffer::RandomCharIter;
578    use language::Buffer;
579    use rand::prelude::*;
580    use std::env;
581
582    #[gpui::test]
583    fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
584        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
585        let font_id = cx
586            .font_cache()
587            .select_font(family_id, &Default::default())
588            .unwrap();
589
590        let text = "aaa\nbbb\nccc\nddd\n";
591
592        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
593        let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
594        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
595        let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx);
596        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
597
598        let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
599        writer.insert(
600            vec![
601                BlockProperties {
602                    position: Point::new(1, 0),
603                    text: "BLOCK 1",
604                    disposition: BlockDisposition::Above,
605                    runs: vec![],
606                },
607                BlockProperties {
608                    position: Point::new(1, 2),
609                    text: "BLOCK 2",
610                    disposition: BlockDisposition::Above,
611                    runs: vec![],
612                },
613                BlockProperties {
614                    position: Point::new(3, 2),
615                    text: "BLOCK 3",
616                    disposition: BlockDisposition::Below,
617                    runs: vec![],
618                },
619            ],
620            cx,
621        );
622
623        let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
624        assert_eq!(
625            snapshot.text(),
626            "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3\n"
627        );
628        assert_eq!(
629            snapshot.to_block_point(WrapPoint::new(1, 0)),
630            BlockPoint(Point::new(3, 0))
631        );
632
633        // Insert a line break, separating two block decorations into separate
634        // lines.
635        buffer.update(cx, |buffer, cx| {
636            buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx)
637        });
638
639        let (folds_snapshot, fold_edits) = fold_map.read(cx);
640        let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
641        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
642            wrap_map.sync(tabs_snapshot, tab_edits, cx)
643        });
644        let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx);
645        assert_eq!(
646            snapshot.text(),
647            "aaa\nBLOCK 1\nb!!!\nBLOCK 2\nbb\nccc\nddd\nBLOCK 3\n"
648        );
649    }
650
651    #[gpui::test(iterations = 100)]
652    fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
653        let operations = env::var("OPERATIONS")
654            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
655            .unwrap_or(10);
656
657        let wrap_width = Some(rng.gen_range(0.0..=1000.0));
658        let tab_size = 1;
659        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
660        let font_id = cx
661            .font_cache()
662            .select_font(family_id, &Default::default())
663            .unwrap();
664        let font_size = 14.0;
665
666        log::info!("Wrap width: {:?}", wrap_width);
667
668        let buffer = cx.add_model(|cx| {
669            let len = rng.gen_range(0..10);
670            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
671            Buffer::new(0, text, cx)
672        });
673        let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
674        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
675        let (wrap_map, wraps_snapshot) =
676            WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx);
677        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot);
678        let mut expected_blocks = Vec::new();
679
680        for _ in 0..operations {
681            match rng.gen_range(0..=100) {
682                0..=19 => {
683                    let wrap_width = if rng.gen_bool(0.2) {
684                        None
685                    } else {
686                        Some(rng.gen_range(0.0..=1000.0))
687                    };
688                    log::info!("Setting wrap width to {:?}", wrap_width);
689                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
690                }
691                20..=39 => {
692                    let block_count = rng.gen_range(1..=4);
693                    let block_properties = (0..block_count)
694                        .map(|_| {
695                            let buffer = buffer.read(cx);
696                            let position = buffer.anchor_before(rng.gen_range(0..=buffer.len()));
697
698                            let len = rng.gen_range(0..10);
699                            let text = Rope::from(
700                                RandomCharIter::new(&mut rng)
701                                    .take(len)
702                                    .collect::<String>()
703                                    .as_str(),
704                            );
705                            BlockProperties {
706                                position,
707                                text,
708                                runs: Vec::<(usize, HighlightStyle)>::new(),
709                                disposition: if rng.gen() {
710                                    BlockDisposition::Above
711                                } else {
712                                    BlockDisposition::Below
713                                },
714                            }
715                        })
716                        .collect::<Vec<_>>();
717
718                    let (folds_snapshot, fold_edits) = fold_map.read(cx);
719                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
720                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
721                        wrap_map.sync(tabs_snapshot, tab_edits, cx)
722                    });
723                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
724                    let block_ids = block_map.insert(block_properties.clone(), cx);
725                    expected_blocks.extend(block_ids.into_iter().zip(block_properties));
726                }
727                40..=59 => {
728                    let block_count = rng.gen_range(1..=4.min(expected_blocks.len()));
729                    let block_ids_to_remove = (0..block_count)
730                        .map(|_| {
731                            expected_blocks
732                                .remove(rng.gen_range(0..expected_blocks.len()))
733                                .0
734                        })
735                        .collect();
736
737                    let (folds_snapshot, fold_edits) = fold_map.read(cx);
738                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
739                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
740                        wrap_map.sync(tabs_snapshot, tab_edits, cx)
741                    });
742                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
743                    block_map.remove(block_ids_to_remove, cx);
744                }
745                _ => {
746                    buffer.update(cx, |buffer, _| buffer.randomly_edit(&mut rng, 5));
747                }
748            }
749
750            let (folds_snapshot, fold_edits) = fold_map.read(cx);
751            let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
752            let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
753                wrap_map.sync(tabs_snapshot, tab_edits, cx)
754            });
755            let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx);
756            assert_eq!(
757                blocks_snapshot.transforms.summary().input_rows,
758                wraps_snapshot.max_point().row() + 1
759            );
760
761            let buffer = buffer.read(cx);
762            let mut sorted_blocks = expected_blocks
763                .iter()
764                .cloned()
765                .map(|(_, block)| BlockProperties {
766                    position: block.position.to_point(buffer),
767                    text: block.text,
768                    runs: block.runs,
769                    disposition: block.disposition,
770                })
771                .collect::<Vec<_>>();
772            sorted_blocks.sort_unstable_by_key(|block| (block.position.row, block.disposition));
773            let mut sorted_blocks = sorted_blocks.into_iter().peekable();
774
775            let mut expected_text = String::new();
776            let input_text = wraps_snapshot.text();
777            for (row, input_line) in input_text.split('\n').enumerate() {
778                let row = row as u32;
779                if row > 0 {
780                    expected_text.push('\n');
781                }
782
783                while let Some(block) = sorted_blocks.peek() {
784                    if block.position.row == row && block.disposition == BlockDisposition::Above {
785                        expected_text.extend(block.text.chunks());
786                        expected_text.push('\n');
787                        sorted_blocks.next();
788                    } else {
789                        break;
790                    }
791                }
792
793                expected_text.push_str(input_line);
794
795                while let Some(block) = sorted_blocks.peek() {
796                    if block.position.row == row && block.disposition == BlockDisposition::Below {
797                        expected_text.push('\n');
798                        expected_text.extend(block.text.chunks());
799                        sorted_blocks.next();
800                    } else {
801                        break;
802                    }
803                }
804            }
805
806            assert_eq!(blocks_snapshot.text(), expected_text);
807        }
808    }
809}