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