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