block_map.rs

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