1use gpui::{AppContext, ModelHandle};
2use language::{
3 Anchor, AnchorRangeExt, Buffer, Chunk, Edit, Point, PointUtf16, TextSummary, ToOffset,
4};
5use parking_lot::Mutex;
6use std::{
7 cmp::{self, Ordering},
8 iter,
9 ops::Range,
10};
11use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
12use theme::SyntaxTheme;
13
14pub trait ToFoldPoint {
15 fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint;
16}
17
18#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
19pub struct FoldPoint(pub super::Point);
20
21impl FoldPoint {
22 pub fn new(row: u32, column: u32) -> Self {
23 Self(super::Point::new(row, column))
24 }
25
26 pub fn row(self) -> u32 {
27 self.0.row
28 }
29
30 pub fn column(self) -> u32 {
31 self.0.column
32 }
33
34 pub fn row_mut(&mut self) -> &mut u32 {
35 &mut self.0.row
36 }
37
38 #[cfg(test)]
39 pub fn column_mut(&mut self) -> &mut u32 {
40 &mut self.0.column
41 }
42
43 pub fn to_buffer_point(&self, snapshot: &Snapshot) -> Point {
44 let mut cursor = snapshot.transforms.cursor::<(FoldPoint, Point)>();
45 cursor.seek(self, Bias::Right, &());
46 let overshoot = self.0 - cursor.start().0 .0;
47 cursor.start().1 + overshoot
48 }
49
50 pub fn to_buffer_offset(&self, snapshot: &Snapshot) -> usize {
51 let mut cursor = snapshot.transforms.cursor::<(FoldPoint, Point)>();
52 cursor.seek(self, Bias::Right, &());
53 let overshoot = self.0 - cursor.start().0 .0;
54 snapshot
55 .buffer_snapshot
56 .to_offset(cursor.start().1 + overshoot)
57 }
58
59 pub fn to_offset(&self, snapshot: &Snapshot) -> FoldOffset {
60 let mut cursor = snapshot
61 .transforms
62 .cursor::<(FoldPoint, TransformSummary)>();
63 cursor.seek(self, Bias::Right, &());
64 let overshoot = self.0 - cursor.start().1.output.lines;
65 let mut offset = cursor.start().1.output.bytes;
66 if !overshoot.is_zero() {
67 let transform = cursor.item().expect("display point out of range");
68 assert!(transform.output_text.is_none());
69 let end_buffer_offset = snapshot
70 .buffer_snapshot
71 .to_offset(cursor.start().1.input.lines + overshoot);
72 offset += end_buffer_offset - cursor.start().1.input.bytes;
73 }
74 FoldOffset(offset)
75 }
76}
77
78impl ToFoldPoint for Point {
79 fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint {
80 let mut cursor = snapshot.transforms.cursor::<(Point, FoldPoint)>();
81 cursor.seek(self, Bias::Right, &());
82 if cursor.item().map_or(false, |t| t.is_fold()) {
83 if bias == Bias::Left || *self == cursor.start().0 {
84 cursor.start().1
85 } else {
86 cursor.end(&()).1
87 }
88 } else {
89 let overshoot = *self - cursor.start().0;
90 FoldPoint(cmp::min(
91 cursor.start().1 .0 + overshoot,
92 cursor.end(&()).1 .0,
93 ))
94 }
95 }
96}
97
98pub struct FoldMapWriter<'a>(&'a mut FoldMap);
99
100impl<'a> FoldMapWriter<'a> {
101 pub fn fold<T: ToOffset>(
102 &mut self,
103 ranges: impl IntoIterator<Item = Range<T>>,
104 cx: &AppContext,
105 ) -> (Snapshot, Vec<FoldEdit>) {
106 let mut edits = Vec::new();
107 let mut folds = Vec::new();
108 let buffer = self.0.buffer.read(cx).snapshot();
109 for range in ranges.into_iter() {
110 let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer);
111 if range.start != range.end {
112 let fold = Fold(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
113 folds.push(fold);
114 edits.push(text::Edit {
115 old: range.clone(),
116 new: range,
117 });
118 }
119 }
120
121 folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(a, b, &buffer));
122
123 self.0.folds = {
124 let mut new_tree = SumTree::new();
125 let mut cursor = self.0.folds.cursor::<Fold>();
126 for fold in folds {
127 new_tree.push_tree(cursor.slice(&fold, Bias::Right, &buffer), &buffer);
128 new_tree.push(fold, &buffer);
129 }
130 new_tree.push_tree(cursor.suffix(&buffer), &buffer);
131 new_tree
132 };
133
134 consolidate_buffer_edits(&mut edits);
135 let edits = self.0.sync(edits, cx);
136 let snapshot = Snapshot {
137 transforms: self.0.transforms.lock().clone(),
138 folds: self.0.folds.clone(),
139 buffer_snapshot: self.0.buffer.read(cx).snapshot(),
140 version: self.0.version,
141 };
142 (snapshot, edits)
143 }
144
145 pub fn unfold<T: ToOffset>(
146 &mut self,
147 ranges: impl IntoIterator<Item = Range<T>>,
148 cx: &AppContext,
149 ) -> (Snapshot, Vec<FoldEdit>) {
150 let mut edits = Vec::new();
151 let mut fold_ixs_to_delete = Vec::new();
152 let buffer = self.0.buffer.read(cx).snapshot();
153 for range in ranges.into_iter() {
154 // Remove intersecting folds and add their ranges to edits that are passed to sync.
155 let mut folds_cursor = intersecting_folds(&buffer, &self.0.folds, range, true);
156 while let Some(fold) = folds_cursor.item() {
157 let offset_range = fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer);
158 edits.push(text::Edit {
159 old: offset_range.clone(),
160 new: offset_range,
161 });
162 fold_ixs_to_delete.push(*folds_cursor.start());
163 folds_cursor.next(&buffer);
164 }
165 }
166
167 fold_ixs_to_delete.sort_unstable();
168 fold_ixs_to_delete.dedup();
169
170 self.0.folds = {
171 let mut cursor = self.0.folds.cursor::<usize>();
172 let mut folds = SumTree::new();
173 for fold_ix in fold_ixs_to_delete {
174 folds.push_tree(cursor.slice(&fold_ix, Bias::Right, &buffer), &buffer);
175 cursor.next(&buffer);
176 }
177 folds.push_tree(cursor.suffix(&buffer), &buffer);
178 folds
179 };
180
181 consolidate_buffer_edits(&mut edits);
182 let edits = self.0.sync(edits, cx);
183 let snapshot = Snapshot {
184 transforms: self.0.transforms.lock().clone(),
185 folds: self.0.folds.clone(),
186 buffer_snapshot: self.0.buffer.read(cx).snapshot(),
187 version: self.0.version,
188 };
189 (snapshot, edits)
190 }
191}
192
193pub struct FoldMap {
194 buffer: ModelHandle<Buffer>,
195 transforms: Mutex<SumTree<Transform>>,
196 folds: SumTree<Fold>,
197 pub version: usize,
198}
199
200impl FoldMap {
201 pub fn new(buffer_handle: ModelHandle<Buffer>, cx: &AppContext) -> (Self, Snapshot) {
202 let buffer = buffer_handle.read(cx);
203 let this = Self {
204 buffer: buffer_handle,
205 folds: Default::default(),
206 transforms: Mutex::new(SumTree::from_item(
207 Transform {
208 summary: TransformSummary {
209 input: buffer.text_summary(),
210 output: buffer.text_summary(),
211 },
212 output_text: None,
213 },
214 &(),
215 )),
216 version: 0,
217 };
218 let (snapshot, _) = this.read(Vec::new(), cx);
219 (this, snapshot)
220 }
221
222 pub fn read(&self, edits: Vec<Edit<usize>>, cx: &AppContext) -> (Snapshot, Vec<FoldEdit>) {
223 let edits = self.sync(edits, cx);
224 self.check_invariants(cx);
225 let snapshot = Snapshot {
226 transforms: self.transforms.lock().clone(),
227 folds: self.folds.clone(),
228 buffer_snapshot: self.buffer.read(cx).snapshot(),
229 version: self.version,
230 };
231 (snapshot, edits)
232 }
233
234 pub fn write(
235 &mut self,
236 edits: Vec<Edit<usize>>,
237 cx: &AppContext,
238 ) -> (FoldMapWriter, Snapshot, Vec<FoldEdit>) {
239 let (snapshot, edits) = self.read(edits, cx);
240 (FoldMapWriter(self), snapshot, edits)
241 }
242
243 fn check_invariants(&self, cx: &AppContext) {
244 if cfg!(test) {
245 let buffer = self.buffer.read(cx);
246 assert_eq!(
247 self.transforms.lock().summary().input.bytes,
248 buffer.len(),
249 "transform tree does not match buffer's length"
250 );
251 }
252 }
253
254 fn sync(&self, buffer_edits: Vec<text::Edit<usize>>, cx: &AppContext) -> Vec<FoldEdit> {
255 if buffer_edits.is_empty() {
256 return Vec::new();
257 }
258
259 let buffer = self.buffer.read(cx).snapshot();
260 let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable();
261
262 let mut new_transforms = SumTree::new();
263 let mut transforms = self.transforms.lock();
264 let mut cursor = transforms.cursor::<usize>();
265 cursor.seek(&0, Bias::Right, &());
266
267 while let Some(mut edit) = buffer_edits_iter.next() {
268 new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &());
269 edit.new.start -= edit.old.start - cursor.start();
270 edit.old.start = *cursor.start();
271
272 cursor.seek(&edit.old.end, Bias::Right, &());
273 cursor.next(&());
274
275 let mut delta = edit.new.len() as isize - edit.old.len() as isize;
276 loop {
277 edit.old.end = *cursor.start();
278
279 if let Some(next_edit) = buffer_edits_iter.peek() {
280 if next_edit.old.start > edit.old.end {
281 break;
282 }
283
284 let next_edit = buffer_edits_iter.next().unwrap();
285 delta += next_edit.new.len() as isize - next_edit.old.len() as isize;
286
287 if next_edit.old.end >= edit.old.end {
288 edit.old.end = next_edit.old.end;
289 cursor.seek(&edit.old.end, Bias::Right, &());
290 cursor.next(&());
291 }
292 } else {
293 break;
294 }
295 }
296
297 edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize;
298
299 let anchor = buffer.anchor_before(edit.new.start);
300 let mut folds_cursor = self.folds.cursor::<Fold>();
301 folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &buffer);
302
303 let mut folds = iter::from_fn({
304 let buffer = &buffer;
305 move || {
306 let item = folds_cursor
307 .item()
308 .map(|f| f.0.start.to_offset(buffer)..f.0.end.to_offset(buffer));
309 folds_cursor.next(buffer);
310 item
311 }
312 })
313 .peekable();
314
315 while folds.peek().map_or(false, |fold| fold.start < edit.new.end) {
316 let mut fold = folds.next().unwrap();
317 let sum = new_transforms.summary();
318
319 assert!(fold.start >= sum.input.bytes);
320
321 while folds
322 .peek()
323 .map_or(false, |next_fold| next_fold.start <= fold.end)
324 {
325 let next_fold = folds.next().unwrap();
326 if next_fold.end > fold.end {
327 fold.end = next_fold.end;
328 }
329 }
330
331 if fold.start > sum.input.bytes {
332 let text_summary = buffer
333 .text_summary_for_range::<TextSummary, _>(sum.input.bytes..fold.start);
334 new_transforms.push(
335 Transform {
336 summary: TransformSummary {
337 output: text_summary.clone(),
338 input: text_summary,
339 },
340 output_text: None,
341 },
342 &(),
343 );
344 }
345
346 if fold.end > fold.start {
347 let output_text = "…";
348 let chars = output_text.chars().count() as u32;
349 let lines = Point::new(0, output_text.len() as u32);
350 let lines_utf16 = PointUtf16::new(0, output_text.encode_utf16().count() as u32);
351 new_transforms.push(
352 Transform {
353 summary: TransformSummary {
354 output: TextSummary {
355 bytes: output_text.len(),
356 lines,
357 lines_utf16,
358 first_line_chars: chars,
359 last_line_chars: chars,
360 longest_row: 0,
361 longest_row_chars: chars,
362 },
363 input: buffer.text_summary_for_range(fold.start..fold.end),
364 },
365 output_text: Some(output_text),
366 },
367 &(),
368 );
369 }
370 }
371
372 let sum = new_transforms.summary();
373 if sum.input.bytes < edit.new.end {
374 let text_summary =
375 buffer.text_summary_for_range::<TextSummary, _>(sum.input.bytes..edit.new.end);
376 new_transforms.push(
377 Transform {
378 summary: TransformSummary {
379 output: text_summary.clone(),
380 input: text_summary,
381 },
382 output_text: None,
383 },
384 &(),
385 );
386 }
387 }
388
389 new_transforms.push_tree(cursor.suffix(&()), &());
390 if new_transforms.is_empty() {
391 let text_summary = buffer.text_summary();
392 new_transforms.push(
393 Transform {
394 summary: TransformSummary {
395 output: text_summary.clone(),
396 input: text_summary,
397 },
398 output_text: None,
399 },
400 &(),
401 );
402 }
403
404 drop(cursor);
405
406 let mut fold_edits = Vec::with_capacity(buffer_edits.len());
407 {
408 let mut old_transforms = transforms.cursor::<(usize, FoldOffset)>();
409 let mut new_transforms = new_transforms.cursor::<(usize, FoldOffset)>();
410
411 for mut edit in buffer_edits {
412 old_transforms.seek(&edit.old.start, Bias::Left, &());
413 if old_transforms.item().map_or(false, |t| t.is_fold()) {
414 edit.old.start = old_transforms.start().0;
415 }
416 let old_start =
417 old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0);
418
419 old_transforms.seek_forward(&edit.old.end, Bias::Right, &());
420 if old_transforms.item().map_or(false, |t| t.is_fold()) {
421 old_transforms.next(&());
422 edit.old.end = old_transforms.start().0;
423 }
424 let old_end =
425 old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0);
426
427 new_transforms.seek(&edit.new.start, Bias::Left, &());
428 if new_transforms.item().map_or(false, |t| t.is_fold()) {
429 edit.new.start = new_transforms.start().0;
430 }
431 let new_start =
432 new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0);
433
434 new_transforms.seek_forward(&edit.new.end, Bias::Right, &());
435 if new_transforms.item().map_or(false, |t| t.is_fold()) {
436 new_transforms.next(&());
437 edit.new.end = new_transforms.start().0;
438 }
439 let new_end =
440 new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0);
441
442 fold_edits.push(FoldEdit {
443 old_bytes: FoldOffset(old_start)..FoldOffset(old_end),
444 new_bytes: FoldOffset(new_start)..FoldOffset(new_end),
445 });
446 }
447
448 consolidate_fold_edits(&mut fold_edits);
449 }
450
451 *transforms = new_transforms;
452 fold_edits
453 }
454}
455
456#[derive(Clone)]
457pub struct Snapshot {
458 transforms: SumTree<Transform>,
459 folds: SumTree<Fold>,
460 buffer_snapshot: language::Snapshot,
461 pub version: usize,
462}
463
464impl Snapshot {
465 #[cfg(test)]
466 pub fn text(&self) -> String {
467 self.chunks(FoldOffset(0)..self.len(), None)
468 .map(|c| c.text)
469 .collect()
470 }
471
472 #[cfg(test)]
473 pub fn fold_count(&self) -> usize {
474 self.folds.items(&self.buffer_snapshot).len()
475 }
476
477 pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
478 let mut summary = TextSummary::default();
479
480 let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>();
481 cursor.seek(&range.start, Bias::Right, &());
482 if let Some(transform) = cursor.item() {
483 let start_in_transform = range.start.0 - cursor.start().0 .0;
484 let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0;
485 if let Some(output_text) = transform.output_text {
486 summary = TextSummary::from(
487 &output_text
488 [start_in_transform.column as usize..end_in_transform.column as usize],
489 );
490 } else {
491 let buffer_start = cursor.start().1 + start_in_transform;
492 let buffer_end = cursor.start().1 + end_in_transform;
493 summary = self
494 .buffer_snapshot
495 .text_summary_for_range(buffer_start..buffer_end);
496 }
497 }
498
499 if range.end > cursor.end(&()).0 {
500 cursor.next(&());
501 summary += &cursor
502 .summary::<_, TransformSummary>(&range.end, Bias::Right, &())
503 .output;
504 if let Some(transform) = cursor.item() {
505 let end_in_transform = range.end.0 - cursor.start().0 .0;
506 if let Some(output_text) = transform.output_text {
507 summary += TextSummary::from(&output_text[..end_in_transform.column as usize]);
508 } else {
509 let buffer_start = cursor.start().1;
510 let buffer_end = cursor.start().1 + end_in_transform;
511 summary += self
512 .buffer_snapshot
513 .text_summary_for_range::<TextSummary, _>(buffer_start..buffer_end);
514 }
515 }
516 }
517
518 summary
519 }
520
521 pub fn len(&self) -> FoldOffset {
522 FoldOffset(self.transforms.summary().output.bytes)
523 }
524
525 #[cfg(test)]
526 pub fn line_len(&self, row: u32) -> u32 {
527 let line_start = FoldPoint::new(row, 0).to_offset(self).0;
528 let line_end = if row >= self.max_point().row() {
529 self.len().0
530 } else {
531 FoldPoint::new(row + 1, 0).to_offset(self).0 - 1
532 };
533 (line_end - line_start) as u32
534 }
535
536 pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
537 if start_row > self.transforms.summary().output.lines.row {
538 panic!("invalid display row {}", start_row);
539 }
540
541 let fold_point = FoldPoint::new(start_row, 0);
542 let mut cursor = self.transforms.cursor();
543 cursor.seek(&fold_point, Bias::Left, &());
544 BufferRows { fold_point, cursor }
545 }
546
547 pub fn max_point(&self) -> FoldPoint {
548 FoldPoint(self.transforms.summary().output.lines)
549 }
550
551 #[cfg(test)]
552 pub fn longest_row(&self) -> u32 {
553 self.transforms.summary().output.longest_row
554 }
555
556 pub fn folds_in_range<'a, T>(
557 &'a self,
558 range: Range<T>,
559 ) -> impl Iterator<Item = &'a Range<Anchor>>
560 where
561 T: ToOffset,
562 {
563 let mut folds = intersecting_folds(&self.buffer_snapshot, &self.folds, range, false);
564 iter::from_fn(move || {
565 let item = folds.item().map(|f| &f.0);
566 folds.next(&self.buffer_snapshot);
567 item
568 })
569 }
570
571 pub fn intersects_fold<T>(&self, offset: T) -> bool
572 where
573 T: ToOffset,
574 {
575 let offset = offset.to_offset(&self.buffer_snapshot);
576 let mut cursor = self.transforms.cursor::<usize>();
577 cursor.seek(&offset, Bias::Right, &());
578 cursor.item().map_or(false, |t| t.output_text.is_some())
579 }
580
581 pub fn is_line_folded(&self, output_row: u32) -> bool {
582 let mut cursor = self.transforms.cursor::<FoldPoint>();
583 cursor.seek(&FoldPoint::new(output_row, 0), Bias::Right, &());
584 while let Some(transform) = cursor.item() {
585 if transform.output_text.is_some() {
586 return true;
587 }
588 if cursor.end(&()).row() == output_row {
589 cursor.next(&())
590 } else {
591 break;
592 }
593 }
594 false
595 }
596
597 pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator<Item = char> {
598 let start = start.to_offset(self);
599 self.chunks(start..self.len(), None)
600 .flat_map(|chunk| chunk.text.chars())
601 }
602
603 pub fn chunks<'a>(
604 &'a self,
605 range: Range<FoldOffset>,
606 theme: Option<&'a SyntaxTheme>,
607 ) -> Chunks<'a> {
608 let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>();
609
610 transform_cursor.seek(&range.end, Bias::Right, &());
611 let overshoot = range.end.0 - transform_cursor.start().0 .0;
612 let buffer_end = transform_cursor.start().1 + overshoot;
613
614 transform_cursor.seek(&range.start, Bias::Right, &());
615 let overshoot = range.start.0 - transform_cursor.start().0 .0;
616 let buffer_start = transform_cursor.start().1 + overshoot;
617
618 Chunks {
619 transform_cursor,
620 buffer_chunks: self.buffer_snapshot.chunks(buffer_start..buffer_end, theme),
621 buffer_chunk: None,
622 buffer_offset: buffer_start,
623 output_offset: range.start.0,
624 max_output_offset: range.end.0,
625 }
626 }
627
628 #[cfg(test)]
629 pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset {
630 let mut cursor = self.transforms.cursor::<(FoldOffset, usize)>();
631 cursor.seek(&offset, Bias::Right, &());
632 if let Some(transform) = cursor.item() {
633 let transform_start = cursor.start().0 .0;
634 if transform.output_text.is_some() {
635 if offset.0 == transform_start || matches!(bias, Bias::Left) {
636 FoldOffset(transform_start)
637 } else {
638 FoldOffset(cursor.end(&()).0 .0)
639 }
640 } else {
641 let overshoot = offset.0 - transform_start;
642 let buffer_offset = cursor.start().1 + overshoot;
643 let clipped_buffer_offset = self.buffer_snapshot.clip_offset(buffer_offset, bias);
644 FoldOffset(
645 (offset.0 as isize + (clipped_buffer_offset as isize - buffer_offset as isize))
646 as usize,
647 )
648 }
649 } else {
650 FoldOffset(self.transforms.summary().output.bytes)
651 }
652 }
653
654 pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
655 let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>();
656 cursor.seek(&point, Bias::Right, &());
657 if let Some(transform) = cursor.item() {
658 let transform_start = cursor.start().0 .0;
659 if transform.output_text.is_some() {
660 if point.0 == transform_start || matches!(bias, Bias::Left) {
661 FoldPoint(transform_start)
662 } else {
663 FoldPoint(cursor.end(&()).0 .0)
664 }
665 } else {
666 let overshoot = point.0 - transform_start;
667 let buffer_position = cursor.start().1 + overshoot;
668 let clipped_buffer_position =
669 self.buffer_snapshot.clip_point(buffer_position, bias);
670 FoldPoint::new(
671 point.row(),
672 ((point.column() as i32) + clipped_buffer_position.column as i32
673 - buffer_position.column as i32) as u32,
674 )
675 }
676 } else {
677 FoldPoint(self.transforms.summary().output.lines)
678 }
679 }
680}
681
682fn intersecting_folds<'a, T>(
683 buffer: &'a text::Snapshot,
684 folds: &'a SumTree<Fold>,
685 range: Range<T>,
686 inclusive: bool,
687) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize>
688where
689 T: ToOffset,
690{
691 let start = buffer.anchor_before(range.start.to_offset(buffer));
692 let end = buffer.anchor_after(range.end.to_offset(buffer));
693 folds.filter::<_, usize>(
694 move |summary| {
695 let start_cmp = start.cmp(&summary.max_end, buffer).unwrap();
696 let end_cmp = end.cmp(&summary.min_start, buffer).unwrap();
697
698 if inclusive {
699 start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
700 } else {
701 start_cmp == Ordering::Less && end_cmp == Ordering::Greater
702 }
703 },
704 buffer,
705 )
706}
707
708fn consolidate_buffer_edits(edits: &mut Vec<text::Edit<usize>>) {
709 edits.sort_unstable_by(|a, b| {
710 a.old
711 .start
712 .cmp(&b.old.start)
713 .then_with(|| b.old.end.cmp(&a.old.end))
714 });
715
716 let mut i = 1;
717 while i < edits.len() {
718 let edit = edits[i].clone();
719 let prev_edit = &mut edits[i - 1];
720 if prev_edit.old.end >= edit.old.start {
721 prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
722 prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
723 prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
724 edits.remove(i);
725 continue;
726 }
727 i += 1;
728 }
729}
730
731fn consolidate_fold_edits(edits: &mut Vec<FoldEdit>) {
732 edits.sort_unstable_by(|a, b| {
733 a.old_bytes
734 .start
735 .cmp(&b.old_bytes.start)
736 .then_with(|| b.old_bytes.end.cmp(&a.old_bytes.end))
737 });
738
739 let mut i = 1;
740 while i < edits.len() {
741 let edit = edits[i].clone();
742 let prev_edit = &mut edits[i - 1];
743 if prev_edit.old_bytes.end >= edit.old_bytes.start {
744 prev_edit.old_bytes.end = prev_edit.old_bytes.end.max(edit.old_bytes.end);
745 prev_edit.new_bytes.start = prev_edit.new_bytes.start.min(edit.new_bytes.start);
746 prev_edit.new_bytes.end = prev_edit.new_bytes.end.max(edit.new_bytes.end);
747 edits.remove(i);
748 continue;
749 }
750 i += 1;
751 }
752}
753
754#[derive(Clone, Debug, Default, Eq, PartialEq)]
755struct Transform {
756 summary: TransformSummary,
757 output_text: Option<&'static str>,
758}
759
760impl Transform {
761 fn is_fold(&self) -> bool {
762 self.output_text.is_some()
763 }
764}
765
766#[derive(Clone, Debug, Default, Eq, PartialEq)]
767struct TransformSummary {
768 output: TextSummary,
769 input: TextSummary,
770}
771
772impl sum_tree::Item for Transform {
773 type Summary = TransformSummary;
774
775 fn summary(&self) -> Self::Summary {
776 self.summary.clone()
777 }
778}
779
780impl sum_tree::Summary for TransformSummary {
781 type Context = ();
782
783 fn add_summary(&mut self, other: &Self, _: &()) {
784 self.input += &other.input;
785 self.output += &other.output;
786 }
787}
788
789#[derive(Clone, Debug)]
790struct Fold(Range<Anchor>);
791
792impl Default for Fold {
793 fn default() -> Self {
794 Self(Anchor::min()..Anchor::max())
795 }
796}
797
798impl sum_tree::Item for Fold {
799 type Summary = FoldSummary;
800
801 fn summary(&self) -> Self::Summary {
802 FoldSummary {
803 start: self.0.start.clone(),
804 end: self.0.end.clone(),
805 min_start: self.0.start.clone(),
806 max_end: self.0.end.clone(),
807 count: 1,
808 }
809 }
810}
811
812#[derive(Clone, Debug)]
813struct FoldSummary {
814 start: Anchor,
815 end: Anchor,
816 min_start: Anchor,
817 max_end: Anchor,
818 count: usize,
819}
820
821impl Default for FoldSummary {
822 fn default() -> Self {
823 Self {
824 start: Anchor::min(),
825 end: Anchor::max(),
826 min_start: Anchor::max(),
827 max_end: Anchor::min(),
828 count: 0,
829 }
830 }
831}
832
833impl sum_tree::Summary for FoldSummary {
834 type Context = text::Snapshot;
835
836 fn add_summary(&mut self, other: &Self, buffer: &text::Snapshot) {
837 if other.min_start.cmp(&self.min_start, buffer).unwrap() == Ordering::Less {
838 self.min_start = other.min_start.clone();
839 }
840 if other.max_end.cmp(&self.max_end, buffer).unwrap() == Ordering::Greater {
841 self.max_end = other.max_end.clone();
842 }
843
844 #[cfg(debug_assertions)]
845 {
846 let start_comparison = self.start.cmp(&other.start, buffer).unwrap();
847 assert!(start_comparison <= Ordering::Equal);
848 if start_comparison == Ordering::Equal {
849 assert!(self.end.cmp(&other.end, buffer).unwrap() >= Ordering::Equal);
850 }
851 }
852
853 self.start = other.start.clone();
854 self.end = other.end.clone();
855 self.count += other.count;
856 }
857}
858
859impl<'a> sum_tree::Dimension<'a, FoldSummary> for Fold {
860 fn add_summary(&mut self, summary: &'a FoldSummary, _: &text::Snapshot) {
861 self.0.start = summary.start.clone();
862 self.0.end = summary.end.clone();
863 }
864}
865
866impl<'a> sum_tree::SeekTarget<'a, FoldSummary, Fold> for Fold {
867 fn cmp(&self, other: &Self, buffer: &text::Snapshot) -> Ordering {
868 self.0.cmp(&other.0, buffer).unwrap()
869 }
870}
871
872impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
873 fn add_summary(&mut self, summary: &'a FoldSummary, _: &text::Snapshot) {
874 *self += summary.count;
875 }
876}
877
878pub struct BufferRows<'a> {
879 cursor: Cursor<'a, Transform, (FoldPoint, Point)>,
880 fold_point: FoldPoint,
881}
882
883impl<'a> Iterator for BufferRows<'a> {
884 type Item = u32;
885
886 fn next(&mut self) -> Option<Self::Item> {
887 while self.fold_point > self.cursor.end(&()).0 {
888 self.cursor.next(&());
889 if self.cursor.item().is_none() {
890 // TODO: Return a bool from next?
891 break;
892 }
893 }
894
895 if self.cursor.item().is_some() {
896 let overshoot = self.fold_point.0 - self.cursor.start().0 .0;
897 let buffer_point = self.cursor.start().1 + overshoot;
898 *self.fold_point.row_mut() += 1;
899 Some(buffer_point.row)
900 } else {
901 None
902 }
903 }
904}
905
906pub struct Chunks<'a> {
907 transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>,
908 buffer_chunks: language::Chunks<'a>,
909 buffer_chunk: Option<(usize, Chunk<'a>)>,
910 buffer_offset: usize,
911 output_offset: usize,
912 max_output_offset: usize,
913}
914
915impl<'a> Iterator for Chunks<'a> {
916 type Item = Chunk<'a>;
917
918 fn next(&mut self) -> Option<Self::Item> {
919 if self.output_offset >= self.max_output_offset {
920 return None;
921 }
922
923 let transform = if let Some(item) = self.transform_cursor.item() {
924 item
925 } else {
926 return None;
927 };
928
929 // If we're in a fold, then return the fold's display text and
930 // advance the transform and buffer cursors to the end of the fold.
931 if let Some(output_text) = transform.output_text {
932 self.buffer_chunk.take();
933 self.buffer_offset += transform.summary.input.bytes;
934 self.buffer_chunks.seek(self.buffer_offset);
935
936 while self.buffer_offset >= self.transform_cursor.end(&()).1
937 && self.transform_cursor.item().is_some()
938 {
939 self.transform_cursor.next(&());
940 }
941
942 self.output_offset += output_text.len();
943 return Some(Chunk {
944 text: output_text,
945 highlight_style: None,
946 diagnostic: None,
947 });
948 }
949
950 // Retrieve a chunk from the current location in the buffer.
951 if self.buffer_chunk.is_none() {
952 let chunk_offset = self.buffer_chunks.offset();
953 self.buffer_chunk = self.buffer_chunks.next().map(|chunk| (chunk_offset, chunk));
954 }
955
956 // Otherwise, take a chunk from the buffer's text.
957 if let Some((chunk_offset, mut chunk)) = self.buffer_chunk {
958 let offset_in_chunk = self.buffer_offset - chunk_offset;
959 chunk.text = &chunk.text[offset_in_chunk..];
960
961 // Truncate the chunk so that it ends at the next fold.
962 let region_end = self.transform_cursor.end(&()).1 - self.buffer_offset;
963 if chunk.text.len() >= region_end {
964 chunk.text = &chunk.text[0..region_end];
965 self.transform_cursor.next(&());
966 } else {
967 self.buffer_chunk.take();
968 }
969
970 self.buffer_offset += chunk.text.len();
971 self.output_offset += chunk.text.len();
972 return Some(chunk);
973 }
974
975 None
976 }
977}
978
979impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
980 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
981 self.0 += &summary.output.lines;
982 }
983}
984
985#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
986pub struct FoldOffset(pub usize);
987
988impl FoldOffset {
989 pub fn to_point(&self, snapshot: &Snapshot) -> FoldPoint {
990 let mut cursor = snapshot
991 .transforms
992 .cursor::<(FoldOffset, TransformSummary)>();
993 cursor.seek(self, Bias::Right, &());
994 let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
995 Point::new(0, (self.0 - cursor.start().0 .0) as u32)
996 } else {
997 let buffer_offset = cursor.start().1.input.bytes + self.0 - cursor.start().0 .0;
998 let buffer_point = snapshot.buffer_snapshot.to_point(buffer_offset);
999 buffer_point - cursor.start().1.input.lines
1000 };
1001 FoldPoint(cursor.start().1.output.lines + overshoot)
1002 }
1003}
1004
1005impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
1006 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1007 self.0 += &summary.output.bytes;
1008 }
1009}
1010
1011impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
1012 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1013 *self += &summary.input.lines;
1014 }
1015}
1016
1017impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
1018 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1019 *self += &summary.input.bytes;
1020 }
1021}
1022
1023#[derive(Clone, Debug, PartialEq, Eq)]
1024pub struct FoldEdit {
1025 pub old_bytes: Range<FoldOffset>,
1026 pub new_bytes: Range<FoldOffset>,
1027}
1028
1029#[cfg(test)]
1030impl FoldEdit {
1031 pub fn delta(&self) -> isize {
1032 self.inserted_bytes() as isize - self.deleted_bytes() as isize
1033 }
1034
1035 pub fn deleted_bytes(&self) -> usize {
1036 self.old_bytes.end.0 - self.old_bytes.start.0
1037 }
1038
1039 pub fn inserted_bytes(&self) -> usize {
1040 self.new_bytes.end.0 - self.new_bytes.start.0
1041 }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046 use super::*;
1047 use crate::{test::sample_text, ToPoint};
1048 use rand::prelude::*;
1049 use std::{env, mem};
1050 use text::RandomCharIter;
1051 use Bias::{Left, Right};
1052
1053 #[gpui::test]
1054 fn test_basic_folds(cx: &mut gpui::MutableAppContext) {
1055 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
1056 let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
1057
1058 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1059 let (snapshot2, edits) = writer.fold(
1060 vec![
1061 Point::new(0, 2)..Point::new(2, 2),
1062 Point::new(2, 4)..Point::new(4, 1),
1063 ],
1064 cx.as_ref(),
1065 );
1066 assert_eq!(snapshot2.text(), "aa…cc…eeeee");
1067 assert_eq!(
1068 edits,
1069 &[
1070 FoldEdit {
1071 old_bytes: FoldOffset(2)..FoldOffset(16),
1072 new_bytes: FoldOffset(2)..FoldOffset(5),
1073 },
1074 FoldEdit {
1075 old_bytes: FoldOffset(18)..FoldOffset(29),
1076 new_bytes: FoldOffset(7)..FoldOffset(10)
1077 },
1078 ]
1079 );
1080
1081 let edits = buffer.update(cx, |buffer, cx| {
1082 let v0 = buffer.version();
1083 buffer.edit(
1084 vec![
1085 Point::new(0, 0)..Point::new(0, 1),
1086 Point::new(2, 3)..Point::new(2, 3),
1087 ],
1088 "123",
1089 cx,
1090 );
1091 buffer.edits_since(&v0).collect()
1092 });
1093 let (snapshot3, edits) = map.read(edits, cx.as_ref());
1094 assert_eq!(snapshot3.text(), "123a…c123c…eeeee");
1095 assert_eq!(
1096 edits,
1097 &[
1098 FoldEdit {
1099 old_bytes: FoldOffset(0)..FoldOffset(1),
1100 new_bytes: FoldOffset(0)..FoldOffset(3),
1101 },
1102 FoldEdit {
1103 old_bytes: FoldOffset(6)..FoldOffset(6),
1104 new_bytes: FoldOffset(8)..FoldOffset(11),
1105 },
1106 ]
1107 );
1108
1109 let edits = buffer.update(cx, |buffer, cx| {
1110 let v0 = buffer.version();
1111 buffer.edit(vec![Point::new(2, 6)..Point::new(4, 3)], "456", cx);
1112 buffer.edits_since(&v0).collect()
1113 });
1114 let (snapshot4, _) = map.read(edits, cx.as_ref());
1115 assert_eq!(snapshot4.text(), "123a…c123456eee");
1116
1117 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1118 writer.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), cx.as_ref());
1119 let (snapshot5, _) = map.read(vec![], cx.as_ref());
1120 assert_eq!(snapshot5.text(), "123aaaaa\nbbbbbb\nccc123456eee");
1121 }
1122
1123 #[gpui::test]
1124 fn test_adjacent_folds(cx: &mut gpui::MutableAppContext) {
1125 let buffer = cx.add_model(|cx| Buffer::new(0, "abcdefghijkl", cx));
1126
1127 {
1128 let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
1129
1130 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1131 writer.fold(vec![5..8], cx.as_ref());
1132 let (snapshot, _) = map.read(vec![], cx.as_ref());
1133 assert_eq!(snapshot.text(), "abcde…ijkl");
1134
1135 // Create an fold adjacent to the start of the first fold.
1136 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1137 writer.fold(vec![0..1, 2..5], cx.as_ref());
1138 let (snapshot, _) = map.read(vec![], cx.as_ref());
1139 assert_eq!(snapshot.text(), "…b…ijkl");
1140
1141 // Create an fold adjacent to the end of the first fold.
1142 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1143 writer.fold(vec![11..11, 8..10], cx.as_ref());
1144 let (snapshot, _) = map.read(vec![], cx.as_ref());
1145 assert_eq!(snapshot.text(), "…b…kl");
1146 }
1147
1148 {
1149 let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
1150
1151 // Create two adjacent folds.
1152 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1153 writer.fold(vec![0..2, 2..5], cx.as_ref());
1154 let (snapshot, _) = map.read(vec![], cx.as_ref());
1155 assert_eq!(snapshot.text(), "…fghijkl");
1156
1157 // Edit within one of the folds.
1158 let edits = buffer.update(cx, |buffer, cx| {
1159 let v0 = buffer.version();
1160 buffer.edit(vec![0..1], "12345", cx);
1161 buffer.edits_since(&v0).collect()
1162 });
1163 let (snapshot, _) = map.read(edits, cx.as_ref());
1164 assert_eq!(snapshot.text(), "12345…fghijkl");
1165 }
1166 }
1167
1168 #[gpui::test]
1169 fn test_overlapping_folds(cx: &mut gpui::MutableAppContext) {
1170 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
1171 let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
1172 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1173 writer.fold(
1174 vec![
1175 Point::new(0, 2)..Point::new(2, 2),
1176 Point::new(0, 4)..Point::new(1, 0),
1177 Point::new(1, 2)..Point::new(3, 2),
1178 Point::new(3, 1)..Point::new(4, 1),
1179 ],
1180 cx.as_ref(),
1181 );
1182 let (snapshot, _) = map.read(vec![], cx.as_ref());
1183 assert_eq!(snapshot.text(), "aa…eeeee");
1184 }
1185
1186 #[gpui::test]
1187 fn test_merging_folds_via_edit(cx: &mut gpui::MutableAppContext) {
1188 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
1189 let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
1190
1191 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1192 writer.fold(
1193 vec![
1194 Point::new(0, 2)..Point::new(2, 2),
1195 Point::new(3, 1)..Point::new(4, 1),
1196 ],
1197 cx.as_ref(),
1198 );
1199 let (snapshot, _) = map.read(vec![], cx.as_ref());
1200 assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee");
1201
1202 let edits = buffer.update(cx, |buffer, cx| {
1203 let v0 = buffer.version();
1204 buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", cx);
1205 buffer.edits_since(&v0).collect()
1206 });
1207 let (snapshot, _) = map.read(edits, cx.as_ref());
1208 assert_eq!(snapshot.text(), "aa…eeeee");
1209 }
1210
1211 #[gpui::test]
1212 fn test_folds_in_range(cx: &mut gpui::MutableAppContext) {
1213 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(5, 6), cx));
1214 let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
1215 let buffer = buffer.read(cx);
1216
1217 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1218 writer.fold(
1219 vec![
1220 Point::new(0, 2)..Point::new(2, 2),
1221 Point::new(0, 4)..Point::new(1, 0),
1222 Point::new(1, 2)..Point::new(3, 2),
1223 Point::new(3, 1)..Point::new(4, 1),
1224 ],
1225 cx.as_ref(),
1226 );
1227 let (snapshot, _) = map.read(vec![], cx.as_ref());
1228 let fold_ranges = snapshot
1229 .folds_in_range(Point::new(1, 0)..Point::new(1, 3))
1230 .map(|fold| fold.start.to_point(buffer)..fold.end.to_point(buffer))
1231 .collect::<Vec<_>>();
1232 assert_eq!(
1233 fold_ranges,
1234 vec![
1235 Point::new(0, 2)..Point::new(2, 2),
1236 Point::new(1, 2)..Point::new(3, 2)
1237 ]
1238 );
1239 }
1240
1241 #[gpui::test(iterations = 100)]
1242 fn test_random_folds(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
1243 let operations = env::var("OPERATIONS")
1244 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1245 .unwrap_or(10);
1246
1247 let buffer = cx.add_model(|cx| {
1248 let len = rng.gen_range(0..10);
1249 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1250 Buffer::new(0, text, cx)
1251 });
1252 let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
1253
1254 let (mut initial_snapshot, _) = map.read(vec![], cx.as_ref());
1255 let mut snapshot_edits = Vec::new();
1256
1257 for _ in 0..operations {
1258 log::info!("text: {:?}", buffer.read(cx).text());
1259 let buffer_edits = match rng.gen_range(0..=100) {
1260 0..=59 => {
1261 snapshot_edits.extend(map.randomly_mutate(&mut rng, cx.as_ref()));
1262 vec![]
1263 }
1264 _ => buffer.update(cx, |buffer, _| {
1265 let start_version = buffer.version.clone();
1266 let edit_count = rng.gen_range(1..=5);
1267 buffer.randomly_edit(&mut rng, edit_count);
1268 let edits = buffer
1269 .edits_since::<Point>(&start_version)
1270 .collect::<Vec<_>>();
1271 log::info!("editing {:?}", edits);
1272 buffer.edits_since::<usize>(&start_version).collect()
1273 }),
1274 };
1275
1276 let buffer = map.buffer.read(cx).snapshot();
1277 let mut expected_text: String = buffer.text().to_string();
1278 let mut expected_buffer_rows = Vec::new();
1279 let mut next_row = buffer.max_point().row;
1280 for fold_range in map.merged_fold_ranges(cx.as_ref()).into_iter().rev() {
1281 let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
1282 let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
1283 expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
1284 next_row = fold_start.row;
1285
1286 expected_text.replace_range(fold_range.start..fold_range.end, "…");
1287 }
1288 expected_buffer_rows.extend((0..=next_row).rev());
1289 expected_buffer_rows.reverse();
1290
1291 let (snapshot, edits) = map.read(buffer_edits, cx.as_ref());
1292 assert_eq!(snapshot.text(), expected_text);
1293 snapshot_edits.push((snapshot.clone(), edits));
1294
1295 for (output_row, line) in expected_text.lines().enumerate() {
1296 let line_len = snapshot.line_len(output_row as u32);
1297 assert_eq!(line_len, line.len() as u32);
1298 }
1299
1300 let longest_row = snapshot.longest_row();
1301 let longest_char_column = expected_text
1302 .split('\n')
1303 .nth(longest_row as usize)
1304 .unwrap()
1305 .chars()
1306 .count();
1307 let mut fold_point = FoldPoint::new(0, 0);
1308 let mut fold_offset = FoldOffset(0);
1309 let mut char_column = 0;
1310 for c in expected_text.chars() {
1311 let buffer_point = fold_point.to_buffer_point(&snapshot);
1312 let buffer_offset = buffer_point.to_offset(&buffer);
1313 assert_eq!(
1314 buffer_point.to_fold_point(&snapshot, Right),
1315 fold_point,
1316 "{:?} -> fold point",
1317 buffer_point,
1318 );
1319 assert_eq!(
1320 fold_point.to_buffer_offset(&snapshot),
1321 buffer_offset,
1322 "fold_point.to_buffer_offset({:?})",
1323 fold_point,
1324 );
1325 assert_eq!(
1326 fold_point.to_offset(&snapshot),
1327 fold_offset,
1328 "fold_point.to_offset({:?})",
1329 fold_point,
1330 );
1331
1332 if c == '\n' {
1333 *fold_point.row_mut() += 1;
1334 *fold_point.column_mut() = 0;
1335 char_column = 0;
1336 } else {
1337 *fold_point.column_mut() += c.len_utf8() as u32;
1338 char_column += 1;
1339 }
1340 fold_offset.0 += c.len_utf8();
1341 if char_column > longest_char_column {
1342 panic!(
1343 "invalid longest row {:?} (chars {}), found row {:?} (chars: {})",
1344 longest_row,
1345 longest_char_column,
1346 fold_point.row(),
1347 char_column
1348 );
1349 }
1350 }
1351
1352 for _ in 0..5 {
1353 let mut start = snapshot
1354 .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Left);
1355 let mut end = snapshot
1356 .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right);
1357 if start > end {
1358 mem::swap(&mut start, &mut end);
1359 }
1360
1361 let text = &expected_text[start.0..end.0];
1362 log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text);
1363 assert_eq!(
1364 snapshot
1365 .chunks(start..end, None)
1366 .map(|c| c.text)
1367 .collect::<String>(),
1368 text,
1369 );
1370 }
1371
1372 for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
1373 let fold_row = Point::new(*buffer_row, 0)
1374 .to_fold_point(&snapshot, Right)
1375 .row();
1376 assert_eq!(
1377 snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
1378 expected_buffer_rows[idx..],
1379 );
1380 }
1381
1382 for fold_range in map.merged_fold_ranges(cx.as_ref()) {
1383 let fold_point = fold_range
1384 .start
1385 .to_point(&buffer)
1386 .to_fold_point(&snapshot, Right);
1387 assert!(snapshot.is_line_folded(fold_point.row()));
1388 }
1389
1390 for _ in 0..5 {
1391 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1392 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1393 let expected_folds = map
1394 .folds
1395 .items(&buffer)
1396 .into_iter()
1397 .filter(|fold| {
1398 let start = buffer.anchor_before(start);
1399 let end = buffer.anchor_after(end);
1400 start.cmp(&fold.0.end, &buffer).unwrap() == Ordering::Less
1401 && end.cmp(&fold.0.start, &buffer).unwrap() == Ordering::Greater
1402 })
1403 .map(|fold| fold.0)
1404 .collect::<Vec<_>>();
1405
1406 assert_eq!(
1407 snapshot
1408 .folds_in_range(start..end)
1409 .cloned()
1410 .collect::<Vec<_>>(),
1411 expected_folds
1412 );
1413 }
1414
1415 let text = snapshot.text();
1416 for _ in 0..5 {
1417 let start_row = rng.gen_range(0..=snapshot.max_point().row());
1418 let start_column = rng.gen_range(0..=snapshot.line_len(start_row));
1419 let end_row = rng.gen_range(0..=snapshot.max_point().row());
1420 let end_column = rng.gen_range(0..=snapshot.line_len(end_row));
1421 let mut start =
1422 snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left);
1423 let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right);
1424 if start > end {
1425 mem::swap(&mut start, &mut end);
1426 }
1427
1428 let lines = start..end;
1429 let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot);
1430 assert_eq!(
1431 snapshot.text_summary_for_range(lines),
1432 TextSummary::from(&text[bytes.start.0..bytes.end.0])
1433 )
1434 }
1435
1436 let mut text = initial_snapshot.text();
1437 for (snapshot, edits) in snapshot_edits.drain(..) {
1438 let new_text = snapshot.text();
1439 let mut delta = 0isize;
1440 for edit in edits {
1441 let old_bytes = ((edit.old_bytes.start.0 as isize) + delta) as usize
1442 ..((edit.old_bytes.end.0 as isize) + delta) as usize;
1443 let new_bytes = edit.new_bytes.start.0..edit.new_bytes.end.0;
1444 delta += edit.delta();
1445 text.replace_range(old_bytes, &new_text[new_bytes]);
1446 }
1447
1448 assert_eq!(text, new_text);
1449 initial_snapshot = snapshot;
1450 }
1451 }
1452 }
1453
1454 #[gpui::test]
1455 fn test_buffer_rows(cx: &mut gpui::MutableAppContext) {
1456 let text = sample_text(6, 6) + "\n";
1457 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
1458
1459 let mut map = FoldMap::new(buffer.clone(), cx.as_ref()).0;
1460
1461 let (mut writer, _, _) = map.write(vec![], cx.as_ref());
1462 writer.fold(
1463 vec![
1464 Point::new(0, 2)..Point::new(2, 2),
1465 Point::new(3, 1)..Point::new(4, 1),
1466 ],
1467 cx.as_ref(),
1468 );
1469
1470 let (snapshot, _) = map.read(vec![], cx.as_ref());
1471 assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee\nffffff\n");
1472 assert_eq!(snapshot.buffer_rows(0).collect::<Vec<_>>(), [0, 3, 5, 6]);
1473 assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [6]);
1474 }
1475
1476 impl FoldMap {
1477 fn merged_fold_ranges(&self, cx: &AppContext) -> Vec<Range<usize>> {
1478 let buffer = self.buffer.read(cx).snapshot();
1479 let mut folds = self.folds.items(&buffer);
1480 // Ensure sorting doesn't change how folds get merged and displayed.
1481 folds.sort_by(|a, b| a.0.cmp(&b.0, &buffer).unwrap());
1482 let mut fold_ranges = folds
1483 .iter()
1484 .map(|fold| fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer))
1485 .peekable();
1486
1487 let mut merged_ranges = Vec::new();
1488 while let Some(mut fold_range) = fold_ranges.next() {
1489 while let Some(next_range) = fold_ranges.peek() {
1490 if fold_range.end >= next_range.start {
1491 if next_range.end > fold_range.end {
1492 fold_range.end = next_range.end;
1493 }
1494 fold_ranges.next();
1495 } else {
1496 break;
1497 }
1498 }
1499 if fold_range.end > fold_range.start {
1500 merged_ranges.push(fold_range);
1501 }
1502 }
1503 merged_ranges
1504 }
1505
1506 pub fn randomly_mutate(
1507 &mut self,
1508 rng: &mut impl Rng,
1509 cx: &AppContext,
1510 ) -> Vec<(Snapshot, Vec<FoldEdit>)> {
1511 let mut snapshot_edits = Vec::new();
1512 match rng.gen_range(0..=100) {
1513 0..=39 if !self.folds.is_empty() => {
1514 let buffer = self.buffer.read(cx);
1515 let mut to_unfold = Vec::new();
1516 for _ in 0..rng.gen_range(1..=3) {
1517 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1518 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1519 to_unfold.push(start..end);
1520 }
1521 log::info!("unfolding {:?}", to_unfold);
1522 let (mut writer, snapshot, edits) = self.write(vec![], cx.as_ref());
1523 snapshot_edits.push((snapshot, edits));
1524 let (snapshot, edits) = writer.fold(to_unfold, cx.as_ref());
1525 snapshot_edits.push((snapshot, edits));
1526 }
1527 _ => {
1528 let buffer = self.buffer.read(cx);
1529 let mut to_fold = Vec::new();
1530 for _ in 0..rng.gen_range(1..=2) {
1531 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1532 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1533 to_fold.push(start..end);
1534 }
1535 log::info!("folding {:?}", to_fold);
1536 let (mut writer, snapshot, edits) = self.write(vec![], cx.as_ref());
1537 snapshot_edits.push((snapshot, edits));
1538 let (snapshot, edits) = writer.fold(to_fold, cx.as_ref());
1539 snapshot_edits.push((snapshot, edits));
1540 }
1541 }
1542 snapshot_edits
1543 }
1544 }
1545}