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