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