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