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