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: &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
854 if let Some(mut first_edit) = inlay_edits.next() {
855 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
856 #[allow(clippy::filter_map_identity)]
857 let mut v: Vec<_> = inlay_edits
858 .scan(&mut first_edit, |prev_edit, edit| {
859 if prev_edit.old.end >= edit.old.start {
860 prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
861 prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
862 prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
863 Some(None) // Skip this edit, it's merged
864 } else {
865 let prev = std::mem::replace(*prev_edit, edit);
866 Some(Some(prev)) // Yield the previous edit
867 }
868 })
869 .filter_map(|x| x)
870 .collect();
871 v.push(first_edit.clone());
872 debug_assert_eq!(_old_alloc_ptr, v.as_ptr(), "Inlay edits were reallocated");
873 v
874 } else {
875 vec![]
876 }
877}
878
879fn consolidate_fold_edits(mut edits: Vec<FoldEdit>) -> Vec<FoldEdit> {
880 edits.sort_unstable_by(|a, b| {
881 a.old
882 .start
883 .cmp(&b.old.start)
884 .then_with(|| b.old.end.cmp(&a.old.end))
885 });
886 let _old_alloc_ptr = edits.as_ptr();
887 let mut fold_edits = edits.into_iter();
888
889 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
913#[derive(Clone, Debug, Default)]
914struct Transform {
915 summary: TransformSummary,
916 placeholder: Option<TransformPlaceholder>,
917}
918
919#[derive(Clone, Debug)]
920struct TransformPlaceholder {
921 text: &'static str,
922 renderer: ChunkRenderer,
923}
924
925impl Transform {
926 fn is_fold(&self) -> bool {
927 self.placeholder.is_some()
928 }
929}
930
931#[derive(Clone, Debug, Default, Eq, PartialEq)]
932struct TransformSummary {
933 output: TextSummary,
934 input: TextSummary,
935}
936
937impl sum_tree::Item for Transform {
938 type Summary = TransformSummary;
939
940 fn summary(&self) -> Self::Summary {
941 self.summary.clone()
942 }
943}
944
945impl sum_tree::Summary for TransformSummary {
946 type Context = ();
947
948 fn add_summary(&mut self, other: &Self, _: &()) {
949 self.input += &other.input;
950 self.output += &other.output;
951 }
952}
953
954#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
955pub struct FoldId(usize);
956
957impl From<FoldId> for ElementId {
958 fn from(val: FoldId) -> Self {
959 ElementId::Integer(val.0)
960 }
961}
962
963#[derive(Clone, Debug, Eq, PartialEq)]
964pub struct Fold {
965 pub id: FoldId,
966 pub range: FoldRange,
967 pub placeholder: FoldPlaceholder,
968}
969
970#[derive(Clone, Debug, Eq, PartialEq)]
971pub struct FoldRange(Range<Anchor>);
972
973impl Deref for FoldRange {
974 type Target = Range<Anchor>;
975
976 fn deref(&self) -> &Self::Target {
977 &self.0
978 }
979}
980
981impl DerefMut for FoldRange {
982 fn deref_mut(&mut self) -> &mut Self::Target {
983 &mut self.0
984 }
985}
986
987impl Default for FoldRange {
988 fn default() -> Self {
989 Self(Anchor::min()..Anchor::max())
990 }
991}
992
993impl sum_tree::Item for Fold {
994 type Summary = FoldSummary;
995
996 fn summary(&self) -> Self::Summary {
997 FoldSummary {
998 start: self.range.start,
999 end: self.range.end,
1000 min_start: self.range.start,
1001 max_end: self.range.end,
1002 count: 1,
1003 }
1004 }
1005}
1006
1007#[derive(Clone, Debug)]
1008pub struct FoldSummary {
1009 start: Anchor,
1010 end: Anchor,
1011 min_start: Anchor,
1012 max_end: Anchor,
1013 count: usize,
1014}
1015
1016impl Default for FoldSummary {
1017 fn default() -> Self {
1018 Self {
1019 start: Anchor::min(),
1020 end: Anchor::max(),
1021 min_start: Anchor::max(),
1022 max_end: Anchor::min(),
1023 count: 0,
1024 }
1025 }
1026}
1027
1028impl sum_tree::Summary for FoldSummary {
1029 type Context = MultiBufferSnapshot;
1030
1031 fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
1032 if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
1033 self.min_start = other.min_start;
1034 }
1035 if other.max_end.cmp(&self.max_end, buffer) == Ordering::Greater {
1036 self.max_end = other.max_end;
1037 }
1038
1039 #[cfg(debug_assertions)]
1040 {
1041 let start_comparison = self.start.cmp(&other.start, buffer);
1042 assert!(start_comparison <= Ordering::Equal);
1043 if start_comparison == Ordering::Equal {
1044 assert!(self.end.cmp(&other.end, buffer) >= Ordering::Equal);
1045 }
1046 }
1047
1048 self.start = other.start;
1049 self.end = other.end;
1050 self.count += other.count;
1051 }
1052}
1053
1054impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
1055 fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
1056 self.0.start = summary.start;
1057 self.0.end = summary.end;
1058 }
1059}
1060
1061impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
1062 fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
1063 AnchorRangeExt::cmp(&self.0, &other.0, buffer)
1064 }
1065}
1066
1067impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
1068 fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
1069 *self += summary.count;
1070 }
1071}
1072
1073#[derive(Clone)]
1074pub struct FoldBufferRows<'a> {
1075 cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>,
1076 input_buffer_rows: InlayBufferRows<'a>,
1077 fold_point: FoldPoint,
1078}
1079
1080impl<'a> Iterator for FoldBufferRows<'a> {
1081 type Item = Option<u32>;
1082
1083 fn next(&mut self) -> Option<Self::Item> {
1084 let mut traversed_fold = false;
1085 while self.fold_point > self.cursor.end(&()).0 {
1086 self.cursor.next(&());
1087 traversed_fold = true;
1088 if self.cursor.item().is_none() {
1089 break;
1090 }
1091 }
1092
1093 if self.cursor.item().is_some() {
1094 if traversed_fold {
1095 self.input_buffer_rows.seek(self.cursor.start().1.row());
1096 self.input_buffer_rows.next();
1097 }
1098 *self.fold_point.row_mut() += 1;
1099 self.input_buffer_rows.next()
1100 } else {
1101 None
1102 }
1103 }
1104}
1105
1106pub struct FoldChunks<'a> {
1107 transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>,
1108 inlay_chunks: InlayChunks<'a>,
1109 inlay_chunk: Option<(InlayOffset, Chunk<'a>)>,
1110 inlay_offset: InlayOffset,
1111 output_offset: FoldOffset,
1112 max_output_offset: FoldOffset,
1113}
1114
1115impl<'a> Iterator for FoldChunks<'a> {
1116 type Item = Chunk<'a>;
1117
1118 fn next(&mut self) -> Option<Self::Item> {
1119 if self.output_offset >= self.max_output_offset {
1120 return None;
1121 }
1122
1123 let transform = self.transform_cursor.item()?;
1124
1125 // If we're in a fold, then return the fold's display text and
1126 // advance the transform and buffer cursors to the end of the fold.
1127 if let Some(placeholder) = transform.placeholder.as_ref() {
1128 self.inlay_chunk.take();
1129 self.inlay_offset += InlayOffset(transform.summary.input.len);
1130
1131 while self.inlay_offset >= self.transform_cursor.end(&()).1
1132 && self.transform_cursor.item().is_some()
1133 {
1134 self.transform_cursor.next(&());
1135 }
1136
1137 self.output_offset.0 += placeholder.text.len();
1138 return Some(Chunk {
1139 text: placeholder.text,
1140 renderer: Some(placeholder.renderer.clone()),
1141 ..Default::default()
1142 });
1143 }
1144
1145 // When we reach a non-fold region, seek the underlying text
1146 // chunk iterator to the next unfolded range.
1147 if self.inlay_offset == self.transform_cursor.start().1
1148 && self.inlay_chunks.offset() != self.inlay_offset
1149 {
1150 let transform_start = self.transform_cursor.start();
1151 let transform_end = self.transform_cursor.end(&());
1152 let inlay_end = if self.max_output_offset < transform_end.0 {
1153 let overshoot = self.max_output_offset.0 - transform_start.0 .0;
1154 transform_start.1 + InlayOffset(overshoot)
1155 } else {
1156 transform_end.1
1157 };
1158
1159 self.inlay_chunks.seek(self.inlay_offset..inlay_end);
1160 }
1161
1162 // Retrieve a chunk from the current location in the buffer.
1163 if self.inlay_chunk.is_none() {
1164 let chunk_offset = self.inlay_chunks.offset();
1165 self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk));
1166 }
1167
1168 // Otherwise, take a chunk from the buffer's text.
1169 if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() {
1170 let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
1171 let transform_end = self.transform_cursor.end(&()).1;
1172 let chunk_end = buffer_chunk_end.min(transform_end);
1173
1174 chunk.text = &chunk.text
1175 [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
1176
1177 if chunk_end == transform_end {
1178 self.transform_cursor.next(&());
1179 } else if chunk_end == buffer_chunk_end {
1180 self.inlay_chunk.take();
1181 }
1182
1183 self.inlay_offset = chunk_end;
1184 self.output_offset.0 += chunk.text.len();
1185 return Some(chunk);
1186 }
1187
1188 None
1189 }
1190}
1191
1192#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
1193pub struct FoldOffset(pub usize);
1194
1195impl FoldOffset {
1196 pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
1197 let mut cursor = snapshot
1198 .transforms
1199 .cursor::<(FoldOffset, TransformSummary)>();
1200 cursor.seek(&self, Bias::Right, &());
1201 let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
1202 Point::new(0, (self.0 - cursor.start().0 .0) as u32)
1203 } else {
1204 let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0;
1205 let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset));
1206 inlay_point.0 - cursor.start().1.input.lines
1207 };
1208 FoldPoint(cursor.start().1.output.lines + overshoot)
1209 }
1210
1211 #[cfg(test)]
1212 pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
1213 let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>();
1214 cursor.seek(&self, Bias::Right, &());
1215 let overshoot = self.0 - cursor.start().0 .0;
1216 InlayOffset(cursor.start().1 .0 + overshoot)
1217 }
1218}
1219
1220impl Add for FoldOffset {
1221 type Output = Self;
1222
1223 fn add(self, rhs: Self) -> Self::Output {
1224 Self(self.0 + rhs.0)
1225 }
1226}
1227
1228impl AddAssign for FoldOffset {
1229 fn add_assign(&mut self, rhs: Self) {
1230 self.0 += rhs.0;
1231 }
1232}
1233
1234impl Sub for FoldOffset {
1235 type Output = Self;
1236
1237 fn sub(self, rhs: Self) -> Self::Output {
1238 Self(self.0 - rhs.0)
1239 }
1240}
1241
1242impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
1243 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1244 self.0 += &summary.output.len;
1245 }
1246}
1247
1248impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
1249 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1250 self.0 += &summary.input.lines;
1251 }
1252}
1253
1254impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
1255 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1256 self.0 += &summary.input.len;
1257 }
1258}
1259
1260pub type FoldEdit = Edit<FoldOffset>;
1261
1262#[cfg(test)]
1263mod tests {
1264 use super::*;
1265 use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint};
1266 use collections::HashSet;
1267 use rand::prelude::*;
1268 use settings::SettingsStore;
1269 use std::{env, mem};
1270 use text::Patch;
1271 use util::test::sample_text;
1272 use util::RandomCharIter;
1273 use Bias::{Left, Right};
1274
1275 #[gpui::test]
1276 fn test_basic_folds(cx: &mut gpui::AppContext) {
1277 init_test(cx);
1278 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1279 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1280 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1281 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1282 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1283
1284 let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
1285 let (snapshot2, edits) = writer.fold(vec![
1286 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1287 (Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()),
1288 ]);
1289 assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
1290 assert_eq!(
1291 edits,
1292 &[
1293 FoldEdit {
1294 old: FoldOffset(2)..FoldOffset(16),
1295 new: FoldOffset(2)..FoldOffset(5),
1296 },
1297 FoldEdit {
1298 old: FoldOffset(18)..FoldOffset(29),
1299 new: FoldOffset(7)..FoldOffset(10)
1300 },
1301 ]
1302 );
1303
1304 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1305 buffer.edit(
1306 vec![
1307 (Point::new(0, 0)..Point::new(0, 1), "123"),
1308 (Point::new(2, 3)..Point::new(2, 3), "123"),
1309 ],
1310 None,
1311 cx,
1312 );
1313 buffer.snapshot(cx)
1314 });
1315
1316 let (inlay_snapshot, inlay_edits) =
1317 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1318 let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
1319 assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
1320 assert_eq!(
1321 edits,
1322 &[
1323 FoldEdit {
1324 old: FoldOffset(0)..FoldOffset(1),
1325 new: FoldOffset(0)..FoldOffset(3),
1326 },
1327 FoldEdit {
1328 old: FoldOffset(6)..FoldOffset(6),
1329 new: FoldOffset(8)..FoldOffset(11),
1330 },
1331 ]
1332 );
1333
1334 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1335 buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
1336 buffer.snapshot(cx)
1337 });
1338 let (inlay_snapshot, inlay_edits) =
1339 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1340 let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
1341 assert_eq!(snapshot4.text(), "123a⋯c123456eee");
1342
1343 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1344 writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false);
1345 let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]);
1346 assert_eq!(snapshot5.text(), "123a⋯c123456eee");
1347
1348 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1349 writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true);
1350 let (snapshot6, _) = map.read(inlay_snapshot, vec![]);
1351 assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee");
1352 }
1353
1354 #[gpui::test]
1355 fn test_adjacent_folds(cx: &mut gpui::AppContext) {
1356 init_test(cx);
1357 let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
1358 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1359 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1360 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1361
1362 {
1363 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1364
1365 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1366 writer.fold(vec![(5..8, FoldPlaceholder::test())]);
1367 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1368 assert_eq!(snapshot.text(), "abcde⋯ijkl");
1369
1370 // Create an fold adjacent to the start of the first fold.
1371 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1372 writer.fold(vec![
1373 (0..1, FoldPlaceholder::test()),
1374 (2..5, FoldPlaceholder::test()),
1375 ]);
1376 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1377 assert_eq!(snapshot.text(), "⋯b⋯ijkl");
1378
1379 // Create an fold adjacent to the end of the first fold.
1380 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1381 writer.fold(vec![
1382 (11..11, FoldPlaceholder::test()),
1383 (8..10, FoldPlaceholder::test()),
1384 ]);
1385 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1386 assert_eq!(snapshot.text(), "⋯b⋯kl");
1387 }
1388
1389 {
1390 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1391
1392 // Create two adjacent folds.
1393 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1394 writer.fold(vec![
1395 (0..2, FoldPlaceholder::test()),
1396 (2..5, FoldPlaceholder::test()),
1397 ]);
1398 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1399 assert_eq!(snapshot.text(), "⋯fghijkl");
1400
1401 // Edit within one of the folds.
1402 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1403 buffer.edit([(0..1, "12345")], None, cx);
1404 buffer.snapshot(cx)
1405 });
1406 let (inlay_snapshot, inlay_edits) =
1407 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1408 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1409 assert_eq!(snapshot.text(), "12345⋯fghijkl");
1410 }
1411 }
1412
1413 #[gpui::test]
1414 fn test_overlapping_folds(cx: &mut gpui::AppContext) {
1415 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1416 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1417 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1418 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1419 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1420 writer.fold(vec![
1421 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1422 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1423 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1424 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1425 ]);
1426 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1427 assert_eq!(snapshot.text(), "aa⋯eeeee");
1428 }
1429
1430 #[gpui::test]
1431 fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) {
1432 init_test(cx);
1433 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1434 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1435 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1436 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1437 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1438
1439 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1440 writer.fold(vec![
1441 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1442 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1443 ]);
1444 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1445 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
1446
1447 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1448 buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
1449 buffer.snapshot(cx)
1450 });
1451 let (inlay_snapshot, inlay_edits) =
1452 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1453 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1454 assert_eq!(snapshot.text(), "aa⋯eeeee");
1455 }
1456
1457 #[gpui::test]
1458 fn test_folds_in_range(cx: &mut gpui::AppContext) {
1459 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1460 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1461 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1462 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1463
1464 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1465 writer.fold(vec![
1466 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1467 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1468 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1469 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1470 ]);
1471 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1472 let fold_ranges = snapshot
1473 .folds_in_range(Point::new(1, 0)..Point::new(1, 3))
1474 .map(|fold| {
1475 fold.range.start.to_point(&buffer_snapshot)
1476 ..fold.range.end.to_point(&buffer_snapshot)
1477 })
1478 .collect::<Vec<_>>();
1479 assert_eq!(
1480 fold_ranges,
1481 vec![
1482 Point::new(0, 2)..Point::new(2, 2),
1483 Point::new(1, 2)..Point::new(3, 2)
1484 ]
1485 );
1486 }
1487
1488 #[gpui::test(iterations = 100)]
1489 fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) {
1490 init_test(cx);
1491 let operations = env::var("OPERATIONS")
1492 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1493 .unwrap_or(10);
1494
1495 let len = rng.gen_range(0..10);
1496 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1497 let buffer = if rng.gen() {
1498 MultiBuffer::build_simple(&text, cx)
1499 } else {
1500 MultiBuffer::build_random(&mut rng, cx)
1501 };
1502 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1503 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1504 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1505
1506 let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1507 let mut snapshot_edits = Vec::new();
1508
1509 let mut next_inlay_id = 0;
1510 for _ in 0..operations {
1511 log::info!("text: {:?}", buffer_snapshot.text());
1512 let mut buffer_edits = Vec::new();
1513 let mut inlay_edits = Vec::new();
1514 match rng.gen_range(0..=100) {
1515 0..=39 => {
1516 snapshot_edits.extend(map.randomly_mutate(&mut rng));
1517 }
1518 40..=59 => {
1519 let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1520 inlay_edits = edits;
1521 }
1522 _ => buffer.update(cx, |buffer, cx| {
1523 let subscription = buffer.subscribe();
1524 let edit_count = rng.gen_range(1..=5);
1525 buffer.randomly_mutate(&mut rng, edit_count, cx);
1526 buffer_snapshot = buffer.snapshot(cx);
1527 let edits = subscription.consume().into_inner();
1528 log::info!("editing {:?}", edits);
1529 buffer_edits.extend(edits);
1530 }),
1531 };
1532
1533 let (inlay_snapshot, new_inlay_edits) =
1534 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1535 log::info!("inlay text {:?}", inlay_snapshot.text());
1536
1537 let inlay_edits = Patch::new(inlay_edits)
1538 .compose(new_inlay_edits)
1539 .into_inner();
1540 let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits);
1541 snapshot_edits.push((snapshot.clone(), edits));
1542
1543 let mut expected_text: String = inlay_snapshot.text().to_string();
1544 for fold_range in map.merged_folds().into_iter().rev() {
1545 let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
1546 let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
1547 expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯");
1548 }
1549
1550 assert_eq!(snapshot.text(), expected_text);
1551 log::info!(
1552 "fold text {:?} ({} lines)",
1553 expected_text,
1554 expected_text.matches('\n').count() + 1
1555 );
1556
1557 let mut prev_row = 0;
1558 let mut expected_buffer_rows = Vec::new();
1559 for fold_range in map.merged_folds() {
1560 let fold_start = inlay_snapshot
1561 .to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
1562 .row();
1563 let fold_end = inlay_snapshot
1564 .to_point(inlay_snapshot.to_inlay_offset(fold_range.end))
1565 .row();
1566 expected_buffer_rows.extend(
1567 inlay_snapshot
1568 .buffer_rows(prev_row)
1569 .take((1 + fold_start - prev_row) as usize),
1570 );
1571 prev_row = 1 + fold_end;
1572 }
1573 expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
1574
1575 assert_eq!(
1576 expected_buffer_rows.len(),
1577 expected_text.matches('\n').count() + 1,
1578 "wrong expected buffer rows {:?}. text: {:?}",
1579 expected_buffer_rows,
1580 expected_text
1581 );
1582
1583 for (output_row, line) in expected_text.lines().enumerate() {
1584 let line_len = snapshot.line_len(output_row as u32);
1585 assert_eq!(line_len, line.len() as u32);
1586 }
1587
1588 let longest_row = snapshot.longest_row();
1589 let longest_char_column = expected_text
1590 .split('\n')
1591 .nth(longest_row as usize)
1592 .unwrap()
1593 .chars()
1594 .count();
1595 let mut fold_point = FoldPoint::new(0, 0);
1596 let mut fold_offset = FoldOffset(0);
1597 let mut char_column = 0;
1598 for c in expected_text.chars() {
1599 let inlay_point = fold_point.to_inlay_point(&snapshot);
1600 let inlay_offset = fold_offset.to_inlay_offset(&snapshot);
1601 assert_eq!(
1602 snapshot.to_fold_point(inlay_point, Right),
1603 fold_point,
1604 "{:?} -> fold point",
1605 inlay_point,
1606 );
1607 assert_eq!(
1608 inlay_snapshot.to_offset(inlay_point),
1609 inlay_offset,
1610 "inlay_snapshot.to_offset({:?})",
1611 inlay_point,
1612 );
1613 assert_eq!(
1614 fold_point.to_offset(&snapshot),
1615 fold_offset,
1616 "fold_point.to_offset({:?})",
1617 fold_point,
1618 );
1619
1620 if c == '\n' {
1621 *fold_point.row_mut() += 1;
1622 *fold_point.column_mut() = 0;
1623 char_column = 0;
1624 } else {
1625 *fold_point.column_mut() += c.len_utf8() as u32;
1626 char_column += 1;
1627 }
1628 fold_offset.0 += c.len_utf8();
1629 if char_column > longest_char_column {
1630 panic!(
1631 "invalid longest row {:?} (chars {}), found row {:?} (chars: {})",
1632 longest_row,
1633 longest_char_column,
1634 fold_point.row(),
1635 char_column
1636 );
1637 }
1638 }
1639
1640 for _ in 0..5 {
1641 let mut start = snapshot
1642 .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Left);
1643 let mut end = snapshot
1644 .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right);
1645 if start > end {
1646 mem::swap(&mut start, &mut end);
1647 }
1648
1649 let text = &expected_text[start.0..end.0];
1650 assert_eq!(
1651 snapshot
1652 .chunks(start..end, false, Highlights::default())
1653 .map(|c| c.text)
1654 .collect::<String>(),
1655 text,
1656 );
1657 }
1658
1659 let mut fold_row = 0;
1660 while fold_row < expected_buffer_rows.len() as u32 {
1661 assert_eq!(
1662 snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
1663 expected_buffer_rows[(fold_row as usize)..],
1664 "wrong buffer rows starting at fold row {}",
1665 fold_row,
1666 );
1667 fold_row += 1;
1668 }
1669
1670 let folded_buffer_rows = map
1671 .merged_folds()
1672 .iter()
1673 .flat_map(|fold_range| {
1674 let start_row = fold_range.start.to_point(&buffer_snapshot).row;
1675 let end = fold_range.end.to_point(&buffer_snapshot);
1676 if end.column == 0 {
1677 start_row..end.row
1678 } else {
1679 start_row..end.row + 1
1680 }
1681 })
1682 .collect::<HashSet<_>>();
1683 for row in 0..=buffer_snapshot.max_point().row {
1684 assert_eq!(
1685 snapshot.is_line_folded(MultiBufferRow(row)),
1686 folded_buffer_rows.contains(&row),
1687 "expected buffer row {}{} to be folded",
1688 row,
1689 if folded_buffer_rows.contains(&row) {
1690 ""
1691 } else {
1692 " not"
1693 }
1694 );
1695 }
1696
1697 for _ in 0..5 {
1698 let end =
1699 buffer_snapshot.clip_offset(rng.gen_range(0..=buffer_snapshot.len()), Right);
1700 let start = buffer_snapshot.clip_offset(rng.gen_range(0..=end), Left);
1701 let expected_folds = map
1702 .snapshot
1703 .folds
1704 .items(&buffer_snapshot)
1705 .into_iter()
1706 .filter(|fold| {
1707 let start = buffer_snapshot.anchor_before(start);
1708 let end = buffer_snapshot.anchor_after(end);
1709 start.cmp(&fold.range.end, &buffer_snapshot) == Ordering::Less
1710 && end.cmp(&fold.range.start, &buffer_snapshot) == Ordering::Greater
1711 })
1712 .collect::<Vec<_>>();
1713
1714 assert_eq!(
1715 snapshot
1716 .folds_in_range(start..end)
1717 .cloned()
1718 .collect::<Vec<_>>(),
1719 expected_folds
1720 );
1721 }
1722
1723 let text = snapshot.text();
1724 for _ in 0..5 {
1725 let start_row = rng.gen_range(0..=snapshot.max_point().row());
1726 let start_column = rng.gen_range(0..=snapshot.line_len(start_row));
1727 let end_row = rng.gen_range(0..=snapshot.max_point().row());
1728 let end_column = rng.gen_range(0..=snapshot.line_len(end_row));
1729 let mut start =
1730 snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left);
1731 let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right);
1732 if start > end {
1733 mem::swap(&mut start, &mut end);
1734 }
1735
1736 let lines = start..end;
1737 let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot);
1738 assert_eq!(
1739 snapshot.text_summary_for_range(lines),
1740 TextSummary::from(&text[bytes.start.0..bytes.end.0])
1741 )
1742 }
1743
1744 let mut text = initial_snapshot.text();
1745 for (snapshot, edits) in snapshot_edits.drain(..) {
1746 let new_text = snapshot.text();
1747 for edit in edits {
1748 let old_bytes = edit.new.start.0..edit.new.start.0 + edit.old_len().0;
1749 let new_bytes = edit.new.start.0..edit.new.end.0;
1750 text.replace_range(old_bytes, &new_text[new_bytes]);
1751 }
1752
1753 assert_eq!(text, new_text);
1754 initial_snapshot = snapshot;
1755 }
1756 }
1757 }
1758
1759 #[gpui::test]
1760 fn test_buffer_rows(cx: &mut gpui::AppContext) {
1761 let text = sample_text(6, 6, 'a') + "\n";
1762 let buffer = MultiBuffer::build_simple(&text, cx);
1763
1764 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1765 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
1766 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1767
1768 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1769 writer.fold(vec![
1770 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1771 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1772 ]);
1773
1774 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1775 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
1776 assert_eq!(
1777 snapshot.buffer_rows(0).collect::<Vec<_>>(),
1778 [Some(0), Some(3), Some(5), Some(6)]
1779 );
1780 assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
1781 }
1782
1783 fn init_test(cx: &mut gpui::AppContext) {
1784 let store = SettingsStore::test(cx);
1785 cx.set_global(store);
1786 }
1787
1788 impl FoldMap {
1789 fn merged_folds(&self) -> Vec<Range<usize>> {
1790 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1791 let buffer = &inlay_snapshot.buffer;
1792 let mut folds = self.snapshot.folds.items(buffer);
1793 // Ensure sorting doesn't change how folds get merged and displayed.
1794 folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
1795 let mut folds = folds
1796 .iter()
1797 .map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
1798 .peekable();
1799
1800 let mut merged_folds = Vec::new();
1801 while let Some(mut fold_range) = folds.next() {
1802 while let Some(next_range) = folds.peek() {
1803 if fold_range.end >= next_range.start {
1804 if next_range.end > fold_range.end {
1805 fold_range.end = next_range.end;
1806 }
1807 folds.next();
1808 } else {
1809 break;
1810 }
1811 }
1812 if fold_range.end > fold_range.start {
1813 merged_folds.push(fold_range);
1814 }
1815 }
1816 merged_folds
1817 }
1818
1819 pub fn randomly_mutate(
1820 &mut self,
1821 rng: &mut impl Rng,
1822 ) -> Vec<(FoldSnapshot, Vec<FoldEdit>)> {
1823 let mut snapshot_edits = Vec::new();
1824 match rng.gen_range(0..=100) {
1825 0..=39 if !self.snapshot.folds.is_empty() => {
1826 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1827 let buffer = &inlay_snapshot.buffer;
1828 let mut to_unfold = Vec::new();
1829 for _ in 0..rng.gen_range(1..=3) {
1830 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1831 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1832 to_unfold.push(start..end);
1833 }
1834 let inclusive = rng.gen();
1835 log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
1836 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
1837 snapshot_edits.push((snapshot, edits));
1838 let (snapshot, edits) = writer.unfold(to_unfold, inclusive);
1839 snapshot_edits.push((snapshot, edits));
1840 }
1841 _ => {
1842 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
1843 let buffer = &inlay_snapshot.buffer;
1844 let mut to_fold = Vec::new();
1845 for _ in 0..rng.gen_range(1..=2) {
1846 let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
1847 let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
1848 to_fold.push((start..end, FoldPlaceholder::test()));
1849 }
1850 log::info!("folding {:?}", to_fold);
1851 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
1852 snapshot_edits.push((snapshot, edits));
1853 let (snapshot, edits) = writer.fold(to_fold);
1854 snapshot_edits.push((snapshot, edits));
1855 }
1856 }
1857 snapshot_edits
1858 }
1859 }
1860}