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