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