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