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