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