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