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