1use super::{
2 buffer::{self, AnchorRangeExt},
3 Anchor, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset,
4};
5use crate::{
6 sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
7 time,
8};
9use anyhow::{anyhow, Result};
10use gpui::{AppContext, ModelHandle};
11use parking_lot::{Mutex, MutexGuard};
12use std::{
13 cmp::{self, Ordering},
14 iter::Take,
15 ops::Range,
16};
17
18pub struct FoldMap {
19 buffer: ModelHandle<Buffer>,
20 transforms: Mutex<SumTree<Transform>>,
21 folds: SumTree<Fold>,
22 last_sync: Mutex<time::Global>,
23}
24
25impl FoldMap {
26 pub fn new(buffer_handle: ModelHandle<Buffer>, ctx: &AppContext) -> Self {
27 let buffer = buffer_handle.read(ctx);
28 let text_summary = buffer.text_summary();
29 Self {
30 buffer: buffer_handle,
31 folds: Default::default(),
32 transforms: Mutex::new(SumTree::from_item(
33 Transform {
34 summary: TransformSummary {
35 buffer: text_summary.clone(),
36 display: text_summary,
37 },
38 display_text: None,
39 },
40 &(),
41 )),
42 last_sync: Mutex::new(buffer.version()),
43 }
44 }
45
46 pub fn snapshot(&self, ctx: &AppContext) -> FoldMapSnapshot {
47 FoldMapSnapshot {
48 transforms: self.sync(ctx).clone(),
49 buffer: self.buffer.clone(),
50 }
51 }
52
53 pub fn len(&self, ctx: &AppContext) -> usize {
54 self.sync(ctx).summary().display.chars
55 }
56
57 pub fn line_len(&self, row: u32, ctx: &AppContext) -> Result<u32> {
58 let line_start = self.to_display_offset(DisplayPoint::new(row, 0), ctx)?.0;
59 let line_end = if row >= self.max_point(ctx).row() {
60 self.len(ctx)
61 } else {
62 self.to_display_offset(DisplayPoint::new(row + 1, 0), ctx)?
63 .0
64 - 1
65 };
66
67 Ok((line_end - line_start) as u32)
68 }
69
70 pub fn max_point(&self, ctx: &AppContext) -> DisplayPoint {
71 DisplayPoint(self.sync(ctx).summary().display.lines)
72 }
73
74 pub fn rightmost_point(&self, ctx: &AppContext) -> DisplayPoint {
75 DisplayPoint(self.sync(ctx).summary().display.rightmost_point)
76 }
77
78 pub fn folds_in_range<'a, T>(
79 &'a self,
80 range: Range<T>,
81 ctx: &'a AppContext,
82 ) -> Result<impl Iterator<Item = &'a Range<Anchor>>>
83 where
84 T: ToOffset,
85 {
86 Ok(self.intersecting_folds(range, ctx)?.map(|f| &f.0))
87 }
88
89 pub fn fold<T: ToOffset>(
90 &mut self,
91 ranges: impl IntoIterator<Item = Range<T>>,
92 ctx: &AppContext,
93 ) -> Result<()> {
94 let _ = self.sync(ctx);
95
96 let mut edits = Vec::new();
97 let buffer = self.buffer.read(ctx);
98 for range in ranges.into_iter() {
99 let range = range.start.to_offset(buffer)?..range.end.to_offset(buffer)?;
100 if range.start == range.end {
101 continue;
102 }
103
104 let fold = Fold(buffer.anchor_after(range.start)?..buffer.anchor_before(range.end)?);
105 edits.push(Edit {
106 old_range: range.clone(),
107 new_range: range.clone(),
108 });
109 self.folds = {
110 let mut new_tree = SumTree::new();
111 let mut cursor = self.folds.cursor::<_, ()>();
112 new_tree.push_tree(cursor.slice(&fold, SeekBias::Right, buffer), buffer);
113 new_tree.push(fold, buffer);
114 new_tree.push_tree(cursor.suffix(buffer), buffer);
115 new_tree
116 };
117 }
118 edits.sort_unstable_by(|a, b| {
119 a.old_range
120 .start
121 .cmp(&b.old_range.start)
122 .then_with(|| b.old_range.end.cmp(&a.old_range.end))
123 });
124
125 self.apply_edits(edits, ctx);
126 Ok(())
127 }
128
129 pub fn unfold<T: ToOffset>(
130 &mut self,
131 ranges: impl IntoIterator<Item = Range<T>>,
132 ctx: &AppContext,
133 ) -> Result<()> {
134 let _ = self.sync(ctx);
135
136 let buffer = self.buffer.read(ctx);
137
138 let mut edits = Vec::new();
139 let mut fold_ixs_to_delete = Vec::new();
140 for range in ranges.into_iter() {
141 // Remove intersecting folds and add their ranges to edits that are passed to apply_edits
142 let mut folds_cursor = self.intersecting_folds(range, ctx)?;
143 while let Some(fold) = folds_cursor.item() {
144 let offset_range =
145 fold.0.start.to_offset(buffer).unwrap()..fold.0.end.to_offset(buffer).unwrap();
146 edits.push(Edit {
147 old_range: offset_range.clone(),
148 new_range: offset_range,
149 });
150 fold_ixs_to_delete.push(*folds_cursor.start());
151 folds_cursor.next();
152 }
153 }
154 fold_ixs_to_delete.sort_unstable();
155 fold_ixs_to_delete.dedup();
156
157 self.folds = {
158 let mut cursor = self.folds.cursor::<_, ()>();
159 let mut folds = SumTree::new();
160 for fold_ix in fold_ixs_to_delete {
161 folds.push_tree(cursor.slice(&fold_ix, SeekBias::Right, buffer), buffer);
162 cursor.next();
163 }
164 folds.push_tree(cursor.suffix(buffer), buffer);
165 folds
166 };
167
168 self.apply_edits(edits, ctx);
169 Ok(())
170 }
171
172 fn intersecting_folds<'a, T>(
173 &self,
174 range: Range<T>,
175 ctx: &'a AppContext,
176 ) -> Result<FilterCursor<impl 'a + Fn(&FoldSummary) -> bool, Fold, usize>>
177 where
178 T: ToOffset,
179 {
180 let buffer = self.buffer.read(ctx);
181 let start = buffer.anchor_before(range.start.to_offset(buffer)?)?;
182 let end = buffer.anchor_after(range.end.to_offset(buffer)?)?;
183 Ok(self.folds.filter::<_, usize>(move |summary| {
184 start.cmp(&summary.max_end, buffer).unwrap() <= Ordering::Equal
185 && end.cmp(&summary.min_start, buffer).unwrap() >= Ordering::Equal
186 }))
187 }
188
189 pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
190 let transforms = self.sync(ctx);
191 let mut cursor = transforms.cursor::<DisplayPoint, DisplayPoint>();
192 cursor.seek(&DisplayPoint::new(display_row, 0), SeekBias::Right, &());
193 while let Some(transform) = cursor.item() {
194 if transform.display_text.is_some() {
195 return true;
196 }
197 if cursor.end().row() == display_row {
198 cursor.next()
199 } else {
200 break;
201 }
202 }
203 false
204 }
205
206 pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> Result<usize> {
207 let transforms = self.sync(ctx);
208 let mut cursor = transforms.cursor::<DisplayPoint, TransformSummary>();
209 cursor.seek(&point, SeekBias::Right, &());
210 let overshoot = point.0 - cursor.start().display.lines;
211 (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx))
212 }
213
214 pub fn to_display_offset(
215 &self,
216 point: DisplayPoint,
217 ctx: &AppContext,
218 ) -> Result<DisplayOffset> {
219 self.snapshot(ctx).to_display_offset(point, ctx)
220 }
221
222 pub fn to_buffer_point(&self, display_point: DisplayPoint, ctx: &AppContext) -> Point {
223 let transforms = self.sync(ctx);
224 let mut cursor = transforms.cursor::<DisplayPoint, TransformSummary>();
225 cursor.seek(&display_point, SeekBias::Right, &());
226 let overshoot = display_point.0 - cursor.start().display.lines;
227 cursor.start().buffer.lines + overshoot
228 }
229
230 pub fn to_display_point(&self, point: Point, ctx: &AppContext) -> DisplayPoint {
231 let transforms = self.sync(ctx);
232 let mut cursor = transforms.cursor::<Point, TransformSummary>();
233 cursor.seek(&point, SeekBias::Right, &());
234 let overshoot = point - cursor.start().buffer.lines;
235 DisplayPoint(cmp::min(
236 cursor.start().display.lines + overshoot,
237 cursor.end().display.lines,
238 ))
239 }
240
241 fn sync(&self, ctx: &AppContext) -> MutexGuard<SumTree<Transform>> {
242 let buffer = self.buffer.read(ctx);
243 let mut edits = buffer.edits_since(self.last_sync.lock().clone()).peekable();
244 if edits.peek().is_some() {
245 self.apply_edits(edits, ctx);
246 }
247 *self.last_sync.lock() = buffer.version();
248 self.transforms.lock()
249 }
250
251 fn apply_edits(&self, edits: impl IntoIterator<Item = Edit>, ctx: &AppContext) {
252 let buffer = self.buffer.read(ctx);
253 let mut edits = edits.into_iter().peekable();
254
255 let mut new_transforms = SumTree::new();
256 let mut transforms = self.transforms.lock();
257 let mut cursor = transforms.cursor::<usize, usize>();
258 cursor.seek(&0, SeekBias::Right, &());
259
260 while let Some(mut edit) = edits.next() {
261 new_transforms.push_tree(
262 cursor.slice(&edit.old_range.start, SeekBias::Left, &()),
263 &(),
264 );
265 edit.new_range.start -= edit.old_range.start - cursor.start();
266 edit.old_range.start = *cursor.start();
267
268 cursor.seek(&edit.old_range.end, SeekBias::Right, &());
269 cursor.next();
270
271 let mut delta = edit.delta();
272 loop {
273 edit.old_range.end = *cursor.start();
274
275 if let Some(next_edit) = edits.peek() {
276 if next_edit.old_range.start > edit.old_range.end {
277 break;
278 }
279
280 let next_edit = edits.next().unwrap();
281 delta += next_edit.delta();
282
283 if next_edit.old_range.end >= edit.old_range.end {
284 edit.old_range.end = next_edit.old_range.end;
285 cursor.seek(&edit.old_range.end, SeekBias::Right, &());
286 cursor.next();
287 }
288 } else {
289 break;
290 }
291 }
292
293 edit.new_range.end =
294 ((edit.new_range.start + edit.old_extent()) as isize + delta) as usize;
295
296 let anchor = buffer.anchor_before(edit.new_range.start).unwrap();
297 let mut folds_cursor = self.folds.cursor::<_, ()>();
298 folds_cursor.seek(&Fold(anchor..Anchor::End), SeekBias::Left, buffer);
299 let mut folds = folds_cursor
300 .map(|f| f.0.start.to_offset(buffer).unwrap()..f.0.end.to_offset(buffer).unwrap())
301 .peekable();
302
303 while folds
304 .peek()
305 .map_or(false, |fold| fold.start < edit.new_range.end)
306 {
307 let mut fold = folds.next().unwrap();
308 let sum = new_transforms.summary();
309
310 assert!(fold.start >= sum.buffer.chars);
311
312 while folds
313 .peek()
314 .map_or(false, |next_fold| next_fold.start <= fold.end)
315 {
316 let next_fold = folds.next().unwrap();
317 if next_fold.end > fold.end {
318 fold.end = next_fold.end;
319 }
320 }
321
322 if fold.start > sum.buffer.chars {
323 let text_summary = buffer.text_summary_for_range(sum.buffer.chars..fold.start);
324 new_transforms.push(
325 Transform {
326 summary: TransformSummary {
327 display: text_summary.clone(),
328 buffer: text_summary,
329 },
330 display_text: None,
331 },
332 &(),
333 );
334 }
335
336 if fold.end > fold.start {
337 new_transforms.push(
338 Transform {
339 summary: TransformSummary {
340 display: TextSummary {
341 chars: 1,
342 bytes: '…'.len_utf8(),
343 lines: Point::new(0, 1),
344 first_line_len: 1,
345 rightmost_point: Point::new(0, 1),
346 },
347 buffer: buffer.text_summary_for_range(fold.start..fold.end),
348 },
349 display_text: Some('…'),
350 },
351 &(),
352 );
353 }
354 }
355
356 let sum = new_transforms.summary();
357 if sum.buffer.chars < edit.new_range.end {
358 let text_summary =
359 buffer.text_summary_for_range(sum.buffer.chars..edit.new_range.end);
360 new_transforms.push(
361 Transform {
362 summary: TransformSummary {
363 display: text_summary.clone(),
364 buffer: text_summary,
365 },
366 display_text: None,
367 },
368 &(),
369 );
370 }
371 }
372
373 new_transforms.push_tree(cursor.suffix(&()), &());
374 if new_transforms.is_empty() {
375 let text_summary = buffer.text_summary();
376 new_transforms.push(
377 Transform {
378 summary: TransformSummary {
379 display: text_summary.clone(),
380 buffer: text_summary,
381 },
382 display_text: None,
383 },
384 &(),
385 );
386 }
387
388 drop(cursor);
389 *transforms = new_transforms;
390 }
391}
392
393pub struct FoldMapSnapshot {
394 transforms: SumTree<Transform>,
395 buffer: ModelHandle<Buffer>,
396}
397
398impl FoldMapSnapshot {
399 pub fn buffer_rows(&self, start_row: u32) -> Result<BufferRows> {
400 if start_row > self.transforms.summary().display.lines.row {
401 return Err(anyhow!("invalid display row {}", start_row));
402 }
403
404 let display_point = Point::new(start_row, 0);
405 let mut cursor = self.transforms.cursor();
406 cursor.seek(&DisplayPoint(display_point), SeekBias::Left, &());
407
408 Ok(BufferRows {
409 display_point,
410 cursor,
411 })
412 }
413
414 pub fn chars_at<'a>(&'a self, point: DisplayPoint, ctx: &'a AppContext) -> Result<Chars<'a>> {
415 let offset = self.to_display_offset(point, ctx)?;
416 let mut cursor = self.transforms.cursor();
417 cursor.seek(&offset, SeekBias::Right, &());
418 Ok(Chars {
419 cursor,
420 offset: offset.0,
421 buffer: self.buffer.read(ctx),
422 buffer_chars: None,
423 })
424 }
425
426 fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> Result<DisplayOffset> {
427 let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
428 cursor.seek(&point, SeekBias::Right, &());
429 let overshoot = point.0 - cursor.start().display.lines;
430 let mut offset = cursor.start().display.chars;
431 if !overshoot.is_zero() {
432 let transform = cursor
433 .item()
434 .ok_or_else(|| anyhow!("display point {:?} is out of range", point))?;
435 assert!(transform.display_text.is_none());
436 let end_buffer_offset =
437 (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx))?;
438 offset += end_buffer_offset - cursor.start().buffer.chars;
439 }
440 Ok(DisplayOffset(offset))
441 }
442}
443
444#[derive(Clone, Debug, Default, Eq, PartialEq)]
445struct Transform {
446 summary: TransformSummary,
447 display_text: Option<char>,
448}
449
450#[derive(Clone, Debug, Default, Eq, PartialEq)]
451struct TransformSummary {
452 display: TextSummary,
453 buffer: TextSummary,
454}
455
456impl sum_tree::Item for Transform {
457 type Summary = TransformSummary;
458
459 fn summary(&self) -> Self::Summary {
460 self.summary.clone()
461 }
462}
463
464impl sum_tree::Summary for TransformSummary {
465 type Context = ();
466
467 fn add_summary(&mut self, other: &Self, _: &()) {
468 self.buffer += &other.buffer;
469 self.display += &other.display;
470 }
471}
472
473impl<'a> sum_tree::Dimension<'a, TransformSummary> for TransformSummary {
474 fn add_summary(&mut self, summary: &'a TransformSummary) {
475 sum_tree::Summary::add_summary(self, summary, &());
476 }
477}
478
479#[derive(Clone, Debug)]
480struct Fold(Range<Anchor>);
481
482impl Default for Fold {
483 fn default() -> Self {
484 Self(Anchor::Start..Anchor::End)
485 }
486}
487
488impl sum_tree::Item for Fold {
489 type Summary = FoldSummary;
490
491 fn summary(&self) -> Self::Summary {
492 FoldSummary {
493 start: self.0.start.clone(),
494 end: self.0.end.clone(),
495 min_start: self.0.start.clone(),
496 max_end: self.0.end.clone(),
497 count: 1,
498 }
499 }
500}
501
502#[derive(Clone, Debug)]
503struct FoldSummary {
504 start: Anchor,
505 end: Anchor,
506 min_start: Anchor,
507 max_end: Anchor,
508 count: usize,
509}
510
511impl Default for FoldSummary {
512 fn default() -> Self {
513 Self {
514 start: Anchor::Start,
515 end: Anchor::End,
516 min_start: Anchor::End,
517 max_end: Anchor::Start,
518 count: 0,
519 }
520 }
521}
522
523impl sum_tree::Summary for FoldSummary {
524 type Context = Buffer;
525
526 fn add_summary(&mut self, other: &Self, buffer: &Buffer) {
527 if other.min_start.cmp(&self.min_start, buffer).unwrap() == Ordering::Less {
528 self.min_start = other.min_start.clone();
529 }
530 if other.max_end.cmp(&self.max_end, buffer).unwrap() == Ordering::Greater {
531 self.max_end = other.max_end.clone();
532 }
533
534 #[cfg(debug_assertions)]
535 {
536 let start_comparison = self.start.cmp(&other.start, buffer).unwrap();
537 assert!(start_comparison <= Ordering::Equal);
538 if start_comparison == Ordering::Equal {
539 assert!(self.end.cmp(&other.end, buffer).unwrap() >= Ordering::Equal);
540 }
541 }
542 self.start = other.start.clone();
543 self.end = other.end.clone();
544 self.count += other.count;
545 }
546}
547
548impl<'a> sum_tree::Dimension<'a, FoldSummary> for Fold {
549 fn add_summary(&mut self, summary: &'a FoldSummary) {
550 self.0.start = summary.start.clone();
551 self.0.end = summary.end.clone();
552 }
553}
554
555impl<'a> sum_tree::SeekDimension<'a, FoldSummary> for Fold {
556 fn cmp(&self, other: &Self, buffer: &Buffer) -> Ordering {
557 self.0.cmp(&other.0, buffer).unwrap()
558 }
559}
560
561impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
562 fn add_summary(&mut self, summary: &'a FoldSummary) {
563 *self += summary.count;
564 }
565}
566
567pub struct BufferRows<'a> {
568 cursor: Cursor<'a, Transform, DisplayPoint, TransformSummary>,
569 display_point: Point,
570}
571
572impl<'a> Iterator for BufferRows<'a> {
573 type Item = u32;
574
575 fn next(&mut self) -> Option<Self::Item> {
576 while self.display_point > self.cursor.end().display.lines {
577 self.cursor.next();
578 if self.cursor.item().is_none() {
579 // TODO: Return a bool from next?
580 break;
581 }
582 }
583
584 if self.cursor.item().is_some() {
585 let overshoot = self.display_point - self.cursor.start().display.lines;
586 let buffer_point = self.cursor.start().buffer.lines + overshoot;
587 self.display_point.row += 1;
588 Some(buffer_point.row)
589 } else {
590 None
591 }
592 }
593}
594
595pub struct Chars<'a> {
596 cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
597 offset: usize,
598 buffer: &'a Buffer,
599 buffer_chars: Option<Take<buffer::CharIter<'a>>>,
600}
601
602impl<'a> Iterator for Chars<'a> {
603 type Item = char;
604
605 fn next(&mut self) -> Option<Self::Item> {
606 if let Some(c) = self.buffer_chars.as_mut().and_then(|chars| chars.next()) {
607 self.offset += 1;
608 return Some(c);
609 }
610
611 while self.offset == self.cursor.end().display.chars && self.cursor.item().is_some() {
612 self.cursor.next();
613 }
614
615 self.cursor.item().and_then(|transform| {
616 if let Some(c) = transform.display_text {
617 self.offset += 1;
618 Some(c)
619 } else {
620 let overshoot = self.offset - self.cursor.start().display.chars;
621 let buffer_start = self.cursor.start().buffer.chars + overshoot;
622 let char_count = self.cursor.end().buffer.chars - buffer_start;
623 self.buffer_chars =
624 Some(self.buffer.chars_at(buffer_start).unwrap().take(char_count));
625 self.next()
626 }
627 })
628 }
629}
630
631impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayPoint {
632 fn add_summary(&mut self, summary: &'a TransformSummary) {
633 self.0 += &summary.display.lines;
634 }
635}
636
637#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
638pub struct DisplayOffset(usize);
639
640impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayOffset {
641 fn add_summary(&mut self, summary: &'a TransformSummary) {
642 self.0 += &summary.display.chars;
643 }
644}
645
646impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
647 fn add_summary(&mut self, summary: &'a TransformSummary) {
648 *self += &summary.buffer.lines;
649 }
650}
651
652impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
653 fn add_summary(&mut self, summary: &'a TransformSummary) {
654 *self += &summary.buffer.chars;
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661 use crate::test::sample_text;
662 use buffer::ToPoint;
663 use gpui::App;
664
665 #[test]
666 fn test_basic_folds() {
667 App::test((), |app| {
668 let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
669 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
670
671 map.fold(
672 vec![
673 Point::new(0, 2)..Point::new(2, 2),
674 Point::new(2, 4)..Point::new(4, 1),
675 ],
676 app.as_ref(),
677 )
678 .unwrap();
679 assert_eq!(map.text(app.as_ref()), "aa…cc…eeeee");
680
681 buffer.update(app, |buffer, ctx| {
682 buffer
683 .edit(
684 vec![
685 Point::new(0, 0)..Point::new(0, 1),
686 Point::new(2, 3)..Point::new(2, 3),
687 ],
688 "123",
689 Some(ctx),
690 )
691 .unwrap();
692 });
693 assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee");
694
695 buffer.update(app, |buffer, ctx| {
696 let start_version = buffer.version.clone();
697 buffer
698 .edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))
699 .unwrap();
700 buffer.edits_since(start_version).collect::<Vec<_>>()
701 });
702 assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
703
704 map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref())
705 .unwrap();
706 assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
707 });
708 }
709
710 #[test]
711 fn test_adjacent_folds() {
712 App::test((), |app| {
713 let buffer = app.add_model(|_| Buffer::new(0, "abcdefghijkl"));
714
715 {
716 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
717
718 map.fold(vec![5..8], app.as_ref()).unwrap();
719 map.check_invariants(app.as_ref());
720 assert_eq!(map.text(app.as_ref()), "abcde…ijkl");
721
722 // Create an fold adjacent to the start of the first fold.
723 map.fold(vec![0..1, 2..5], app.as_ref()).unwrap();
724 map.check_invariants(app.as_ref());
725 assert_eq!(map.text(app.as_ref()), "…b…ijkl");
726
727 // Create an fold adjacent to the end of the first fold.
728 map.fold(vec![11..11, 8..10], app.as_ref()).unwrap();
729 map.check_invariants(app.as_ref());
730 assert_eq!(map.text(app.as_ref()), "…b…kl");
731 }
732
733 {
734 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
735
736 // Create two adjacent folds.
737 map.fold(vec![0..2, 2..5], app.as_ref()).unwrap();
738 map.check_invariants(app.as_ref());
739 assert_eq!(map.text(app.as_ref()), "…fghijkl");
740
741 // Edit within one of the folds.
742 buffer.update(app, |buffer, ctx| {
743 let version = buffer.version();
744 buffer.edit(vec![0..1], "12345", Some(ctx)).unwrap();
745 buffer.edits_since(version).collect::<Vec<_>>()
746 });
747 map.check_invariants(app.as_ref());
748 assert_eq!(map.text(app.as_ref()), "12345…fghijkl");
749 }
750 });
751 }
752
753 #[test]
754 fn test_overlapping_folds() {
755 App::test((), |app| {
756 let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
757 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
758 map.fold(
759 vec![
760 Point::new(0, 2)..Point::new(2, 2),
761 Point::new(0, 4)..Point::new(1, 0),
762 Point::new(1, 2)..Point::new(3, 2),
763 Point::new(3, 1)..Point::new(4, 1),
764 ],
765 app.as_ref(),
766 )
767 .unwrap();
768 assert_eq!(map.text(app.as_ref()), "aa…eeeee");
769 })
770 }
771
772 #[test]
773 fn test_merging_folds_via_edit() {
774 App::test((), |app| {
775 let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
776 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
777
778 map.fold(
779 vec![
780 Point::new(0, 2)..Point::new(2, 2),
781 Point::new(3, 1)..Point::new(4, 1),
782 ],
783 app.as_ref(),
784 )
785 .unwrap();
786 assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee");
787
788 buffer.update(app, |buffer, ctx| {
789 buffer
790 .edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))
791 .unwrap();
792 });
793 assert_eq!(map.text(app.as_ref()), "aa…eeeee");
794 });
795 }
796
797 #[test]
798 fn test_folds_in_range() {
799 App::test((), |app| {
800 let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
801 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
802 let buffer = buffer.read(app);
803
804 map.fold(
805 vec![
806 Point::new(0, 2)..Point::new(2, 2),
807 Point::new(0, 4)..Point::new(1, 0),
808 Point::new(1, 2)..Point::new(3, 2),
809 Point::new(3, 1)..Point::new(4, 1),
810 ],
811 app.as_ref(),
812 )
813 .unwrap();
814 let fold_ranges = map
815 .folds_in_range(Point::new(1, 0)..Point::new(1, 3), app.as_ref())
816 .unwrap()
817 .map(|fold| {
818 fold.start.to_point(buffer).unwrap()..fold.end.to_point(buffer).unwrap()
819 })
820 .collect::<Vec<_>>();
821 assert_eq!(
822 fold_ranges,
823 vec![
824 Point::new(0, 2)..Point::new(2, 2),
825 Point::new(0, 4)..Point::new(1, 0),
826 Point::new(1, 2)..Point::new(3, 2)
827 ]
828 );
829 });
830 }
831
832 #[test]
833 fn test_random_folds() {
834 use crate::editor::ToPoint;
835 use crate::util::RandomCharIter;
836 use rand::prelude::*;
837 use std::env;
838
839 let iterations = env::var("ITERATIONS")
840 .map(|i| i.parse().expect("invalid `ITERATIONS` variable"))
841 .unwrap_or(100);
842 let operations = env::var("OPERATIONS")
843 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
844 .unwrap_or(10);
845 let seed_range = if let Ok(seed) = env::var("SEED") {
846 let seed = seed.parse().expect("invalid `SEED` variable");
847 seed..seed + 1
848 } else {
849 0..iterations
850 };
851
852 for seed in seed_range {
853 dbg!(seed);
854 let mut rng = StdRng::seed_from_u64(seed);
855
856 App::test((), |app| {
857 let buffer = app.add_model(|_| {
858 let len = rng.gen_range(0..10);
859 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
860 Buffer::new(0, text)
861 });
862 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
863
864 for _ in 0..operations {
865 log::info!("text: {:?}", buffer.read(app).text());
866 if rng.gen() {
867 let buffer = buffer.read(app);
868
869 let fold_count = rng.gen_range(1..=5);
870 let mut fold_ranges: Vec<Range<usize>> = Vec::new();
871 for _ in 0..fold_count {
872 let end = rng.gen_range(0..buffer.len() + 1);
873 let start = rng.gen_range(0..end + 1);
874 fold_ranges.push(start..end);
875 }
876 log::info!("folding {:?}", fold_ranges);
877 map.fold(fold_ranges.clone(), app.as_ref()).unwrap();
878 } else {
879 let edits = buffer.update(app, |buffer, ctx| {
880 let start_version = buffer.version.clone();
881 let edit_count = rng.gen_range(1..=5);
882 buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
883 buffer.edits_since(start_version).collect::<Vec<_>>()
884 });
885 log::info!("editing {:?}", edits);
886 }
887 map.check_invariants(app.as_ref());
888
889 let buffer = map.buffer.read(app);
890 let mut expected_text = buffer.text();
891 let mut expected_buffer_rows = Vec::new();
892 let mut next_row = buffer.max_point().row;
893 for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
894 let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
895 let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
896 expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
897 next_row = fold_start.row;
898
899 expected_text.replace_range(fold_range.start..fold_range.end, "…");
900 }
901 expected_buffer_rows.extend((0..=next_row).rev());
902 expected_buffer_rows.reverse();
903
904 assert_eq!(map.text(app.as_ref()), expected_text);
905
906 for (display_row, line) in expected_text.lines().enumerate() {
907 let line_len = map.line_len(display_row as u32, app.as_ref()).unwrap();
908 assert_eq!(line_len, line.chars().count() as u32);
909 }
910
911 let mut display_point = DisplayPoint::new(0, 0);
912 let mut display_offset = DisplayOffset(0);
913 for c in expected_text.chars() {
914 let buffer_point = map.to_buffer_point(display_point, app.as_ref());
915 let buffer_offset = buffer_point.to_offset(buffer).unwrap();
916 assert_eq!(
917 map.to_display_point(buffer_point, app.as_ref()),
918 display_point
919 );
920 assert_eq!(
921 map.to_buffer_offset(display_point, app.as_ref()).unwrap(),
922 buffer_offset
923 );
924 assert_eq!(
925 map.to_display_offset(display_point, app.as_ref()).unwrap(),
926 display_offset
927 );
928
929 if c == '\n' {
930 *display_point.row_mut() += 1;
931 *display_point.column_mut() = 0;
932 } else {
933 *display_point.column_mut() += 1;
934 }
935 display_offset.0 += 1;
936 }
937
938 for _ in 0..5 {
939 let row = rng.gen_range(0..=map.max_point(app.as_ref()).row());
940 let column = rng.gen_range(0..=map.line_len(row, app.as_ref()).unwrap());
941 let point = DisplayPoint::new(row, column);
942 let offset = map.to_display_offset(point, app.as_ref()).unwrap().0;
943 let len = rng.gen_range(0..=map.len(app.as_ref()) - offset);
944 assert_eq!(
945 map.snapshot(app.as_ref())
946 .chars_at(point, app.as_ref())
947 .unwrap()
948 .take(len)
949 .collect::<String>(),
950 expected_text
951 .chars()
952 .skip(offset)
953 .take(len)
954 .collect::<String>()
955 );
956 }
957
958 for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
959 let display_row = map
960 .to_display_point(Point::new(*buffer_row, 0), app.as_ref())
961 .row();
962 assert_eq!(
963 map.snapshot(app.as_ref())
964 .buffer_rows(display_row)
965 .unwrap()
966 .collect::<Vec<_>>(),
967 expected_buffer_rows[idx..],
968 );
969 }
970
971 for fold_range in map.merged_fold_ranges(app.as_ref()) {
972 let display_point = map.to_display_point(
973 fold_range.start.to_point(buffer).unwrap(),
974 app.as_ref(),
975 );
976 assert!(map.is_line_folded(display_point.row(), app.as_ref()));
977 }
978
979 for _ in 0..5 {
980 let end = rng.gen_range(0..=buffer.len());
981 let start = rng.gen_range(0..=end);
982 let expected_folds = map
983 .folds
984 .items()
985 .into_iter()
986 .filter(|fold| {
987 let fold_start = fold.0.start.to_offset(buffer).unwrap();
988 let fold_end = fold.0.end.to_offset(buffer).unwrap();
989 start <= fold_end && end >= fold_start
990 })
991 .map(|fold| fold.0)
992 .collect::<Vec<_>>();
993
994 assert_eq!(
995 map.folds_in_range(start..end, app.as_ref())
996 .unwrap()
997 .cloned()
998 .collect::<Vec<_>>(),
999 expected_folds
1000 );
1001 }
1002 }
1003 });
1004 }
1005 }
1006
1007 #[test]
1008 fn test_buffer_rows() {
1009 App::test((), |app| {
1010 let text = sample_text(6, 6) + "\n";
1011 let buffer = app.add_model(|_| Buffer::new(0, text));
1012
1013 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
1014
1015 map.fold(
1016 vec![
1017 Point::new(0, 2)..Point::new(2, 2),
1018 Point::new(3, 1)..Point::new(4, 1),
1019 ],
1020 app.as_ref(),
1021 )
1022 .unwrap();
1023
1024 assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
1025 assert_eq!(
1026 map.snapshot(app.as_ref())
1027 .buffer_rows(0)
1028 .unwrap()
1029 .collect::<Vec<_>>(),
1030 vec![0, 3, 5, 6]
1031 );
1032 assert_eq!(
1033 map.snapshot(app.as_ref())
1034 .buffer_rows(3)
1035 .unwrap()
1036 .collect::<Vec<_>>(),
1037 vec![6]
1038 );
1039 });
1040 }
1041
1042 impl FoldMap {
1043 fn text(&self, app: &AppContext) -> String {
1044 self.snapshot(app)
1045 .chars_at(DisplayPoint(Point::zero()), app)
1046 .unwrap()
1047 .collect()
1048 }
1049
1050 fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
1051 let buffer = self.buffer.read(app);
1052 let mut folds = self.folds.items();
1053 // Ensure sorting doesn't change how folds get merged and displayed.
1054 folds.sort_by(|a, b| a.0.cmp(&b.0, buffer).unwrap());
1055 let mut fold_ranges = folds
1056 .iter()
1057 .map(|fold| {
1058 fold.0.start.to_offset(buffer).unwrap()..fold.0.end.to_offset(buffer).unwrap()
1059 })
1060 .peekable();
1061
1062 let mut merged_ranges = Vec::new();
1063 while let Some(mut fold_range) = fold_ranges.next() {
1064 while let Some(next_range) = fold_ranges.peek() {
1065 if fold_range.end >= next_range.start {
1066 if next_range.end > fold_range.end {
1067 fold_range.end = next_range.end;
1068 }
1069 fold_ranges.next();
1070 } else {
1071 break;
1072 }
1073 }
1074 if fold_range.end > fold_range.start {
1075 merged_ranges.push(fold_range);
1076 }
1077 }
1078 merged_ranges
1079 }
1080
1081 fn check_invariants(&self, ctx: &AppContext) {
1082 let transforms = self.sync(ctx);
1083 let buffer = self.buffer.read(ctx);
1084 assert_eq!(
1085 transforms.summary().buffer.chars,
1086 buffer.len(),
1087 "transform tree does not match buffer's length"
1088 );
1089 }
1090 }
1091}