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