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