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