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