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