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