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