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