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