1use super::{
2 buffer, Anchor, AnchorRangeExt, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset,
3};
4use crate::{
5 sum_tree::{self, Cursor, SumTree},
6 util::find_insertion_index,
7};
8use anyhow::{anyhow, Result};
9use gpui::{AppContext, ModelHandle};
10use std::{
11 cmp::{self, Ordering},
12 iter::Take,
13 ops::Range,
14};
15use sum_tree::{Dimension, SeekBias};
16
17pub struct FoldMap {
18 buffer: ModelHandle<Buffer>,
19 transforms: SumTree<Transform>,
20 folds: Vec<Range<Anchor>>,
21}
22
23impl FoldMap {
24 pub fn new(buffer: ModelHandle<Buffer>, app: &AppContext) -> Self {
25 let text_summary = buffer.read(app).text_summary();
26 Self {
27 buffer,
28 folds: Vec::new(),
29 transforms: SumTree::from_item(Transform {
30 summary: TransformSummary {
31 buffer: text_summary.clone(),
32 display: text_summary,
33 },
34 display_text: None,
35 }),
36 }
37 }
38
39 pub fn buffer_rows(&self, start_row: u32) -> Result<BufferRows> {
40 if start_row > self.transforms.summary().display.lines.row {
41 return Err(anyhow!("invalid display row {}", start_row));
42 }
43
44 let display_point = Point::new(start_row, 0);
45 let mut cursor = self.transforms.cursor();
46 cursor.seek(&DisplayPoint(display_point), SeekBias::Left);
47
48 Ok(BufferRows {
49 display_point,
50 cursor,
51 })
52 }
53
54 pub fn len(&self) -> usize {
55 self.transforms.summary().display.chars
56 }
57
58 pub fn line_len(&self, row: u32, ctx: &AppContext) -> Result<u32> {
59 let line_start = self.to_display_offset(DisplayPoint::new(row, 0), ctx)?.0;
60 let line_end = if row >= self.max_point().row() {
61 self.len()
62 } else {
63 self.to_display_offset(DisplayPoint::new(row + 1, 0), ctx)?
64 .0
65 - 1
66 };
67
68 Ok((line_end - line_start) as u32)
69 }
70
71 pub fn chars_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Result<Chars<'a>> {
72 let offset = self.to_display_offset(point, app)?;
73 let mut cursor = self.transforms.cursor();
74 cursor.seek(&offset, SeekBias::Right);
75 let buffer = self.buffer.read(app);
76 Ok(Chars {
77 cursor,
78 offset: offset.0,
79 buffer,
80 buffer_chars: None,
81 })
82 }
83
84 pub fn max_point(&self) -> DisplayPoint {
85 DisplayPoint(self.transforms.summary().display.lines)
86 }
87
88 pub fn rightmost_point(&self) -> DisplayPoint {
89 DisplayPoint(self.transforms.summary().display.rightmost_point)
90 }
91
92 pub fn folds_in_range<T>(&self, range: Range<T>, app: &AppContext) -> Result<&[Range<Anchor>]>
93 where
94 T: ToOffset,
95 {
96 let buffer = self.buffer.read(app);
97 let range = buffer.anchor_before(range.start)?..buffer.anchor_before(range.end)?;
98 let mut start_ix = find_insertion_index(&self.folds, |probe| probe.cmp(&range, buffer))?;
99 let mut end_ix = start_ix;
100
101 for fold in self.folds[..start_ix].iter().rev() {
102 if fold.end.cmp(&range.start, buffer)? == Ordering::Greater {
103 start_ix -= 1;
104 } else {
105 break;
106 }
107 }
108 for fold in &self.folds[end_ix..] {
109 if range.end.cmp(&fold.start, buffer)? == Ordering::Greater {
110 end_ix += 1;
111 } else {
112 break;
113 }
114 }
115
116 Ok(&self.folds[start_ix..end_ix])
117 }
118
119 pub fn fold<T: ToOffset>(
120 &mut self,
121 ranges: impl IntoIterator<Item = Range<T>>,
122 app: &AppContext,
123 ) -> Result<()> {
124 let mut edits = Vec::new();
125 let buffer = self.buffer.read(app);
126 for range in ranges.into_iter() {
127 let start = range.start.to_offset(buffer)?;
128 let end = range.end.to_offset(buffer)?;
129 edits.push(Edit {
130 old_range: start..end,
131 new_range: start..end,
132 });
133
134 let fold = buffer.anchor_after(start)?..buffer.anchor_before(end)?;
135 let ix = find_insertion_index(&self.folds, |probe| probe.cmp(&fold, buffer))?;
136 self.folds.insert(ix, fold);
137 }
138 edits.sort_unstable_by(|a, b| {
139 a.old_range
140 .start
141 .cmp(&b.old_range.start)
142 .then_with(|| b.old_range.end.cmp(&a.old_range.end))
143 });
144
145 self.apply_edits(&edits, app)?;
146 Ok(())
147 }
148
149 pub fn unfold<T: ToOffset>(
150 &mut self,
151 ranges: impl IntoIterator<Item = Range<T>>,
152 app: &AppContext,
153 ) -> Result<()> {
154 let buffer = self.buffer.read(app);
155
156 let mut edits = Vec::new();
157 for range in ranges.into_iter() {
158 let start = buffer.anchor_before(range.start.to_offset(buffer)?)?;
159 let end = buffer.anchor_after(range.end.to_offset(buffer)?)?;
160
161 // Remove intersecting folds and add their ranges to edits that are passed to apply_edits
162 self.folds.retain(|fold| {
163 if fold.start.cmp(&end, buffer).unwrap() > Ordering::Equal
164 || fold.end.cmp(&start, buffer).unwrap() < Ordering::Equal
165 {
166 true
167 } else {
168 let offset_range =
169 fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap();
170 edits.push(Edit {
171 old_range: offset_range.clone(),
172 new_range: offset_range,
173 });
174 false
175 }
176 });
177 }
178
179 self.apply_edits(&edits, app)?;
180 Ok(())
181 }
182
183 pub fn is_line_folded(&self, display_row: u32) -> bool {
184 let mut cursor = self.transforms.cursor::<DisplayPoint, DisplayPoint>();
185 cursor.seek(&DisplayPoint::new(display_row, 0), SeekBias::Right);
186 while let Some(transform) = cursor.item() {
187 if transform.display_text.is_some() {
188 return true;
189 }
190 if cursor.end().row() == display_row {
191 cursor.next()
192 } else {
193 break;
194 }
195 }
196 false
197 }
198
199 pub fn to_buffer_offset(&self, point: DisplayPoint, app: &AppContext) -> Result<usize> {
200 let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
201 cursor.seek(&point, SeekBias::Right);
202 let overshoot = point.0 - cursor.start().display.lines;
203 (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(app))
204 }
205
206 pub fn to_display_offset(
207 &self,
208 point: DisplayPoint,
209 app: &AppContext,
210 ) -> Result<DisplayOffset> {
211 let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
212 cursor.seek(&point, SeekBias::Right);
213 let overshoot = point.0 - cursor.start().display.lines;
214 let mut offset = cursor.start().display.chars;
215 if !overshoot.is_zero() {
216 let transform = cursor
217 .item()
218 .ok_or_else(|| anyhow!("display point {:?} is out of range", point))?;
219 assert!(transform.display_text.is_none());
220 let end_buffer_offset =
221 (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(app))?;
222 offset += end_buffer_offset - cursor.start().buffer.chars;
223 }
224 Ok(DisplayOffset(offset))
225 }
226
227 pub fn to_buffer_point(&self, display_point: DisplayPoint) -> Point {
228 let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
229 cursor.seek(&display_point, SeekBias::Right);
230 let overshoot = display_point.0 - cursor.start().display.lines;
231 cursor.start().buffer.lines + overshoot
232 }
233
234 pub fn to_display_point(&self, point: Point) -> DisplayPoint {
235 let mut cursor = self.transforms.cursor::<Point, TransformSummary>();
236 cursor.seek(&point, SeekBias::Right);
237 let overshoot = point - cursor.start().buffer.lines;
238 DisplayPoint(cmp::min(
239 cursor.start().display.lines + overshoot,
240 cursor.end().display.lines,
241 ))
242 }
243
244 pub fn apply_edits(&mut self, edits: &[Edit], app: &AppContext) -> Result<()> {
245 let buffer = self.buffer.read(app);
246 let mut edits = edits.iter().cloned().peekable();
247
248 let mut new_transforms = SumTree::new();
249 let mut cursor = self.transforms.cursor::<usize, usize>();
250 cursor.seek(&0, SeekBias::Right);
251
252 while let Some(mut edit) = edits.next() {
253 new_transforms.push_tree(cursor.slice(&edit.old_range.start, SeekBias::Left));
254 edit.new_range.start -= edit.old_range.start - cursor.start();
255 edit.old_range.start = *cursor.start();
256
257 cursor.seek(&edit.old_range.end, SeekBias::Right);
258 cursor.next();
259
260 let mut delta = edit.delta();
261 loop {
262 edit.old_range.end = *cursor.start();
263
264 if let Some(next_edit) = edits.peek() {
265 if next_edit.old_range.start > edit.old_range.end {
266 break;
267 }
268
269 let next_edit = edits.next().unwrap();
270 delta += next_edit.delta();
271
272 if next_edit.old_range.end >= edit.old_range.end {
273 edit.old_range.end = next_edit.old_range.end;
274 cursor.seek(&edit.old_range.end, SeekBias::Right);
275 cursor.next();
276 }
277 } else {
278 break;
279 }
280 }
281
282 edit.new_range.end =
283 ((edit.new_range.start + edit.old_extent()) as isize + delta) as usize;
284
285 let anchor = buffer.anchor_before(edit.new_range.start)?;
286 let folds_start =
287 find_insertion_index(&self.folds, |probe| probe.start.cmp(&anchor, buffer))?;
288 let mut folds = self.folds[folds_start..]
289 .iter()
290 .map(|fold| {
291 fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap()
292 })
293 .peekable();
294
295 while folds
296 .peek()
297 .map_or(false, |fold| fold.start < edit.new_range.end)
298 {
299 let mut fold = folds.next().unwrap();
300 let sum = new_transforms.summary();
301
302 assert!(fold.start >= sum.buffer.chars);
303
304 while folds
305 .peek()
306 .map_or(false, |next_fold| next_fold.start <= fold.end)
307 {
308 let next_fold = folds.next().unwrap();
309 if next_fold.end > fold.end {
310 fold.end = next_fold.end;
311 }
312 }
313
314 if fold.start > sum.buffer.chars {
315 let text_summary = buffer.text_summary_for_range(sum.buffer.chars..fold.start);
316 new_transforms.push(Transform {
317 summary: TransformSummary {
318 display: text_summary.clone(),
319 buffer: text_summary,
320 },
321 display_text: None,
322 });
323 }
324
325 if fold.end > fold.start {
326 new_transforms.push(Transform {
327 summary: TransformSummary {
328 display: TextSummary {
329 chars: 1,
330 bytes: '…'.len_utf8(),
331 lines: Point::new(0, 1),
332 first_line_len: 1,
333 rightmost_point: Point::new(0, 1),
334 },
335 buffer: buffer.text_summary_for_range(fold.start..fold.end),
336 },
337 display_text: Some('…'),
338 });
339 }
340 }
341
342 let sum = new_transforms.summary();
343 if sum.buffer.chars < edit.new_range.end {
344 let text_summary =
345 buffer.text_summary_for_range(sum.buffer.chars..edit.new_range.end);
346 new_transforms.push(Transform {
347 summary: TransformSummary {
348 display: text_summary.clone(),
349 buffer: text_summary,
350 },
351 display_text: None,
352 });
353 }
354 }
355
356 new_transforms.push_tree(cursor.suffix());
357 if new_transforms.is_empty() {
358 let text_summary = buffer.text_summary();
359 new_transforms.push(Transform {
360 summary: TransformSummary {
361 display: text_summary.clone(),
362 buffer: text_summary,
363 },
364 display_text: None,
365 });
366 }
367
368 drop(cursor);
369 self.transforms = new_transforms;
370
371 Ok(())
372 }
373}
374
375#[derive(Clone, Debug, Default, Eq, PartialEq)]
376struct Transform {
377 summary: TransformSummary,
378 display_text: Option<char>,
379}
380
381#[derive(Clone, Debug, Default, Eq, PartialEq)]
382struct TransformSummary {
383 display: TextSummary,
384 buffer: TextSummary,
385}
386
387impl sum_tree::Item for Transform {
388 type Summary = TransformSummary;
389
390 fn summary(&self) -> Self::Summary {
391 self.summary.clone()
392 }
393}
394
395impl<'a> std::ops::AddAssign<&'a Self> for TransformSummary {
396 fn add_assign(&mut self, other: &'a Self) {
397 self.buffer += &other.buffer;
398 self.display += &other.display;
399 }
400}
401
402impl<'a> Dimension<'a, TransformSummary> for TransformSummary {
403 fn add_summary(&mut self, summary: &'a TransformSummary) {
404 *self += summary;
405 }
406}
407
408pub struct BufferRows<'a> {
409 cursor: Cursor<'a, Transform, DisplayPoint, TransformSummary>,
410 display_point: Point,
411}
412
413impl<'a> Iterator for BufferRows<'a> {
414 type Item = u32;
415
416 fn next(&mut self) -> Option<Self::Item> {
417 while self.display_point > self.cursor.end().display.lines {
418 self.cursor.next();
419 if self.cursor.item().is_none() {
420 // TODO: Return a bool from next?
421 break;
422 }
423 }
424
425 if self.cursor.item().is_some() {
426 let overshoot = self.display_point - self.cursor.start().display.lines;
427 let buffer_point = self.cursor.start().buffer.lines + overshoot;
428 self.display_point.row += 1;
429 Some(buffer_point.row)
430 } else {
431 None
432 }
433 }
434}
435
436pub struct Chars<'a> {
437 cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
438 offset: usize,
439 buffer: &'a Buffer,
440 buffer_chars: Option<Take<buffer::CharIter<'a>>>,
441}
442
443impl<'a> Iterator for Chars<'a> {
444 type Item = char;
445
446 fn next(&mut self) -> Option<Self::Item> {
447 if let Some(c) = self.buffer_chars.as_mut().and_then(|chars| chars.next()) {
448 self.offset += 1;
449 return Some(c);
450 }
451
452 while self.offset == self.cursor.end().display.chars && self.cursor.item().is_some() {
453 self.cursor.next();
454 }
455
456 self.cursor.item().and_then(|transform| {
457 if let Some(c) = transform.display_text {
458 self.offset += 1;
459 Some(c)
460 } else {
461 let overshoot = self.offset - self.cursor.start().display.chars;
462 let buffer_start = self.cursor.start().buffer.chars + overshoot;
463 let char_count = self.cursor.end().buffer.chars - buffer_start;
464 self.buffer_chars =
465 Some(self.buffer.chars_at(buffer_start).unwrap().take(char_count));
466 self.next()
467 }
468 })
469 }
470}
471
472impl<'a> Dimension<'a, TransformSummary> for DisplayPoint {
473 fn add_summary(&mut self, summary: &'a TransformSummary) {
474 self.0 += &summary.display.lines;
475 }
476}
477
478#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
479pub struct DisplayOffset(usize);
480
481impl<'a> Dimension<'a, TransformSummary> for DisplayOffset {
482 fn add_summary(&mut self, summary: &'a TransformSummary) {
483 self.0 += &summary.display.chars;
484 }
485}
486
487impl<'a> Dimension<'a, TransformSummary> for Point {
488 fn add_summary(&mut self, summary: &'a TransformSummary) {
489 *self += &summary.buffer.lines;
490 }
491}
492
493impl<'a> Dimension<'a, TransformSummary> for usize {
494 fn add_summary(&mut self, summary: &'a TransformSummary) {
495 *self += &summary.buffer.chars;
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502 use crate::test::sample_text;
503 use gpui::App;
504
505 #[test]
506 fn test_basic_folds() {
507 App::test((), |app| {
508 let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
509 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
510
511 map.fold(
512 vec![
513 Point::new(0, 2)..Point::new(2, 2),
514 Point::new(2, 4)..Point::new(4, 1),
515 ],
516 app.as_ref(),
517 )
518 .unwrap();
519 assert_eq!(map.text(app.as_ref()), "aa…cc…eeeee");
520
521 let edits = buffer.update(app, |buffer, ctx| {
522 let start_version = buffer.version.clone();
523 buffer
524 .edit(
525 vec![
526 Point::new(0, 0)..Point::new(0, 1),
527 Point::new(2, 3)..Point::new(2, 3),
528 ],
529 "123",
530 Some(ctx),
531 )
532 .unwrap();
533 buffer.edits_since(start_version).collect::<Vec<_>>()
534 });
535
536 map.apply_edits(&edits, app.as_ref()).unwrap();
537 assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee");
538
539 let edits = buffer.update(app, |buffer, ctx| {
540 let start_version = buffer.version.clone();
541 buffer
542 .edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))
543 .unwrap();
544 buffer.edits_since(start_version).collect::<Vec<_>>()
545 });
546
547 map.apply_edits(&edits, app.as_ref()).unwrap();
548 assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
549
550 map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref())
551 .unwrap();
552 assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
553 });
554 }
555
556 #[test]
557 fn test_adjacent_folds() {
558 App::test((), |app| {
559 let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx));
560
561 {
562 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
563
564 map.fold(vec![5..8], app.as_ref()).unwrap();
565 map.check_invariants(app.as_ref());
566 assert_eq!(map.text(app.as_ref()), "abcde…ijkl");
567
568 // Create an fold adjacent to the start of the first fold.
569 map.fold(vec![0..1, 2..5], app.as_ref()).unwrap();
570 map.check_invariants(app.as_ref());
571 assert_eq!(map.text(app.as_ref()), "…b…ijkl");
572
573 // Create an fold adjacent to the end of the first fold.
574 map.fold(vec![11..11, 8..10], app.as_ref()).unwrap();
575 map.check_invariants(app.as_ref());
576 assert_eq!(map.text(app.as_ref()), "…b…kl");
577 }
578
579 {
580 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
581
582 // Create two adjacent folds.
583 map.fold(vec![0..2, 2..5], app.as_ref()).unwrap();
584 map.check_invariants(app.as_ref());
585 assert_eq!(map.text(app.as_ref()), "…fghijkl");
586
587 // Edit within one of the folds.
588 let edits = buffer.update(app, |buffer, ctx| {
589 let version = buffer.version();
590 buffer.edit(vec![0..1], "12345", Some(ctx)).unwrap();
591 buffer.edits_since(version).collect::<Vec<_>>()
592 });
593 map.apply_edits(edits.as_slice(), app.as_ref()).unwrap();
594 map.check_invariants(app.as_ref());
595 assert_eq!(map.text(app.as_ref()), "12345…fghijkl");
596 }
597 });
598 }
599
600 #[test]
601 fn test_overlapping_folds() {
602 App::test((), |app| {
603 let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
604 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
605 map.fold(
606 vec![
607 Point::new(0, 2)..Point::new(2, 2),
608 Point::new(0, 4)..Point::new(1, 0),
609 Point::new(1, 2)..Point::new(3, 2),
610 Point::new(3, 1)..Point::new(4, 1),
611 ],
612 app.as_ref(),
613 )
614 .unwrap();
615 assert_eq!(map.text(app.as_ref()), "aa…eeeee");
616 })
617 }
618
619 #[test]
620 fn test_merging_folds_via_edit() {
621 App::test((), |app| {
622 let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
623 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
624
625 map.fold(
626 vec![
627 Point::new(0, 2)..Point::new(2, 2),
628 Point::new(3, 1)..Point::new(4, 1),
629 ],
630 app.as_ref(),
631 )
632 .unwrap();
633 assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee");
634
635 let edits = buffer.update(app, |buffer, ctx| {
636 let start_version = buffer.version.clone();
637 buffer
638 .edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))
639 .unwrap();
640 buffer.edits_since(start_version).collect::<Vec<_>>()
641 });
642
643 map.apply_edits(&edits, app.as_ref()).unwrap();
644 assert_eq!(map.text(app.as_ref()), "aa…eeeee");
645 });
646 }
647
648 #[test]
649 fn test_random_folds() {
650 use crate::editor::ToPoint;
651 use crate::util::RandomCharIter;
652 use rand::prelude::*;
653 use std::env;
654
655 let iterations = env::var("ITERATIONS")
656 .map(|i| i.parse().expect("invalid `ITERATIONS` variable"))
657 .unwrap_or(100);
658 let operations = env::var("OPERATIONS")
659 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
660 .unwrap_or(10);
661 let seed_range = if let Ok(seed) = env::var("SEED") {
662 let seed = seed.parse().expect("invalid `SEED` variable");
663 seed..seed + 1
664 } else {
665 0..iterations
666 };
667
668 for seed in seed_range {
669 println!("{:?}", seed);
670 let mut rng = StdRng::seed_from_u64(seed);
671
672 App::test((), |app| {
673 let buffer = app.add_model(|ctx| {
674 let len = rng.gen_range(0..10);
675 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
676 Buffer::new(0, text, ctx)
677 });
678 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
679
680 for _ in 0..operations {
681 log::info!("text: {:?}", buffer.read(app).text());
682 {
683 let buffer = buffer.read(app);
684
685 let fold_count = rng.gen_range(0..=2);
686 let mut fold_ranges: Vec<Range<usize>> = Vec::new();
687 for _ in 0..fold_count {
688 let end = rng.gen_range(0..buffer.len() + 1);
689 let start = rng.gen_range(0..end + 1);
690 fold_ranges.push(start..end);
691 }
692 log::info!("folding {:?}", fold_ranges);
693 map.fold(fold_ranges.clone(), app.as_ref()).unwrap();
694 map.check_invariants(app.as_ref());
695
696 let mut expected_text = buffer.text();
697 for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
698 expected_text.replace_range(fold_range.start..fold_range.end, "…");
699 }
700 assert_eq!(map.text(app.as_ref()), expected_text);
701
702 for fold_range in map.merged_fold_ranges(app.as_ref()) {
703 let display_point =
704 map.to_display_point(fold_range.start.to_point(buffer).unwrap());
705 assert!(map.is_line_folded(display_point.row()));
706 }
707 }
708
709 let edits = buffer.update(app, |buffer, ctx| {
710 let start_version = buffer.version.clone();
711 let edit_count = rng.gen_range(0..=2);
712 buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
713 buffer.edits_since(start_version).collect::<Vec<_>>()
714 });
715 log::info!("editing {:?}", edits);
716 map.apply_edits(&edits, app.as_ref()).unwrap();
717 map.check_invariants(app.as_ref());
718
719 let buffer = map.buffer.read(app);
720 let mut expected_text = buffer.text();
721 let mut expected_buffer_rows = Vec::new();
722 let mut next_row = buffer.max_point().row;
723 for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
724 let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
725 let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
726 expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
727 next_row = fold_start.row;
728
729 expected_text.replace_range(fold_range.start..fold_range.end, "…");
730 }
731 expected_buffer_rows.extend((0..=next_row).rev());
732 expected_buffer_rows.reverse();
733
734 assert_eq!(map.text(app.as_ref()), expected_text);
735
736 for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
737 let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
738 assert_eq!(
739 map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
740 expected_buffer_rows[idx..],
741 );
742 }
743 }
744 });
745 }
746 }
747
748 #[test]
749 fn test_buffer_rows() {
750 App::test((), |app| {
751 let text = sample_text(6, 6) + "\n";
752 let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
753
754 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
755
756 map.fold(
757 vec![
758 Point::new(0, 2)..Point::new(2, 2),
759 Point::new(3, 1)..Point::new(4, 1),
760 ],
761 app.as_ref(),
762 )
763 .unwrap();
764
765 assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
766 assert_eq!(
767 map.buffer_rows(0).unwrap().collect::<Vec<_>>(),
768 vec![0, 3, 5, 6]
769 );
770 assert_eq!(map.buffer_rows(3).unwrap().collect::<Vec<_>>(), vec![6]);
771 });
772 }
773
774 impl FoldMap {
775 fn text(&self, app: &AppContext) -> String {
776 self.chars_at(DisplayPoint(Point::zero()), app)
777 .unwrap()
778 .collect()
779 }
780
781 fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
782 let buffer = self.buffer.read(app);
783 let mut fold_ranges = self
784 .folds
785 .iter()
786 .map(|fold| {
787 fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap()
788 })
789 .peekable();
790
791 let mut merged_ranges = Vec::new();
792 while let Some(mut fold_range) = fold_ranges.next() {
793 while let Some(next_range) = fold_ranges.peek() {
794 if fold_range.end >= next_range.start {
795 if next_range.end > fold_range.end {
796 fold_range.end = next_range.end;
797 }
798 fold_ranges.next();
799 } else {
800 break;
801 }
802 }
803 if fold_range.end > fold_range.start {
804 merged_ranges.push(fold_range);
805 }
806 }
807 merged_ranges
808 }
809
810 fn check_invariants(&self, app: &AppContext) {
811 assert_eq!(
812 self.transforms.summary().buffer.chars,
813 self.buffer.read(app).len(),
814 "transform tree does not match buffer's length"
815 );
816 }
817 }
818}