block_map.rs

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