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