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