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