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 start_block_ix = match self.blocks[last_block_ix..]
193                .binary_search_by(|probe| probe.position.cmp(&start_anchor, buffer).unwrap())
194            {
195                Ok(ix) | Err(ix) => last_block_ix + ix,
196            };
197            let end_block_ix = if edit.new.end > wrap_snapshot.max_point().row() {
198                self.blocks.len()
199            } else {
200                let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0));
201                match self.blocks[start_block_ix..].binary_search_by(|probe| {
202                    probe
203                        .position
204                        .cmp(&end_anchor, buffer)
205                        .unwrap()
206                        .then(Ordering::Greater)
207                }) {
208                    Ok(ix) | Err(ix) => start_block_ix + ix,
209                }
210            };
211            last_block_ix = end_block_ix;
212
213            blocks_in_edit.clear();
214            blocks_in_edit.extend(
215                self.blocks[start_block_ix..end_block_ix]
216                    .iter()
217                    .map(|block| (block.position.to_point(buffer).row, block)),
218            );
219            blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition));
220            dbg!(&blocks_in_edit);
221
222            for (block_row, block) in blocks_in_edit.iter().copied() {
223                let new_transforms_end = new_transforms.summary().input;
224                if block.disposition.is_above() {
225                    if block_row > new_transforms_end.row {
226                        new_transforms.push(
227                            Transform::isomorphic(Point::new(block_row, 0) - new_transforms_end),
228                            &(),
229                        );
230                    }
231                } else {
232                    if block_row >= new_transforms_end.row {
233                        new_transforms.push(
234                            Transform::isomorphic(
235                                Point::new(block_row, wrap_snapshot.line_len(block_row))
236                                    - new_transforms_end,
237                            ),
238                            &(),
239                        );
240                    }
241                }
242
243                new_transforms.push(Transform::block(block.clone()), &());
244            }
245
246            let new_transforms_end = new_transforms.summary().input;
247            let edit_new_end_point =
248                cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0);
249            if new_transforms_end < edit_new_end_point {
250                new_transforms.push(
251                    Transform::isomorphic(edit_new_end_point - new_transforms_end),
252                    &(),
253                );
254            }
255        }
256        new_transforms.push_tree(cursor.suffix(&()), &());
257        drop(cursor);
258        *transforms = new_transforms;
259    }
260}
261
262impl BlockPoint {
263    fn new(row: u32, column: u32) -> Self {
264        Self(Point::new(row, column))
265    }
266}
267
268impl std::ops::Deref for BlockPoint {
269    type Target = Point;
270
271    fn deref(&self) -> &Self::Target {
272        &self.0
273    }
274}
275
276impl std::ops::DerefMut for BlockPoint {
277    fn deref_mut(&mut self) -> &mut Self::Target {
278        &mut self.0
279    }
280}
281
282impl<'a> BlockMapWriter<'a> {
283    pub fn insert<P, T>(
284        &mut self,
285        blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
286        cx: &AppContext,
287    ) -> Vec<BlockId>
288    where
289        P: ToOffset + Clone,
290        T: Into<Rope> + Clone,
291    {
292        let buffer = self.0.buffer.read(cx);
293        let mut ids = Vec::new();
294        let mut edits = Vec::<Edit<u32>>::new();
295
296        for block in blocks {
297            let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst));
298            ids.push(id);
299
300            let position = buffer.anchor_before(block.position);
301            let row = position.to_point(buffer).row;
302
303            let block_ix = match self
304                .0
305                .blocks
306                .binary_search_by(|probe| probe.position.cmp(&position, buffer).unwrap())
307            {
308                Ok(ix) | Err(ix) => ix,
309            };
310            let mut text = block.text.into();
311            if block.disposition.is_above() {
312                text.push("\n");
313            } else {
314                text.push_front("\n");
315            }
316
317            self.0.blocks.insert(
318                block_ix,
319                Arc::new(Block {
320                    id,
321                    position,
322                    text,
323                    runs: block.runs,
324                    disposition: block.disposition,
325                }),
326            );
327
328            if let Err(edit_ix) = edits.binary_search_by_key(&row, |edit| edit.old.start) {
329                edits.insert(
330                    edit_ix,
331                    Edit {
332                        old: row..(row + 1),
333                        new: row..(row + 1),
334                    },
335                );
336            }
337        }
338
339        self.0.apply_edits(&*self.0.wrap_snapshot.lock(), edits, cx);
340        ids
341    }
342
343    pub fn remove(&mut self, _: HashSet<BlockId>, _: &AppContext) {
344        todo!()
345    }
346}
347
348impl BlockSnapshot {
349    #[cfg(test)]
350    fn text(&mut self) -> String {
351        self.highlighted_chunks_for_rows(0..self.max_point().0.row + 1)
352            .map(|chunk| chunk.text)
353            .collect()
354    }
355
356    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
357        let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>();
358        let output_position = BlockPoint::new(rows.start, 0);
359        cursor.seek(&output_position, Bias::Right, &());
360        let (input_start, output_start) = cursor.start();
361        let row_overshoot = rows.start - output_start.0.row;
362        let input_start_row = input_start.0.row + row_overshoot;
363        let input_end_row = self.to_wrap_point(BlockPoint::new(rows.end, 0)).row();
364        let input_chunks = self
365            .wrap_snapshot
366            .highlighted_chunks_for_rows(input_start_row..input_end_row);
367        HighlightedChunks {
368            input_chunks,
369            input_chunk: Default::default(),
370            block_chunks: None,
371            transforms: cursor,
372            output_position,
373            max_output_row: rows.end,
374        }
375    }
376
377    pub fn max_point(&self) -> BlockPoint {
378        BlockPoint(self.transforms.summary().output)
379    }
380
381    pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
382        let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>();
383        cursor.seek(&point, Bias::Right, &());
384        if let Some(transform) = cursor.item() {
385            if transform.is_isomorphic() {
386                let (output_start, input_start) = cursor.start();
387                let output_overshoot = point.0 - output_start.0;
388                let input_point = self
389                    .wrap_snapshot
390                    .clip_point(WrapPoint(input_start.0 + output_overshoot), bias);
391                let input_overshoot = input_point.0 - input_start.0;
392                BlockPoint(output_start.0 + input_overshoot)
393            } else {
394                if bias == Bias::Left && cursor.start().1 .0 > Point::zero()
395                    || cursor.end(&()).1 == self.wrap_snapshot.max_point()
396                {
397                    loop {
398                        cursor.prev(&());
399                        let transform = cursor.item().unwrap();
400                        if transform.is_isomorphic() {
401                            return BlockPoint(cursor.end(&()).0 .0);
402                        }
403                    }
404                } else {
405                    loop {
406                        cursor.next(&());
407                        let transform = cursor.item().unwrap();
408                        if transform.is_isomorphic() {
409                            return BlockPoint(cursor.start().0 .0);
410                        }
411                    }
412                }
413            }
414        } else {
415            self.max_point()
416        }
417    }
418
419    pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
420        let mut cursor = self.transforms.cursor::<(WrapPoint, BlockPoint)>();
421        cursor.seek(&wrap_point, Bias::Right, &());
422        while let Some(item) = cursor.item() {
423            if item.is_isomorphic() {
424                break;
425            }
426            cursor.next(&());
427        }
428        let (input_start, output_start) = cursor.start();
429        let input_overshoot = wrap_point.0 - input_start.0;
430        BlockPoint(output_start.0 + input_overshoot)
431    }
432
433    pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
434        let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>();
435        cursor.seek(&block_point, Bias::Right, &());
436        let (output_start, input_start) = cursor.start();
437        let output_overshoot = block_point.0 - output_start.0;
438        WrapPoint(input_start.0 + output_overshoot)
439    }
440}
441
442impl Transform {
443    fn isomorphic(lines: Point) -> Self {
444        Self {
445            summary: TransformSummary {
446                input: lines,
447                output: lines,
448            },
449            block: None,
450        }
451    }
452
453    fn block(block: Arc<Block>) -> Self {
454        Self {
455            summary: TransformSummary {
456                input: Default::default(),
457                output: block.text.summary().lines,
458            },
459            block: Some(block),
460        }
461    }
462
463    fn is_isomorphic(&self) -> bool {
464        self.block.is_none()
465    }
466
467    fn block_disposition(&self) -> Option<BlockDisposition> {
468        self.block.as_ref().map(|b| b.disposition)
469    }
470}
471
472impl<'a> Iterator for HighlightedChunks<'a> {
473    type Item = HighlightedChunk<'a>;
474
475    fn next(&mut self) -> Option<Self::Item> {
476        if self.output_position.row >= self.max_output_row {
477            return None;
478        }
479
480        if let Some(block_chunks) = self.block_chunks.as_mut() {
481            if let Some(block_chunk) = block_chunks.next() {
482                self.output_position.0 += Point::from_str(block_chunk.text);
483                return Some(block_chunk);
484            } else {
485                self.block_chunks.take();
486            }
487        }
488
489        let transform = self.transforms.item()?;
490        if let Some(block) = transform.block.as_ref() {
491            let block_start = self.transforms.start().0 .0;
492            let block_end = self.transforms.end(&()).0 .0;
493            let start_in_block = self.output_position.0 - block_start;
494            let end_in_block =
495                cmp::min(Point::new(self.max_output_row, 0), block_end) - block_start;
496            self.transforms.next(&());
497            let mut block_chunks = BlockChunks::new(block, start_in_block..end_in_block);
498            if let Some(block_chunk) = block_chunks.next() {
499                self.output_position.0 += Point::from_str(block_chunk.text);
500                return Some(block_chunk);
501            }
502        }
503
504        if self.input_chunk.text.is_empty() {
505            if let Some(input_chunk) = self.input_chunks.next() {
506                self.input_chunk = input_chunk;
507            }
508        }
509
510        let transform_end = self.transforms.end(&()).0 .0;
511        let (prefix_lines, prefix_bytes) = offset_for_point(
512            self.input_chunk.text,
513            transform_end - self.output_position.0,
514        );
515        self.output_position.0 += prefix_lines;
516        let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
517        self.input_chunk.text = suffix;
518        if self.output_position.0 == transform_end {
519            self.transforms.next(&());
520        }
521
522        Some(HighlightedChunk {
523            text: prefix,
524            ..self.input_chunk
525        })
526    }
527}
528
529impl<'a> BlockChunks<'a> {
530    fn new(block: &'a Block, point_range: Range<Point>) -> Self {
531        let offset_range = block.text.point_to_offset(point_range.start)
532            ..block.text.point_to_offset(point_range.end);
533
534        let mut runs = block.runs.iter().peekable();
535        let mut run_start = 0;
536        while let Some((run_len, _)) = runs.peek() {
537            let run_end = run_start + run_len;
538            if run_end <= offset_range.start {
539                run_start = run_end;
540                runs.next();
541            } else {
542                break;
543            }
544        }
545
546        Self {
547            chunk: None,
548            run_start,
549            chunks: block.text.chunks_in_range(offset_range.clone()),
550            runs,
551            offset: offset_range.start,
552        }
553    }
554}
555
556impl<'a> Iterator for BlockChunks<'a> {
557    type Item = HighlightedChunk<'a>;
558
559    fn next(&mut self) -> Option<Self::Item> {
560        if self.chunk.is_none() {
561            self.chunk = self.chunks.next();
562        }
563
564        let chunk = self.chunk?;
565        let mut chunk_len = chunk.len();
566        // let mut highlight_style = None;
567        if let Some((run_len, _)) = self.runs.peek() {
568            // highlight_style = Some(style.clone());
569            let run_end_in_chunk = self.run_start + run_len - self.offset;
570            if run_end_in_chunk <= chunk_len {
571                chunk_len = run_end_in_chunk;
572                self.run_start += run_len;
573                self.runs.next();
574            }
575        }
576
577        self.offset += chunk_len;
578        let (chunk, suffix) = chunk.split_at(chunk_len);
579        self.chunk = if suffix.is_empty() {
580            None
581        } else {
582            Some(suffix)
583        };
584
585        Some(HighlightedChunk {
586            text: chunk,
587            highlight_id: Default::default(),
588            diagnostic: None,
589        })
590    }
591}
592
593impl sum_tree::Item for Transform {
594    type Summary = TransformSummary;
595
596    fn summary(&self) -> Self::Summary {
597        self.summary.clone()
598    }
599}
600
601impl sum_tree::Summary for TransformSummary {
602    type Context = ();
603
604    fn add_summary(&mut self, summary: &Self, _: &()) {
605        self.input += summary.input;
606        self.output += summary.output;
607    }
608}
609
610impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
611    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
612        self.0 += summary.input;
613    }
614}
615
616impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockPoint {
617    fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
618        self.0 += summary.output;
619    }
620}
621
622impl BlockDisposition {
623    fn is_above(&self) -> bool {
624        matches!(self, BlockDisposition::Above)
625    }
626}
627
628// Count the number of bytes prior to a target point. If the string doesn't contain the target
629// point, return its total extent. Otherwise return the target point itself.
630fn offset_for_point(s: &str, target: Point) -> (Point, usize) {
631    let mut point = Point::zero();
632    let mut offset = 0;
633    for (row, line) in s.split('\n').enumerate().take(target.row as usize + 1) {
634        let row = row as u32;
635        if row > 0 {
636            offset += 1;
637        }
638        point.row = row;
639        point.column = if row == target.row {
640            cmp::min(line.len() as u32, target.column)
641        } else {
642            line.len() as u32
643        };
644        offset += point.column as usize;
645    }
646    (point, offset)
647}
648
649#[cfg(test)]
650mod tests {
651    use super::*;
652    use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
653    use buffer::RandomCharIter;
654    use language::Buffer;
655    use rand::prelude::*;
656    use std::env;
657
658    #[gpui::test]
659    fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
660        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
661        let font_id = cx
662            .font_cache()
663            .select_font(family_id, &Default::default())
664            .unwrap();
665
666        let text = "aaa\nbbb\nccc\nddd";
667
668        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
669        let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx);
670        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
671        let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx);
672        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
673
674        let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
675        writer.insert(
676            vec![
677                BlockProperties {
678                    position: Point::new(1, 0),
679                    text: "BLOCK 1",
680                    disposition: BlockDisposition::Above,
681                    runs: vec![],
682                },
683                BlockProperties {
684                    position: Point::new(1, 2),
685                    text: "BLOCK 2",
686                    disposition: BlockDisposition::Above,
687                    runs: vec![],
688                },
689                BlockProperties {
690                    position: Point::new(3, 2),
691                    text: "BLOCK 3",
692                    disposition: BlockDisposition::Below,
693                    runs: vec![],
694                },
695            ],
696            cx,
697        );
698
699        let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
700        assert_eq!(
701            snapshot.text(),
702            "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3"
703        );
704        assert_eq!(
705            snapshot.to_block_point(WrapPoint::new(1, 0)),
706            BlockPoint::new(3, 0)
707        );
708
709        // Insert a line break, separating two block decorations into separate
710        // lines.
711        buffer.update(cx, |buffer, cx| {
712            buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx)
713        });
714
715        let (folds_snapshot, fold_edits) = fold_map.read(cx);
716        let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
717        let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
718            wrap_map.sync(tabs_snapshot, tab_edits, cx)
719        });
720        let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx);
721        assert_eq!(
722            snapshot.text(),
723            "aaa\nBLOCK 1\nb!!!\nBLOCK 2\nbb\nccc\nddd\nBLOCK 3"
724        );
725    }
726
727    #[gpui::test(iterations = 100)]
728    fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
729        let operations = env::var("OPERATIONS")
730            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
731            .unwrap_or(10);
732
733        let wrap_width = Some(rng.gen_range(0.0..=1000.0));
734        let tab_size = 1;
735        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
736        let font_id = cx
737            .font_cache()
738            .select_font(family_id, &Default::default())
739            .unwrap();
740        let font_size = 14.0;
741
742        log::info!("Wrap width: {:?}", wrap_width);
743
744        let buffer = cx.add_model(|cx| {
745            let len = rng.gen_range(0..10);
746            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
747            log::info!("initial buffer text: {:?}", text);
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}