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