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