1use crate::display_map::inlay_map::InlayChunk;
2
3use super::{
4 Highlights,
5 inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
6};
7use gpui::{AnyElement, App, ElementId, HighlightStyle, Pixels, Window};
8use language::{Edit, HighlightId, Point, TextSummary};
9use multi_buffer::{
10 Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
11};
12use project::InlayId;
13use std::{
14 any::TypeId,
15 cmp::{self, Ordering},
16 fmt, iter,
17 ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
18 sync::Arc,
19 usize,
20};
21use sum_tree::{Bias, Cursor, Dimensions, FilterCursor, SumTree, Summary, TreeMap};
22use ui::IntoElement as _;
23use util::post_inc;
24
25#[derive(Clone)]
26pub struct FoldPlaceholder {
27 /// Creates an element to represent this fold's placeholder.
28 pub render: Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement>,
29 /// If true, the element is constrained to the shaped width of an ellipsis.
30 pub constrain_width: bool,
31 /// If true, merges the fold with an adjacent one.
32 pub merge_adjacent: bool,
33 /// Category of the fold. Useful for carefully removing from overlapping folds.
34 pub type_tag: Option<TypeId>,
35}
36
37impl Default for FoldPlaceholder {
38 fn default() -> Self {
39 Self {
40 render: Arc::new(|_, _, _| gpui::Empty.into_any_element()),
41 constrain_width: true,
42 merge_adjacent: true,
43 type_tag: None,
44 }
45 }
46}
47
48impl FoldPlaceholder {
49 #[cfg(any(test, feature = "test-support"))]
50 pub fn test() -> Self {
51 Self {
52 render: Arc::new(|_id, _range, _cx| gpui::Empty.into_any_element()),
53 constrain_width: true,
54 merge_adjacent: true,
55 type_tag: None,
56 }
57 }
58}
59
60impl fmt::Debug for FoldPlaceholder {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.debug_struct("FoldPlaceholder")
63 .field("constrain_width", &self.constrain_width)
64 .finish()
65 }
66}
67
68impl Eq for FoldPlaceholder {}
69
70impl PartialEq for FoldPlaceholder {
71 fn eq(&self, other: &Self) -> bool {
72 Arc::ptr_eq(&self.render, &other.render) && self.constrain_width == other.constrain_width
73 }
74}
75
76#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
77pub struct FoldPoint(pub Point);
78
79impl FoldPoint {
80 pub fn new(row: u32, column: u32) -> Self {
81 Self(Point::new(row, column))
82 }
83
84 pub fn row(self) -> u32 {
85 self.0.row
86 }
87
88 pub fn column(self) -> u32 {
89 self.0.column
90 }
91
92 pub fn row_mut(&mut self) -> &mut u32 {
93 &mut self.0.row
94 }
95
96 #[cfg(test)]
97 pub fn column_mut(&mut self) -> &mut u32 {
98 &mut self.0.column
99 }
100
101 pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
102 let (start, _, _) = snapshot
103 .transforms
104 .find::<Dimensions<FoldPoint, InlayPoint>, _>((), &self, Bias::Right);
105 let overshoot = self.0 - start.0.0;
106 InlayPoint(start.1.0 + overshoot)
107 }
108
109 pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
110 let (start, _, item) = snapshot
111 .transforms
112 .find::<Dimensions<FoldPoint, TransformSummary>, _>((), &self, Bias::Right);
113 let overshoot = self.0 - start.1.output.lines;
114 let mut offset = start.1.output.len;
115 if !overshoot.is_zero() {
116 let transform = item.expect("display point out of range");
117 assert!(transform.placeholder.is_none());
118 let end_inlay_offset = snapshot
119 .inlay_snapshot
120 .to_offset(InlayPoint(start.1.input.lines + overshoot));
121 offset += end_inlay_offset.0 - start.1.input.len;
122 }
123 FoldOffset(offset)
124 }
125}
126
127impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
128 fn zero(_cx: ()) -> Self {
129 Default::default()
130 }
131
132 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
133 self.0 += &summary.output.lines;
134 }
135}
136
137pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap);
138
139impl FoldMapWriter<'_> {
140 pub(crate) fn fold<T: ToOffset>(
141 &mut self,
142 ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
143 ) -> (FoldSnapshot, Vec<FoldEdit>) {
144 let mut edits = Vec::new();
145 let mut folds = Vec::new();
146 let snapshot = self.0.snapshot.inlay_snapshot.clone();
147 for (range, fold_text) in ranges.into_iter() {
148 let buffer = &snapshot.filter_snapshot.buffer_snapshot;
149 let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
150
151 // Ignore any empty ranges.
152 if range.start == range.end {
153 continue;
154 }
155
156 // For now, ignore any ranges that span an excerpt boundary.
157 let fold_range =
158 FoldRange(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
159 if fold_range.0.start.excerpt_id != fold_range.0.end.excerpt_id {
160 continue;
161 }
162 // FIXME we should ignore any folds that start or end in a filtered region
163
164 folds.push(Fold {
165 id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
166 range: fold_range,
167 placeholder: fold_text,
168 });
169
170 let filter_range = snapshot.filter_snapshot.to_filter_offset(range.start)
171 ..snapshot.filter_snapshot.to_filter_offset(range.end);
172 let inlay_range = snapshot.to_inlay_offset(filter_range.start)
173 ..snapshot.to_inlay_offset(filter_range.end);
174 edits.push(InlayEdit {
175 old: inlay_range.clone(),
176 new: inlay_range,
177 });
178 }
179
180 let buffer = &snapshot.filter_snapshot.buffer_snapshot;
181 folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
182
183 self.0.snapshot.folds = {
184 let mut new_tree = SumTree::new(buffer);
185 let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>(buffer);
186 for fold in folds {
187 self.0.snapshot.fold_metadata_by_id.insert(
188 fold.id,
189 FoldMetadata {
190 range: fold.range.clone(),
191 width: None,
192 },
193 );
194 new_tree.append(cursor.slice(&fold.range, Bias::Right), buffer);
195 new_tree.push(fold, buffer);
196 }
197 new_tree.append(cursor.suffix(), buffer);
198 new_tree
199 };
200
201 let edits = consolidate_inlay_edits(edits);
202 let edits = self.0.sync(snapshot.clone(), edits);
203 (self.0.snapshot.clone(), edits)
204 }
205
206 /// Removes any folds with the given ranges.
207 pub(crate) fn remove_folds<T: ToOffset>(
208 &mut self,
209 ranges: impl IntoIterator<Item = Range<T>>,
210 type_id: TypeId,
211 ) -> (FoldSnapshot, Vec<FoldEdit>) {
212 self.remove_folds_with(
213 ranges,
214 |fold| fold.placeholder.type_tag == Some(type_id),
215 false,
216 )
217 }
218
219 /// Removes any folds whose ranges intersect the given ranges.
220 pub(crate) fn unfold_intersecting<T: ToOffset>(
221 &mut self,
222 ranges: impl IntoIterator<Item = Range<T>>,
223 inclusive: bool,
224 ) -> (FoldSnapshot, Vec<FoldEdit>) {
225 self.remove_folds_with(ranges, |_| true, inclusive)
226 }
227
228 /// Removes any folds that intersect the given ranges and for which the given predicate
229 /// returns true.
230 fn remove_folds_with<T: ToOffset>(
231 &mut self,
232 ranges: impl IntoIterator<Item = Range<T>>,
233 should_unfold: impl Fn(&Fold) -> bool,
234 inclusive: bool,
235 ) -> (FoldSnapshot, Vec<FoldEdit>) {
236 let mut edits = Vec::new();
237 let mut fold_ixs_to_delete = Vec::new();
238 let snapshot = self.0.snapshot.inlay_snapshot.clone();
239 let buffer = &snapshot.filter_snapshot.buffer_snapshot;
240 for range in ranges.into_iter() {
241 let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
242 let mut folds_cursor =
243 intersecting_folds(&snapshot, &self.0.snapshot.folds, range.clone(), inclusive);
244 while let Some(fold) = folds_cursor.item() {
245 let offset_range =
246 fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
247 let filter_range = snapshot
248 .filter_snapshot
249 .to_filter_offset(offset_range.start)
250 ..snapshot.filter_snapshot.to_filter_offset(offset_range.end);
251 if should_unfold(fold) {
252 if offset_range.end > offset_range.start {
253 let inlay_range = snapshot.to_inlay_offset(filter_range.start)
254 ..snapshot.to_inlay_offset(filter_range.end);
255 edits.push(InlayEdit {
256 old: inlay_range.clone(),
257 new: inlay_range,
258 });
259 }
260 fold_ixs_to_delete.push(*folds_cursor.start());
261 self.0.snapshot.fold_metadata_by_id.remove(&fold.id);
262 }
263 folds_cursor.next();
264 }
265 }
266
267 fold_ixs_to_delete.sort_unstable();
268 fold_ixs_to_delete.dedup();
269
270 self.0.snapshot.folds = {
271 let mut cursor = self.0.snapshot.folds.cursor::<usize>(buffer);
272 let mut folds = SumTree::new(buffer);
273 for fold_ix in fold_ixs_to_delete {
274 folds.append(cursor.slice(&fold_ix, Bias::Right), buffer);
275 cursor.next();
276 }
277 folds.append(cursor.suffix(), buffer);
278 folds
279 };
280
281 let edits = consolidate_inlay_edits(edits);
282 let edits = self.0.sync(snapshot.clone(), edits);
283 (self.0.snapshot.clone(), edits)
284 }
285
286 pub(crate) fn update_fold_widths(
287 &mut self,
288 new_widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
289 ) -> (FoldSnapshot, Vec<FoldEdit>) {
290 let mut edits = Vec::new();
291 let inlay_snapshot = self.0.snapshot.inlay_snapshot.clone();
292 let buffer = &inlay_snapshot.filter_snapshot.buffer_snapshot;
293
294 for (id, new_width) in new_widths {
295 let ChunkRendererId::Fold(id) = id else {
296 continue;
297 };
298 if let Some(metadata) = self.0.snapshot.fold_metadata_by_id.get(&id).cloned()
299 && Some(new_width) != metadata.width
300 {
301 let buffer_start = metadata.range.start.to_offset(buffer);
302 let buffer_end = metadata.range.end.to_offset(buffer);
303 let filter_range = inlay_snapshot
304 .filter_snapshot
305 .to_filter_offset(buffer_start)
306 ..inlay_snapshot.filter_snapshot.to_filter_offset(buffer_end);
307 let inlay_range = inlay_snapshot.to_inlay_offset(filter_range.start)
308 ..inlay_snapshot.to_inlay_offset(filter_range.end);
309 edits.push(InlayEdit {
310 old: inlay_range.clone(),
311 new: inlay_range.clone(),
312 });
313
314 self.0.snapshot.fold_metadata_by_id.insert(
315 id,
316 FoldMetadata {
317 range: metadata.range,
318 width: Some(new_width),
319 },
320 );
321 }
322 }
323
324 let edits = consolidate_inlay_edits(edits);
325 let edits = self.0.sync(inlay_snapshot, edits);
326 (self.0.snapshot.clone(), edits)
327 }
328}
329
330/// Decides where the fold indicators should be; also tracks parts of a source file that are currently folded.
331///
332/// See the [`display_map` module documentation](crate::display_map) for more information.
333pub struct FoldMap {
334 snapshot: FoldSnapshot,
335 next_fold_id: FoldId,
336}
337
338impl FoldMap {
339 pub fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
340 let this = Self {
341 snapshot: FoldSnapshot {
342 folds: SumTree::new(&inlay_snapshot.filter_snapshot.buffer_snapshot),
343 transforms: SumTree::from_item(
344 Transform {
345 summary: TransformSummary {
346 input: inlay_snapshot.text_summary(),
347 output: inlay_snapshot.text_summary(),
348 },
349 placeholder: None,
350 },
351 (),
352 ),
353 inlay_snapshot: inlay_snapshot,
354 version: 0,
355 fold_metadata_by_id: TreeMap::default(),
356 },
357 next_fold_id: FoldId::default(),
358 };
359 let snapshot = this.snapshot.clone();
360 (this, snapshot)
361 }
362
363 pub fn read(
364 &mut self,
365 inlay_snapshot: InlaySnapshot,
366 edits: Vec<InlayEdit>,
367 ) -> (FoldSnapshot, Vec<FoldEdit>) {
368 let edits = self.sync(inlay_snapshot, edits);
369 self.check_invariants();
370 (self.snapshot.clone(), edits)
371 }
372
373 pub(crate) fn write(
374 &mut self,
375 inlay_snapshot: InlaySnapshot,
376 edits: Vec<InlayEdit>,
377 ) -> (FoldMapWriter<'_>, FoldSnapshot, Vec<FoldEdit>) {
378 let (snapshot, edits) = self.read(inlay_snapshot, edits);
379 (FoldMapWriter(self), snapshot, edits)
380 }
381
382 fn check_invariants(&self) {
383 if cfg!(test) {
384 assert_eq!(
385 self.snapshot.transforms.summary().input.len,
386 self.snapshot.inlay_snapshot.len().0,
387 "transform tree does not match inlay snapshot's length"
388 );
389
390 let mut prev_transform_isomorphic = false;
391 for transform in self.snapshot.transforms.iter() {
392 if !transform.is_fold() && prev_transform_isomorphic {
393 panic!(
394 "found adjacent isomorphic transforms: {:?}",
395 self.snapshot.transforms.items(())
396 );
397 }
398 prev_transform_isomorphic = !transform.is_fold();
399 }
400
401 let mut folds = self.snapshot.folds.iter().peekable();
402 while let Some(fold) = folds.next() {
403 if let Some(next_fold) = folds.peek() {
404 let comparison = fold.range.cmp(&next_fold.range, self.snapshot.buffer());
405 assert!(comparison.is_le());
406 }
407 }
408 }
409 }
410
411 fn sync(
412 &mut self,
413 inlay_snapshot: InlaySnapshot,
414 inlay_edits: Vec<InlayEdit>,
415 ) -> Vec<FoldEdit> {
416 if inlay_edits.is_empty() {
417 if self.snapshot.inlay_snapshot.version != inlay_snapshot.version {
418 self.snapshot.version += 1;
419 }
420 self.snapshot.inlay_snapshot = inlay_snapshot;
421 Vec::new()
422 } else {
423 let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable();
424
425 let mut new_transforms = SumTree::<Transform>::default();
426 let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>(());
427 cursor.seek(&InlayOffset(0), Bias::Right);
428
429 while let Some(mut edit) = inlay_edits_iter.next() {
430 if let Some(item) = cursor.item()
431 && !item.is_fold()
432 {
433 new_transforms.update_last(
434 |transform| {
435 if !transform.is_fold() {
436 transform.summary.add_summary(&item.summary, ());
437 cursor.next();
438 }
439 },
440 (),
441 );
442 }
443 new_transforms.append(cursor.slice(&edit.old.start, Bias::Left), ());
444 edit.new.start -= edit.old.start - *cursor.start();
445 edit.old.start = *cursor.start();
446
447 cursor.seek(&edit.old.end, Bias::Right);
448 cursor.next();
449
450 let mut delta = edit.new_len().0 as isize - edit.old_len().0 as isize;
451 loop {
452 edit.old.end = *cursor.start();
453
454 if let Some(next_edit) = inlay_edits_iter.peek() {
455 if next_edit.old.start > edit.old.end {
456 break;
457 }
458
459 let next_edit = inlay_edits_iter.next().unwrap();
460 delta += next_edit.new_len().0 as isize - next_edit.old_len().0 as isize;
461
462 if next_edit.old.end >= edit.old.end {
463 edit.old.end = next_edit.old.end;
464 cursor.seek(&edit.old.end, Bias::Right);
465 cursor.next();
466 }
467 } else {
468 break;
469 }
470 }
471
472 edit.new.end =
473 InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize);
474
475 let filter_offset = inlay_snapshot.to_filter_offset(edit.new.start);
476 let anchor = inlay_snapshot
477 .filter_snapshot
478 .buffer_snapshot
479 .anchor_before(
480 inlay_snapshot
481 .filter_snapshot
482 .to_buffer_offset(filter_offset, Bias::Left),
483 );
484 let mut folds_cursor = self
485 .snapshot
486 .folds
487 .cursor::<FoldRange>(&inlay_snapshot.filter_snapshot.buffer_snapshot);
488 folds_cursor.seek(&FoldRange(anchor..Anchor::max()), Bias::Left);
489
490 let mut folds = iter::from_fn({
491 let inlay_snapshot = &inlay_snapshot;
492 move || {
493 let item = folds_cursor.item().map(|fold| {
494 let buffer_start = fold
495 .range
496 .start
497 .to_offset(&inlay_snapshot.filter_snapshot.buffer_snapshot);
498 let buffer_end = fold
499 .range
500 .end
501 .to_offset(&inlay_snapshot.filter_snapshot.buffer_snapshot);
502 let filter_range = inlay_snapshot
503 .filter_snapshot
504 .to_filter_offset(buffer_start)
505 ..inlay_snapshot.filter_snapshot.to_filter_offset(buffer_end);
506 (
507 fold.clone(),
508 inlay_snapshot.to_inlay_offset(filter_range.start)
509 ..inlay_snapshot.to_inlay_offset(filter_range.end),
510 )
511 });
512 folds_cursor.next();
513 item
514 }
515 })
516 .peekable();
517
518 while folds
519 .peek()
520 .is_some_and(|(_, fold_range)| fold_range.start < edit.new.end)
521 {
522 let (fold, mut fold_range) = folds.next().unwrap();
523 let sum = new_transforms.summary();
524
525 assert!(fold_range.start.0 >= sum.input.len);
526
527 while folds.peek().is_some_and(|(next_fold, next_fold_range)| {
528 next_fold_range.start < fold_range.end
529 || (next_fold_range.start == fold_range.end
530 && fold.placeholder.merge_adjacent
531 && next_fold.placeholder.merge_adjacent)
532 }) {
533 let (_, next_fold_range) = folds.next().unwrap();
534 if next_fold_range.end > fold_range.end {
535 fold_range.end = next_fold_range.end;
536 }
537 }
538
539 if fold_range.start.0 > sum.input.len {
540 let text_summary = inlay_snapshot
541 .text_summary_for_range(InlayOffset(sum.input.len)..fold_range.start);
542 push_isomorphic(&mut new_transforms, text_summary);
543 }
544
545 if fold_range.end > fold_range.start {
546 const ELLIPSIS: &str = "⋯";
547
548 let fold_id = fold.id;
549 new_transforms.push(
550 Transform {
551 summary: TransformSummary {
552 output: TextSummary::from(ELLIPSIS),
553 input: inlay_snapshot
554 .text_summary_for_range(fold_range.start..fold_range.end),
555 },
556 placeholder: Some(TransformPlaceholder {
557 text: ELLIPSIS,
558 chars: 1,
559 renderer: ChunkRenderer {
560 id: ChunkRendererId::Fold(fold.id),
561 render: Arc::new(move |cx| {
562 (fold.placeholder.render)(
563 fold_id,
564 fold.range.0.clone(),
565 cx.context,
566 )
567 }),
568 constrain_width: fold.placeholder.constrain_width,
569 measured_width: self.snapshot.fold_width(&fold_id),
570 },
571 }),
572 },
573 (),
574 );
575 }
576 }
577
578 let sum = new_transforms.summary();
579 if sum.input.len < edit.new.end.0 {
580 let text_summary = inlay_snapshot
581 .text_summary_for_range(InlayOffset(sum.input.len)..edit.new.end);
582 push_isomorphic(&mut new_transforms, text_summary);
583 }
584 }
585
586 new_transforms.append(cursor.suffix(), ());
587 if new_transforms.is_empty() {
588 let text_summary = inlay_snapshot.text_summary();
589 push_isomorphic(&mut new_transforms, text_summary);
590 }
591
592 drop(cursor);
593
594 let mut fold_edits = Vec::with_capacity(inlay_edits.len());
595 {
596 let mut old_transforms = self
597 .snapshot
598 .transforms
599 .cursor::<Dimensions<InlayOffset, FoldOffset>>(());
600 let mut new_transforms =
601 new_transforms.cursor::<Dimensions<InlayOffset, FoldOffset>>(());
602
603 for mut edit in inlay_edits {
604 old_transforms.seek(&edit.old.start, Bias::Left);
605 if old_transforms.item().is_some_and(|t| t.is_fold()) {
606 edit.old.start = old_transforms.start().0;
607 }
608 let old_start =
609 old_transforms.start().1.0 + (edit.old.start - old_transforms.start().0).0;
610
611 old_transforms.seek_forward(&edit.old.end, Bias::Right);
612 if old_transforms.item().is_some_and(|t| t.is_fold()) {
613 old_transforms.next();
614 edit.old.end = old_transforms.start().0;
615 }
616 let old_end =
617 old_transforms.start().1.0 + (edit.old.end - old_transforms.start().0).0;
618
619 new_transforms.seek(&edit.new.start, Bias::Left);
620 if new_transforms.item().is_some_and(|t| t.is_fold()) {
621 edit.new.start = new_transforms.start().0;
622 }
623 let new_start =
624 new_transforms.start().1.0 + (edit.new.start - new_transforms.start().0).0;
625
626 new_transforms.seek_forward(&edit.new.end, Bias::Right);
627 if new_transforms.item().is_some_and(|t| t.is_fold()) {
628 new_transforms.next();
629 edit.new.end = new_transforms.start().0;
630 }
631 let new_end =
632 new_transforms.start().1.0 + (edit.new.end - new_transforms.start().0).0;
633
634 fold_edits.push(FoldEdit {
635 old: FoldOffset(old_start)..FoldOffset(old_end),
636 new: FoldOffset(new_start)..FoldOffset(new_end),
637 });
638 }
639
640 fold_edits = consolidate_fold_edits(fold_edits);
641 }
642
643 self.snapshot.transforms = new_transforms;
644 self.snapshot.inlay_snapshot = inlay_snapshot;
645 self.snapshot.version += 1;
646 fold_edits
647 }
648 }
649}
650
651#[derive(Clone)]
652pub struct FoldSnapshot {
653 pub inlay_snapshot: InlaySnapshot,
654 transforms: SumTree<Transform>,
655 folds: SumTree<Fold>,
656 fold_metadata_by_id: TreeMap<FoldId, FoldMetadata>,
657 pub version: usize,
658}
659
660impl FoldSnapshot {
661 pub fn buffer(&self) -> &MultiBufferSnapshot {
662 &self.inlay_snapshot.filter_snapshot.buffer_snapshot
663 }
664
665 fn fold_width(&self, fold_id: &FoldId) -> Option<Pixels> {
666 self.fold_metadata_by_id.get(fold_id)?.width
667 }
668
669 #[cfg(test)]
670 pub fn text(&self) -> String {
671 self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
672 .map(|c| c.text)
673 .collect()
674 }
675
676 #[cfg(test)]
677 pub fn fold_count(&self) -> usize {
678 self.folds
679 .items(&self.inlay_snapshot.filter_snapshot.buffer_snapshot)
680 .len()
681 }
682
683 pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
684 let mut summary = TextSummary::default();
685
686 let mut cursor = self
687 .transforms
688 .cursor::<Dimensions<FoldPoint, InlayPoint>>(());
689 cursor.seek(&range.start, Bias::Right);
690 if let Some(transform) = cursor.item() {
691 let start_in_transform = range.start.0 - cursor.start().0.0;
692 let end_in_transform = cmp::min(range.end, cursor.end().0).0 - cursor.start().0.0;
693 if let Some(placeholder) = transform.placeholder.as_ref() {
694 summary = TextSummary::from(
695 &placeholder.text
696 [start_in_transform.column as usize..end_in_transform.column as usize],
697 );
698 } else {
699 let inlay_start = self
700 .inlay_snapshot
701 .to_offset(InlayPoint(cursor.start().1.0 + start_in_transform));
702 let inlay_end = self
703 .inlay_snapshot
704 .to_offset(InlayPoint(cursor.start().1.0 + end_in_transform));
705 summary = self
706 .inlay_snapshot
707 .text_summary_for_range(inlay_start..inlay_end);
708 }
709 }
710
711 if range.end > cursor.end().0 {
712 cursor.next();
713 summary += &cursor
714 .summary::<_, TransformSummary>(&range.end, Bias::Right)
715 .output;
716 if let Some(transform) = cursor.item() {
717 let end_in_transform = range.end.0 - cursor.start().0.0;
718 if let Some(placeholder) = transform.placeholder.as_ref() {
719 summary +=
720 TextSummary::from(&placeholder.text[..end_in_transform.column as usize]);
721 } else {
722 let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
723 let inlay_end = self
724 .inlay_snapshot
725 .to_offset(InlayPoint(cursor.start().1.0 + end_in_transform));
726 summary += self
727 .inlay_snapshot
728 .text_summary_for_range(inlay_start..inlay_end);
729 }
730 }
731 }
732
733 summary
734 }
735
736 pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
737 let (start, end, item) = self
738 .transforms
739 .find::<Dimensions<InlayPoint, FoldPoint>, _>((), &point, Bias::Right);
740 if item.is_some_and(|t| t.is_fold()) {
741 if bias == Bias::Left || point == start.0 {
742 start.1
743 } else {
744 end.1
745 }
746 } else {
747 let overshoot = point.0 - start.0.0;
748 FoldPoint(cmp::min(start.1.0 + overshoot, end.1.0))
749 }
750 }
751
752 pub fn len(&self) -> FoldOffset {
753 FoldOffset(self.transforms.summary().output.len)
754 }
755
756 pub fn line_len(&self, row: u32) -> u32 {
757 let line_start = FoldPoint::new(row, 0).to_offset(self).0;
758 let line_end = if row >= self.max_point().row() {
759 self.len().0
760 } else {
761 FoldPoint::new(row + 1, 0).to_offset(self).0 - 1
762 };
763 (line_end - line_start) as u32
764 }
765
766 pub fn row_infos(&self, start_row: u32) -> FoldRows<'_> {
767 if start_row > self.transforms.summary().output.lines.row {
768 panic!("invalid display row {}", start_row);
769 }
770
771 let fold_point = FoldPoint::new(start_row, 0);
772 let mut cursor = self
773 .transforms
774 .cursor::<Dimensions<FoldPoint, InlayPoint>>(());
775 cursor.seek(&fold_point, Bias::Left);
776
777 let overshoot = fold_point.0 - cursor.start().0.0;
778 let inlay_point = InlayPoint(cursor.start().1.0 + overshoot);
779 let input_rows = self.inlay_snapshot.row_infos(inlay_point.row());
780
781 FoldRows {
782 fold_point,
783 input_rows,
784 cursor,
785 }
786 }
787
788 pub fn max_point(&self) -> FoldPoint {
789 FoldPoint(self.transforms.summary().output.lines)
790 }
791
792 #[cfg(test)]
793 pub fn longest_row(&self) -> u32 {
794 self.transforms.summary().output.longest_row
795 }
796
797 pub fn folds_in_range<T>(&self, range: Range<T>) -> impl Iterator<Item = &Fold>
798 where
799 T: ToOffset,
800 {
801 let buffer = &self.inlay_snapshot.filter_snapshot.buffer_snapshot;
802 let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
803 let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
804 iter::from_fn(move || {
805 let item = folds.item();
806 folds.next();
807 item
808 })
809 }
810
811 pub fn intersects_fold<T>(&self, offset: T) -> bool
812 where
813 T: ToOffset,
814 {
815 let buffer_offset = offset.to_offset(&self.inlay_snapshot.filter_snapshot.buffer_snapshot);
816 let filter_offset = self
817 .inlay_snapshot
818 .filter_snapshot
819 .to_filter_offset(buffer_offset);
820 let inlay_offset = self.inlay_snapshot.to_inlay_offset(filter_offset);
821 let (_, _, item) = self
822 .transforms
823 .find::<InlayOffset, _>((), &inlay_offset, Bias::Right);
824 item.is_some_and(|t| t.placeholder.is_some())
825 }
826
827 pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
828 let filter_point = self
829 .inlay_snapshot
830 .filter_snapshot
831 .to_filter_point(Point::new(buffer_row.0, 0));
832 let mut inlay_point = self.inlay_snapshot.to_inlay_point(filter_point);
833 let mut cursor = self.transforms.cursor::<InlayPoint>(());
834 cursor.seek(&inlay_point, Bias::Right);
835 loop {
836 match cursor.item() {
837 Some(transform) => {
838 let filter_point = self.inlay_snapshot.to_filter_point(inlay_point);
839 // FIXME unclear what bias to use here
840 let buffer_point = self
841 .inlay_snapshot
842 .filter_snapshot
843 .to_buffer_point(filter_point, Bias::Left);
844 if buffer_point.row != buffer_row.0 {
845 return false;
846 } else if transform.placeholder.is_some() {
847 return true;
848 }
849 }
850 None => return false,
851 }
852
853 if cursor.end().row() == inlay_point.row() {
854 cursor.next();
855 } else {
856 inlay_point.0 += Point::new(1, 0);
857 cursor.seek(&inlay_point, Bias::Right);
858 }
859 }
860 }
861
862 pub(crate) fn chunks<'a>(
863 &'a self,
864 range: Range<FoldOffset>,
865 language_aware: bool,
866 highlights: Highlights<'a>,
867 ) -> FoldChunks<'a> {
868 let mut transform_cursor = self
869 .transforms
870 .cursor::<Dimensions<FoldOffset, InlayOffset>>(());
871 transform_cursor.seek(&range.start, Bias::Right);
872
873 let inlay_start = {
874 let overshoot = range.start.0 - transform_cursor.start().0.0;
875 transform_cursor.start().1 + InlayOffset(overshoot)
876 };
877
878 let transform_end = transform_cursor.end();
879
880 let inlay_end = if transform_cursor
881 .item()
882 .is_none_or(|transform| transform.is_fold())
883 {
884 inlay_start
885 } else if range.end < transform_end.0 {
886 let overshoot = range.end.0 - transform_cursor.start().0.0;
887 transform_cursor.start().1 + InlayOffset(overshoot)
888 } else {
889 transform_end.1
890 };
891
892 FoldChunks {
893 transform_cursor,
894 inlay_chunks: self.inlay_snapshot.chunks(
895 inlay_start..inlay_end,
896 language_aware,
897 highlights,
898 ),
899 inlay_chunk: None,
900 inlay_offset: inlay_start,
901 output_offset: range.start,
902 max_output_offset: range.end,
903 }
904 }
905
906 pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator<Item = char> {
907 self.chunks(
908 start.to_offset(self)..self.len(),
909 false,
910 Highlights::default(),
911 )
912 .flat_map(|chunk| chunk.text.chars())
913 }
914
915 pub fn chunks_at(&self, start: FoldPoint) -> FoldChunks<'_> {
916 self.chunks(
917 start.to_offset(self)..self.len(),
918 false,
919 Highlights::default(),
920 )
921 }
922
923 #[cfg(test)]
924 pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset {
925 if offset > self.len() {
926 self.len()
927 } else {
928 self.clip_point(offset.to_point(self), bias).to_offset(self)
929 }
930 }
931
932 pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
933 let (start, end, item) = self
934 .transforms
935 .find::<Dimensions<FoldPoint, InlayPoint>, _>((), &point, Bias::Right);
936 if let Some(transform) = item {
937 let transform_start = start.0.0;
938 if transform.placeholder.is_some() {
939 if point.0 == transform_start || matches!(bias, Bias::Left) {
940 FoldPoint(transform_start)
941 } else {
942 FoldPoint(end.0.0)
943 }
944 } else {
945 let overshoot = InlayPoint(point.0 - transform_start);
946 let inlay_point = start.1 + overshoot;
947 let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias);
948 FoldPoint(start.0.0 + (clipped_inlay_point - start.1).0)
949 }
950 } else {
951 FoldPoint(self.transforms.summary().output.lines)
952 }
953 }
954}
955
956fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
957 let mut did_merge = false;
958 transforms.update_last(
959 |last| {
960 if !last.is_fold() {
961 last.summary.input += summary;
962 last.summary.output += summary;
963 did_merge = true;
964 }
965 },
966 (),
967 );
968 if !did_merge {
969 transforms.push(
970 Transform {
971 summary: TransformSummary {
972 input: summary,
973 output: summary,
974 },
975 placeholder: None,
976 },
977 (),
978 )
979 }
980}
981
982fn intersecting_folds<'a>(
983 inlay_snapshot: &'a InlaySnapshot,
984 folds: &'a SumTree<Fold>,
985 range: Range<usize>,
986 inclusive: bool,
987) -> FilterCursor<'a, 'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
988 let buffer = &inlay_snapshot.filter_snapshot.buffer_snapshot;
989 let start = buffer.anchor_before(range.start.to_offset(buffer));
990 let end = buffer.anchor_after(range.end.to_offset(buffer));
991 let mut cursor = folds.filter::<_, usize>(buffer, move |summary| {
992 let start_cmp = start.cmp(&summary.max_end, buffer);
993 let end_cmp = end.cmp(&summary.min_start, buffer);
994
995 if inclusive {
996 start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
997 } else {
998 start_cmp == Ordering::Less && end_cmp == Ordering::Greater
999 }
1000 });
1001 cursor.next();
1002 cursor
1003}
1004
1005fn consolidate_inlay_edits(mut edits: Vec<InlayEdit>) -> Vec<InlayEdit> {
1006 edits.sort_unstable_by(|a, b| {
1007 a.old
1008 .start
1009 .cmp(&b.old.start)
1010 .then_with(|| b.old.end.cmp(&a.old.end))
1011 });
1012
1013 let _old_alloc_ptr = edits.as_ptr();
1014 let mut inlay_edits = edits.into_iter();
1015
1016 if let Some(mut first_edit) = inlay_edits.next() {
1017 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
1018 #[allow(clippy::filter_map_identity)]
1019 let mut v: Vec<_> = inlay_edits
1020 .scan(&mut first_edit, |prev_edit, edit| {
1021 if prev_edit.old.end >= edit.old.start {
1022 prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
1023 prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
1024 prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
1025 Some(None) // Skip this edit, it's merged
1026 } else {
1027 let prev = std::mem::replace(*prev_edit, edit);
1028 Some(Some(prev)) // Yield the previous edit
1029 }
1030 })
1031 .filter_map(|x| x)
1032 .collect();
1033 v.push(first_edit.clone());
1034 debug_assert_eq!(_old_alloc_ptr, v.as_ptr(), "Inlay edits were reallocated");
1035 v
1036 } else {
1037 vec![]
1038 }
1039}
1040
1041fn consolidate_fold_edits(mut edits: Vec<FoldEdit>) -> Vec<FoldEdit> {
1042 edits.sort_unstable_by(|a, b| {
1043 a.old
1044 .start
1045 .cmp(&b.old.start)
1046 .then_with(|| b.old.end.cmp(&a.old.end))
1047 });
1048 let _old_alloc_ptr = edits.as_ptr();
1049 let mut fold_edits = edits.into_iter();
1050
1051 if let Some(mut first_edit) = fold_edits.next() {
1052 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
1053 #[allow(clippy::filter_map_identity)]
1054 let mut v: Vec<_> = fold_edits
1055 .scan(&mut first_edit, |prev_edit, edit| {
1056 if prev_edit.old.end >= edit.old.start {
1057 prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
1058 prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
1059 prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
1060 Some(None) // Skip this edit, it's merged
1061 } else {
1062 let prev = std::mem::replace(*prev_edit, edit);
1063 Some(Some(prev)) // Yield the previous edit
1064 }
1065 })
1066 .filter_map(|x| x)
1067 .collect();
1068 v.push(first_edit.clone());
1069 v
1070 } else {
1071 vec![]
1072 }
1073}
1074
1075#[derive(Clone, Debug, Default)]
1076struct Transform {
1077 summary: TransformSummary,
1078 placeholder: Option<TransformPlaceholder>,
1079}
1080
1081#[derive(Clone, Debug)]
1082struct TransformPlaceholder {
1083 text: &'static str,
1084 chars: u128,
1085 renderer: ChunkRenderer,
1086}
1087
1088impl Transform {
1089 fn is_fold(&self) -> bool {
1090 self.placeholder.is_some()
1091 }
1092}
1093
1094#[derive(Clone, Debug, Default, Eq, PartialEq)]
1095struct TransformSummary {
1096 output: TextSummary,
1097 input: TextSummary,
1098}
1099
1100impl sum_tree::Item for Transform {
1101 type Summary = TransformSummary;
1102
1103 fn summary(&self, _cx: ()) -> Self::Summary {
1104 self.summary.clone()
1105 }
1106}
1107
1108impl sum_tree::ContextLessSummary for TransformSummary {
1109 fn zero() -> Self {
1110 Default::default()
1111 }
1112
1113 fn add_summary(&mut self, other: &Self) {
1114 self.input += &other.input;
1115 self.output += &other.output;
1116 }
1117}
1118
1119#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Ord, PartialOrd, Hash)]
1120pub struct FoldId(pub(super) usize);
1121
1122impl From<FoldId> for ElementId {
1123 fn from(val: FoldId) -> Self {
1124 val.0.into()
1125 }
1126}
1127
1128#[derive(Clone, Debug, Eq, PartialEq)]
1129pub struct Fold {
1130 pub id: FoldId,
1131 pub range: FoldRange,
1132 pub placeholder: FoldPlaceholder,
1133}
1134
1135#[derive(Clone, Debug, Eq, PartialEq)]
1136pub struct FoldRange(Range<Anchor>);
1137
1138impl Deref for FoldRange {
1139 type Target = Range<Anchor>;
1140
1141 fn deref(&self) -> &Self::Target {
1142 &self.0
1143 }
1144}
1145
1146impl DerefMut for FoldRange {
1147 fn deref_mut(&mut self) -> &mut Self::Target {
1148 &mut self.0
1149 }
1150}
1151
1152impl Default for FoldRange {
1153 fn default() -> Self {
1154 Self(Anchor::min()..Anchor::max())
1155 }
1156}
1157
1158#[derive(Clone, Debug)]
1159struct FoldMetadata {
1160 range: FoldRange,
1161 width: Option<Pixels>,
1162}
1163
1164impl sum_tree::Item for Fold {
1165 type Summary = FoldSummary;
1166
1167 fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
1168 FoldSummary {
1169 start: self.range.start,
1170 end: self.range.end,
1171 min_start: self.range.start,
1172 max_end: self.range.end,
1173 count: 1,
1174 }
1175 }
1176}
1177
1178#[derive(Clone, Debug)]
1179pub struct FoldSummary {
1180 start: Anchor,
1181 end: Anchor,
1182 min_start: Anchor,
1183 max_end: Anchor,
1184 count: usize,
1185}
1186
1187impl Default for FoldSummary {
1188 fn default() -> Self {
1189 Self {
1190 start: Anchor::min(),
1191 end: Anchor::max(),
1192 min_start: Anchor::max(),
1193 max_end: Anchor::min(),
1194 count: 0,
1195 }
1196 }
1197}
1198
1199impl sum_tree::Summary for FoldSummary {
1200 type Context<'a> = &'a MultiBufferSnapshot;
1201
1202 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1203 Default::default()
1204 }
1205
1206 fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
1207 if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
1208 self.min_start = other.min_start;
1209 }
1210 if other.max_end.cmp(&self.max_end, buffer) == Ordering::Greater {
1211 self.max_end = other.max_end;
1212 }
1213
1214 #[cfg(debug_assertions)]
1215 {
1216 let start_comparison = self.start.cmp(&other.start, buffer);
1217 assert!(start_comparison <= Ordering::Equal);
1218 if start_comparison == Ordering::Equal {
1219 assert!(self.end.cmp(&other.end, buffer) >= Ordering::Equal);
1220 }
1221 }
1222
1223 self.start = other.start;
1224 self.end = other.end;
1225 self.count += other.count;
1226 }
1227}
1228
1229impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
1230 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1231 Default::default()
1232 }
1233
1234 fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
1235 self.0.start = summary.start;
1236 self.0.end = summary.end;
1237 }
1238}
1239
1240impl sum_tree::SeekTarget<'_, FoldSummary, FoldRange> for FoldRange {
1241 fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
1242 AnchorRangeExt::cmp(&self.0, &other.0, buffer)
1243 }
1244}
1245
1246impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
1247 fn zero(_cx: &MultiBufferSnapshot) -> Self {
1248 Default::default()
1249 }
1250
1251 fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
1252 *self += summary.count;
1253 }
1254}
1255
1256#[derive(Clone)]
1257pub struct FoldRows<'a> {
1258 cursor: Cursor<'a, 'static, Transform, Dimensions<FoldPoint, InlayPoint>>,
1259 input_rows: InlayBufferRows<'a>,
1260 fold_point: FoldPoint,
1261}
1262
1263impl FoldRows<'_> {
1264 pub(crate) fn seek(&mut self, row: u32) {
1265 let fold_point = FoldPoint::new(row, 0);
1266 self.cursor.seek(&fold_point, Bias::Left);
1267 let overshoot = fold_point.0 - self.cursor.start().0.0;
1268 let inlay_point = InlayPoint(self.cursor.start().1.0 + overshoot);
1269 self.input_rows.seek(inlay_point.row());
1270 self.fold_point = fold_point;
1271 }
1272}
1273
1274impl Iterator for FoldRows<'_> {
1275 type Item = RowInfo;
1276
1277 fn next(&mut self) -> Option<Self::Item> {
1278 let mut traversed_fold = false;
1279 while self.fold_point > self.cursor.end().0 {
1280 self.cursor.next();
1281 traversed_fold = true;
1282 if self.cursor.item().is_none() {
1283 break;
1284 }
1285 }
1286
1287 if self.cursor.item().is_some() {
1288 if traversed_fold {
1289 self.input_rows.seek(self.cursor.start().1.0.row);
1290 self.input_rows.next();
1291 }
1292 *self.fold_point.row_mut() += 1;
1293 self.input_rows.next()
1294 } else {
1295 None
1296 }
1297 }
1298}
1299
1300/// A chunk of a buffer's text, along with its syntax highlight and
1301/// diagnostic status.
1302#[derive(Clone, Debug, Default)]
1303pub struct Chunk<'a> {
1304 /// The text of the chunk.
1305 pub text: &'a str,
1306 /// The syntax highlighting style of the chunk.
1307 pub syntax_highlight_id: Option<HighlightId>,
1308 /// The highlight style that has been applied to this chunk in
1309 /// the editor.
1310 pub highlight_style: Option<HighlightStyle>,
1311 /// The severity of diagnostic associated with this chunk, if any.
1312 pub diagnostic_severity: Option<lsp::DiagnosticSeverity>,
1313 /// Whether this chunk of text is marked as unnecessary.
1314 pub is_unnecessary: bool,
1315 /// Whether this chunk of text should be underlined.
1316 pub underline: bool,
1317 /// Whether this chunk of text was originally a tab character.
1318 pub is_tab: bool,
1319 /// Whether this chunk of text was originally a tab character.
1320 pub is_inlay: bool,
1321 /// An optional recipe for how the chunk should be presented.
1322 pub renderer: Option<ChunkRenderer>,
1323 /// Bitmap of tab character locations in chunk
1324 pub tabs: u128,
1325 /// Bitmap of character locations in chunk
1326 pub chars: u128,
1327}
1328
1329#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1330pub enum ChunkRendererId {
1331 Fold(FoldId),
1332 Inlay(InlayId),
1333}
1334
1335/// A recipe for how the chunk should be presented.
1336#[derive(Clone)]
1337pub struct ChunkRenderer {
1338 /// The id of the renderer associated with this chunk.
1339 pub id: ChunkRendererId,
1340 /// Creates a custom element to represent this chunk.
1341 pub render: Arc<dyn Send + Sync + Fn(&mut ChunkRendererContext) -> AnyElement>,
1342 /// If true, the element is constrained to the shaped width of the text.
1343 pub constrain_width: bool,
1344 /// The width of the element, as measured during the last layout pass.
1345 ///
1346 /// This is None if the element has not been laid out yet.
1347 pub measured_width: Option<Pixels>,
1348}
1349
1350pub struct ChunkRendererContext<'a, 'b> {
1351 pub window: &'a mut Window,
1352 pub context: &'b mut App,
1353 pub max_width: Pixels,
1354}
1355
1356impl fmt::Debug for ChunkRenderer {
1357 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1358 f.debug_struct("ChunkRenderer")
1359 .field("constrain_width", &self.constrain_width)
1360 .finish()
1361 }
1362}
1363
1364impl Deref for ChunkRendererContext<'_, '_> {
1365 type Target = App;
1366
1367 fn deref(&self) -> &Self::Target {
1368 self.context
1369 }
1370}
1371
1372impl DerefMut for ChunkRendererContext<'_, '_> {
1373 fn deref_mut(&mut self) -> &mut Self::Target {
1374 self.context
1375 }
1376}
1377
1378pub struct FoldChunks<'a> {
1379 transform_cursor: Cursor<'a, 'static, Transform, Dimensions<FoldOffset, InlayOffset>>,
1380 inlay_chunks: InlayChunks<'a>,
1381 inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>,
1382 inlay_offset: InlayOffset,
1383 output_offset: FoldOffset,
1384 max_output_offset: FoldOffset,
1385}
1386
1387impl FoldChunks<'_> {
1388 pub(crate) fn seek(&mut self, range: Range<FoldOffset>) {
1389 self.transform_cursor.seek(&range.start, Bias::Right);
1390
1391 let inlay_start = {
1392 let overshoot = range.start.0 - self.transform_cursor.start().0.0;
1393 self.transform_cursor.start().1 + InlayOffset(overshoot)
1394 };
1395
1396 let transform_end = self.transform_cursor.end();
1397
1398 let inlay_end = if self
1399 .transform_cursor
1400 .item()
1401 .is_none_or(|transform| transform.is_fold())
1402 {
1403 inlay_start
1404 } else if range.end < transform_end.0 {
1405 let overshoot = range.end.0 - self.transform_cursor.start().0.0;
1406 self.transform_cursor.start().1 + InlayOffset(overshoot)
1407 } else {
1408 transform_end.1
1409 };
1410
1411 self.inlay_chunks.seek(inlay_start..inlay_end);
1412 self.inlay_chunk = None;
1413 self.inlay_offset = inlay_start;
1414 self.output_offset = range.start;
1415 self.max_output_offset = range.end;
1416 }
1417}
1418
1419impl<'a> Iterator for FoldChunks<'a> {
1420 type Item = Chunk<'a>;
1421
1422 fn next(&mut self) -> Option<Self::Item> {
1423 if self.output_offset >= self.max_output_offset {
1424 return None;
1425 }
1426
1427 let transform = self.transform_cursor.item()?;
1428
1429 // If we're in a fold, then return the fold's display text and
1430 // advance the transform and buffer cursors to the end of the fold.
1431 if let Some(placeholder) = transform.placeholder.as_ref() {
1432 self.inlay_chunk.take();
1433 self.inlay_offset += InlayOffset(transform.summary.input.len);
1434
1435 while self.inlay_offset >= self.transform_cursor.end().1
1436 && self.transform_cursor.item().is_some()
1437 {
1438 self.transform_cursor.next();
1439 }
1440
1441 self.output_offset.0 += placeholder.text.len();
1442 return Some(Chunk {
1443 text: placeholder.text,
1444 chars: placeholder.chars,
1445 renderer: Some(placeholder.renderer.clone()),
1446 ..Default::default()
1447 });
1448 }
1449
1450 // When we reach a non-fold region, seek the underlying text
1451 // chunk iterator to the next unfolded range.
1452 if self.inlay_offset == self.transform_cursor.start().1
1453 && self.inlay_chunks.offset() != self.inlay_offset
1454 {
1455 let transform_start = self.transform_cursor.start();
1456 let transform_end = self.transform_cursor.end();
1457 let inlay_end = if self.max_output_offset < transform_end.0 {
1458 let overshoot = self.max_output_offset.0 - transform_start.0.0;
1459 transform_start.1 + InlayOffset(overshoot)
1460 } else {
1461 transform_end.1
1462 };
1463
1464 self.inlay_chunks.seek(self.inlay_offset..inlay_end);
1465 }
1466
1467 // Retrieve a chunk from the current location in the buffer.
1468 if self.inlay_chunk.is_none() {
1469 let chunk_offset = self.inlay_chunks.offset();
1470 self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk));
1471 }
1472
1473 // Otherwise, take a chunk from the buffer's text.
1474 if let Some((buffer_chunk_start, mut inlay_chunk)) = self.inlay_chunk.clone() {
1475 let chunk = &mut inlay_chunk.chunk;
1476 let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
1477 let transform_end = self.transform_cursor.end().1;
1478 let chunk_end = buffer_chunk_end.min(transform_end);
1479
1480 let bit_start = (self.inlay_offset - buffer_chunk_start).0;
1481 let bit_end = (chunk_end - buffer_chunk_start).0;
1482 chunk.text = &chunk.text[bit_start..bit_end];
1483
1484 let bit_end = (chunk_end - buffer_chunk_start).0;
1485 let mask = 1u128.unbounded_shl(bit_end as u32).wrapping_sub(1);
1486
1487 chunk.tabs = (chunk.tabs >> bit_start) & mask;
1488 chunk.chars = (chunk.chars >> bit_start) & mask;
1489
1490 if chunk_end == transform_end {
1491 self.transform_cursor.next();
1492 } else if chunk_end == buffer_chunk_end {
1493 self.inlay_chunk.take();
1494 }
1495
1496 self.inlay_offset = chunk_end;
1497 self.output_offset.0 += chunk.text.len();
1498 return Some(Chunk {
1499 text: chunk.text,
1500 tabs: chunk.tabs,
1501 chars: chunk.chars,
1502 syntax_highlight_id: chunk.syntax_highlight_id,
1503 highlight_style: chunk.highlight_style,
1504 diagnostic_severity: chunk.diagnostic_severity,
1505 is_unnecessary: chunk.is_unnecessary,
1506 is_tab: chunk.is_tab,
1507 is_inlay: chunk.is_inlay,
1508 underline: chunk.underline,
1509 renderer: inlay_chunk.renderer,
1510 });
1511 }
1512
1513 None
1514 }
1515}
1516
1517#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
1518pub struct FoldOffset(pub usize);
1519
1520impl FoldOffset {
1521 pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
1522 let (start, _, item) = snapshot
1523 .transforms
1524 .find::<Dimensions<FoldOffset, TransformSummary>, _>((), &self, Bias::Right);
1525 let overshoot = if item.is_none_or(|t| t.is_fold()) {
1526 Point::new(0, (self.0 - start.0.0) as u32)
1527 } else {
1528 let inlay_offset = start.1.input.len + self.0 - start.0.0;
1529 let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset));
1530 inlay_point.0 - start.1.input.lines
1531 };
1532 FoldPoint(start.1.output.lines + overshoot)
1533 }
1534
1535 #[cfg(test)]
1536 pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
1537 let (start, _, _) = snapshot
1538 .transforms
1539 .find::<Dimensions<FoldOffset, InlayOffset>, _>((), &self, Bias::Right);
1540 let overshoot = self.0 - start.0.0;
1541 InlayOffset(start.1.0 + overshoot)
1542 }
1543}
1544
1545impl Add for FoldOffset {
1546 type Output = Self;
1547
1548 fn add(self, rhs: Self) -> Self::Output {
1549 Self(self.0 + rhs.0)
1550 }
1551}
1552
1553impl AddAssign for FoldOffset {
1554 fn add_assign(&mut self, rhs: Self) {
1555 self.0 += rhs.0;
1556 }
1557}
1558
1559impl Sub for FoldOffset {
1560 type Output = Self;
1561
1562 fn sub(self, rhs: Self) -> Self::Output {
1563 Self(self.0 - rhs.0)
1564 }
1565}
1566
1567impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
1568 fn zero(_cx: ()) -> Self {
1569 Default::default()
1570 }
1571
1572 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1573 self.0 += &summary.output.len;
1574 }
1575}
1576
1577impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
1578 fn zero(_cx: ()) -> Self {
1579 Default::default()
1580 }
1581
1582 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1583 self.0 += &summary.input.lines;
1584 }
1585}
1586
1587impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
1588 fn zero(_cx: ()) -> Self {
1589 Default::default()
1590 }
1591
1592 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1593 self.0 += &summary.input.len;
1594 }
1595}
1596
1597pub type FoldEdit = Edit<FoldOffset>;
1598
1599#[cfg(test)]
1600mod tests {
1601 use super::*;
1602 use crate::display_map::filter_map::FilterMap;
1603 use crate::{MultiBuffer, ToPoint, display_map::inlay_map::InlayMap};
1604 use Bias::{Left, Right};
1605 use collections::HashSet;
1606 use rand::prelude::*;
1607 use settings::SettingsStore;
1608 use std::{env, mem};
1609 use text::Patch;
1610 use util::RandomCharIter;
1611 use util::test::sample_text;
1612
1613 #[gpui::test]
1614 fn test_basic_folds(cx: &mut gpui::App) {
1615 init_test(cx);
1616 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1617 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1618 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1619 let (mut filter_map, filter_snapshot) = FilterMap::new(None, buffer_snapshot);
1620 let (mut inlay_map, inlay_snapshot) = InlayMap::new(filter_snapshot);
1621 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1622
1623 let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
1624 let (snapshot2, edits) = writer.fold(vec![
1625 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1626 (Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()),
1627 ]);
1628 assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
1629 assert_eq!(
1630 edits,
1631 &[
1632 FoldEdit {
1633 old: FoldOffset(2)..FoldOffset(16),
1634 new: FoldOffset(2)..FoldOffset(5),
1635 },
1636 FoldEdit {
1637 old: FoldOffset(18)..FoldOffset(29),
1638 new: FoldOffset(7)..FoldOffset(10)
1639 },
1640 ]
1641 );
1642
1643 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1644 buffer.edit(
1645 vec![
1646 (Point::new(0, 0)..Point::new(0, 1), "123"),
1647 (Point::new(2, 3)..Point::new(2, 3), "123"),
1648 ],
1649 None,
1650 cx,
1651 );
1652 buffer.snapshot(cx)
1653 });
1654
1655 let (filter_snapshot, filter_edits) =
1656 filter_map.sync(buffer_snapshot, subscription.consume().into_inner());
1657 let (inlay_snapshot, inlay_edits) = inlay_map.sync(filter_snapshot, filter_edits);
1658 let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
1659 assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
1660 assert_eq!(
1661 edits,
1662 &[
1663 FoldEdit {
1664 old: FoldOffset(0)..FoldOffset(1),
1665 new: FoldOffset(0)..FoldOffset(3),
1666 },
1667 FoldEdit {
1668 old: FoldOffset(6)..FoldOffset(6),
1669 new: FoldOffset(8)..FoldOffset(11),
1670 },
1671 ]
1672 );
1673
1674 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1675 buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
1676 buffer.snapshot(cx)
1677 });
1678 let (filter_snapshot, filter_edits) =
1679 filter_map.sync(buffer_snapshot, subscription.consume().into_inner());
1680 let (inlay_snapshot, inlay_edits) = inlay_map.sync(filter_snapshot, filter_edits);
1681 let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
1682 assert_eq!(snapshot4.text(), "123a⋯c123456eee");
1683
1684 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1685 writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), false);
1686 let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]);
1687 assert_eq!(snapshot5.text(), "123a⋯c123456eee");
1688
1689 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1690 writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), true);
1691 let (snapshot6, _) = map.read(inlay_snapshot, vec![]);
1692 assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee");
1693 }
1694
1695 #[gpui::test]
1696 fn test_adjacent_folds(cx: &mut gpui::App) {
1697 init_test(cx);
1698 let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
1699 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1700 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1701 let (mut filter_map, filter_snapshot) = FilterMap::new(None, buffer_snapshot);
1702 let (mut inlay_map, inlay_snapshot) = InlayMap::new(filter_snapshot);
1703
1704 {
1705 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1706
1707 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1708 writer.fold(vec![(5..8, FoldPlaceholder::test())]);
1709 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1710 assert_eq!(snapshot.text(), "abcde⋯ijkl");
1711
1712 // Create an fold adjacent to the start of the first fold.
1713 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1714 writer.fold(vec![
1715 (0..1, FoldPlaceholder::test()),
1716 (2..5, FoldPlaceholder::test()),
1717 ]);
1718 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1719 assert_eq!(snapshot.text(), "⋯b⋯ijkl");
1720
1721 // Create an fold adjacent to the end of the first fold.
1722 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1723 writer.fold(vec![
1724 (11..11, FoldPlaceholder::test()),
1725 (8..10, FoldPlaceholder::test()),
1726 ]);
1727 let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
1728 assert_eq!(snapshot.text(), "⋯b⋯kl");
1729 }
1730
1731 {
1732 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1733
1734 // Create two adjacent folds.
1735 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1736 writer.fold(vec![
1737 (0..2, FoldPlaceholder::test()),
1738 (2..5, FoldPlaceholder::test()),
1739 ]);
1740 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1741 assert_eq!(snapshot.text(), "⋯fghijkl");
1742
1743 // Edit within one of the folds.
1744 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1745 buffer.edit([(0..1, "12345")], None, cx);
1746 buffer.snapshot(cx)
1747 });
1748 let (filter_snapshot, filter_edits) =
1749 filter_map.sync(buffer_snapshot, subscription.consume().into_inner());
1750 let (inlay_snapshot, inlay_edits) = inlay_map.sync(filter_snapshot, filter_edits);
1751 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1752 assert_eq!(snapshot.text(), "12345⋯fghijkl");
1753 }
1754 }
1755
1756 #[gpui::test]
1757 fn test_overlapping_folds(cx: &mut gpui::App) {
1758 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1759 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1760 let (_, filter_snapshot) = FilterMap::new(None, buffer_snapshot);
1761 let (_, inlay_snapshot) = InlayMap::new(filter_snapshot);
1762 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1763 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1764 writer.fold(vec![
1765 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1766 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1767 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1768 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1769 ]);
1770 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1771 assert_eq!(snapshot.text(), "aa⋯eeeee");
1772 }
1773
1774 #[gpui::test]
1775 fn test_merging_folds_via_edit(cx: &mut gpui::App) {
1776 init_test(cx);
1777 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1778 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1779 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1780 let (mut filter_map, filter_snapshot) = FilterMap::new(None, buffer_snapshot);
1781 let (mut inlay_map, inlay_snapshot) = InlayMap::new(filter_snapshot);
1782 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1783
1784 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1785 writer.fold(vec![
1786 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1787 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1788 ]);
1789 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1790 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
1791
1792 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1793 buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
1794 buffer.snapshot(cx)
1795 });
1796 let (filter_snapshot, filter_edits) =
1797 filter_map.sync(buffer_snapshot, subscription.consume().into_inner());
1798 let (inlay_snapshot, inlay_edits) = inlay_map.sync(filter_snapshot, filter_edits);
1799 let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
1800 assert_eq!(snapshot.text(), "aa⋯eeeee");
1801 }
1802
1803 #[gpui::test]
1804 fn test_folds_in_range(cx: &mut gpui::App) {
1805 let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
1806 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1807 let (_, filter_snapshot) = FilterMap::new(None, buffer_snapshot.clone());
1808 let (_, inlay_snapshot) = InlayMap::new(filter_snapshot);
1809 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1810
1811 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
1812 writer.fold(vec![
1813 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
1814 (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
1815 (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
1816 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
1817 ]);
1818 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
1819 let fold_ranges = snapshot
1820 .folds_in_range(Point::new(1, 0)..Point::new(1, 3))
1821 .map(|fold| {
1822 fold.range.start.to_point(&buffer_snapshot)
1823 ..fold.range.end.to_point(&buffer_snapshot)
1824 })
1825 .collect::<Vec<_>>();
1826 assert_eq!(
1827 fold_ranges,
1828 vec![
1829 Point::new(0, 2)..Point::new(2, 2),
1830 Point::new(1, 2)..Point::new(3, 2)
1831 ]
1832 );
1833 }
1834
1835 #[gpui::test(iterations = 100)]
1836 fn test_random_folds(cx: &mut gpui::App, mut rng: StdRng) {
1837 init_test(cx);
1838 let operations = env::var("OPERATIONS")
1839 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1840 .unwrap_or(10);
1841
1842 let len = rng.random_range(0..10);
1843 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1844 let buffer = if rng.random() {
1845 MultiBuffer::build_simple(&text, cx)
1846 } else {
1847 MultiBuffer::build_random(&mut rng, cx)
1848 };
1849 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1850 let (mut filter_map, filter_snapshot) = FilterMap::new(None, buffer_snapshot.clone());
1851 let (mut inlay_map, inlay_snapshot) = InlayMap::new(filter_snapshot);
1852 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
1853
1854 let (mut initial_snapshot, _) = map.read(inlay_snapshot, vec![]);
1855 let mut snapshot_edits = Vec::new();
1856
1857 let mut next_inlay_id = 0;
1858 for _ in 0..operations {
1859 log::info!("text: {:?}", buffer_snapshot.text());
1860 let mut buffer_edits = Vec::new();
1861 let mut inlay_edits = Vec::new();
1862 match rng.random_range(0..=100) {
1863 0..=39 => {
1864 snapshot_edits.extend(map.randomly_mutate(&mut rng));
1865 }
1866 40..=59 => {
1867 let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1868 inlay_edits = edits;
1869 }
1870 _ => buffer.update(cx, |buffer, cx| {
1871 let subscription = buffer.subscribe();
1872 let edit_count = rng.random_range(1..=5);
1873 buffer.randomly_mutate(&mut rng, edit_count, cx);
1874 buffer_snapshot = buffer.snapshot(cx);
1875 let edits = subscription.consume().into_inner();
1876 log::info!("editing {:?}", edits);
1877 buffer_edits.extend(edits);
1878 }),
1879 };
1880
1881 let (filter_snapshot, filter_edits) =
1882 filter_map.sync(buffer_snapshot.clone(), buffer_edits);
1883 let (inlay_snapshot, new_inlay_edits) =
1884 inlay_map.sync(filter_snapshot.clone(), filter_edits);
1885 log::info!("inlay text {:?}", inlay_snapshot.text());
1886
1887 let inlay_edits = Patch::new(inlay_edits)
1888 .compose(new_inlay_edits)
1889 .into_inner();
1890 let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits);
1891 snapshot_edits.push((snapshot.clone(), edits));
1892
1893 let mut expected_text: String = inlay_snapshot.text().to_string();
1894 for fold_range in map.merged_folds().into_iter().rev() {
1895 let fold_filter_range = filter_snapshot.to_filter_offset(fold_range.start)
1896 ..filter_snapshot.to_filter_offset(fold_range.end);
1897 let fold_inlay_range = inlay_snapshot.to_inlay_offset(fold_filter_range.start)
1898 ..inlay_snapshot.to_inlay_offset(fold_filter_range.end);
1899 expected_text.replace_range(fold_inlay_range.start.0..fold_inlay_range.end.0, "⋯");
1900 }
1901
1902 assert_eq!(snapshot.text(), expected_text);
1903 log::info!(
1904 "fold text {:?} ({} lines)",
1905 expected_text,
1906 expected_text.matches('\n').count() + 1
1907 );
1908
1909 let mut prev_row = 0;
1910 let mut expected_buffer_rows = Vec::new();
1911 for fold_range in map.merged_folds() {
1912 let fold_filter_range = filter_snapshot.to_filter_offset(fold_range.start)
1913 ..filter_snapshot.to_filter_offset(fold_range.end);
1914 let fold_row_range = inlay_snapshot
1915 .to_point(inlay_snapshot.to_inlay_offset(fold_filter_range.start))
1916 .row()
1917 ..inlay_snapshot
1918 .to_point(inlay_snapshot.to_inlay_offset(fold_filter_range.end))
1919 .row();
1920 expected_buffer_rows.extend(
1921 inlay_snapshot
1922 .row_infos(prev_row)
1923 .take((1 + fold_row_range.start - prev_row) as usize),
1924 );
1925 prev_row = 1 + fold_row_range.end;
1926 }
1927 expected_buffer_rows.extend(inlay_snapshot.row_infos(prev_row));
1928
1929 assert_eq!(
1930 expected_buffer_rows.len(),
1931 expected_text.matches('\n').count() + 1,
1932 "wrong expected buffer rows {:?}. text: {:?}",
1933 expected_buffer_rows,
1934 expected_text
1935 );
1936
1937 for (output_row, line) in expected_text.lines().enumerate() {
1938 let line_len = snapshot.line_len(output_row as u32);
1939 assert_eq!(line_len, line.len() as u32);
1940 }
1941
1942 let longest_row = snapshot.longest_row();
1943 let longest_char_column = expected_text
1944 .split('\n')
1945 .nth(longest_row as usize)
1946 .unwrap()
1947 .chars()
1948 .count();
1949 let mut fold_point = FoldPoint::new(0, 0);
1950 let mut fold_offset = FoldOffset(0);
1951 let mut char_column = 0;
1952 for c in expected_text.chars() {
1953 let inlay_point = fold_point.to_inlay_point(&snapshot);
1954 let inlay_offset = fold_offset.to_inlay_offset(&snapshot);
1955 assert_eq!(
1956 snapshot.to_fold_point(inlay_point, Right),
1957 fold_point,
1958 "{:?} -> fold point",
1959 inlay_point,
1960 );
1961 assert_eq!(
1962 inlay_snapshot.to_offset(inlay_point),
1963 inlay_offset,
1964 "inlay_snapshot.to_offset({:?})",
1965 inlay_point,
1966 );
1967 assert_eq!(
1968 fold_point.to_offset(&snapshot),
1969 fold_offset,
1970 "fold_point.to_offset({:?})",
1971 fold_point,
1972 );
1973
1974 if c == '\n' {
1975 *fold_point.row_mut() += 1;
1976 *fold_point.column_mut() = 0;
1977 char_column = 0;
1978 } else {
1979 *fold_point.column_mut() += c.len_utf8() as u32;
1980 char_column += 1;
1981 }
1982 fold_offset.0 += c.len_utf8();
1983 if char_column > longest_char_column {
1984 panic!(
1985 "invalid longest row {:?} (chars {}), found row {:?} (chars: {})",
1986 longest_row,
1987 longest_char_column,
1988 fold_point.row(),
1989 char_column
1990 );
1991 }
1992 }
1993
1994 for _ in 0..5 {
1995 let mut start = snapshot.clip_offset(
1996 FoldOffset(rng.random_range(0..=snapshot.len().0)),
1997 Bias::Left,
1998 );
1999 let mut end = snapshot.clip_offset(
2000 FoldOffset(rng.random_range(0..=snapshot.len().0)),
2001 Bias::Right,
2002 );
2003 if start > end {
2004 mem::swap(&mut start, &mut end);
2005 }
2006
2007 let text = &expected_text[start.0..end.0];
2008 assert_eq!(
2009 snapshot
2010 .chunks(start..end, false, Highlights::default())
2011 .map(|c| c.text)
2012 .collect::<String>(),
2013 text,
2014 );
2015 }
2016
2017 let mut fold_row = 0;
2018 while fold_row < expected_buffer_rows.len() as u32 {
2019 assert_eq!(
2020 snapshot.row_infos(fold_row).collect::<Vec<_>>(),
2021 expected_buffer_rows[(fold_row as usize)..],
2022 "wrong buffer rows starting at fold row {}",
2023 fold_row,
2024 );
2025 fold_row += 1;
2026 }
2027
2028 let folded_buffer_rows = map
2029 .merged_folds()
2030 .iter()
2031 .flat_map(|fold_range| {
2032 let start_row = fold_range.start.to_point(&buffer_snapshot).row;
2033 let end = fold_range.end.to_point(&buffer_snapshot);
2034 if end.column == 0 {
2035 start_row..end.row
2036 } else {
2037 start_row..end.row + 1
2038 }
2039 })
2040 .collect::<HashSet<_>>();
2041 for row in 0..=buffer_snapshot.max_point().row {
2042 assert_eq!(
2043 snapshot.is_line_folded(MultiBufferRow(row)),
2044 folded_buffer_rows.contains(&row),
2045 "expected buffer row {}{} to be folded",
2046 row,
2047 if folded_buffer_rows.contains(&row) {
2048 ""
2049 } else {
2050 " not"
2051 }
2052 );
2053 }
2054
2055 for _ in 0..5 {
2056 let end =
2057 buffer_snapshot.clip_offset(rng.random_range(0..=buffer_snapshot.len()), Right);
2058 let start = buffer_snapshot.clip_offset(rng.random_range(0..=end), Left);
2059 let expected_folds = map
2060 .snapshot
2061 .folds
2062 .items(&buffer_snapshot)
2063 .into_iter()
2064 .filter(|fold| {
2065 let start = buffer_snapshot.anchor_before(start);
2066 let end = buffer_snapshot.anchor_after(end);
2067 start.cmp(&fold.range.end, &buffer_snapshot) == Ordering::Less
2068 && end.cmp(&fold.range.start, &buffer_snapshot) == Ordering::Greater
2069 })
2070 .collect::<Vec<_>>();
2071
2072 assert_eq!(
2073 snapshot
2074 .folds_in_range(start..end)
2075 .cloned()
2076 .collect::<Vec<_>>(),
2077 expected_folds
2078 );
2079 }
2080
2081 let text = snapshot.text();
2082 for _ in 0..5 {
2083 let start_row = rng.random_range(0..=snapshot.max_point().row());
2084 let start_column = rng.random_range(0..=snapshot.line_len(start_row));
2085 let end_row = rng.random_range(0..=snapshot.max_point().row());
2086 let end_column = rng.random_range(0..=snapshot.line_len(end_row));
2087 let mut start =
2088 snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left);
2089 let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right);
2090 if start > end {
2091 mem::swap(&mut start, &mut end);
2092 }
2093
2094 let lines = start..end;
2095 let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot);
2096 assert_eq!(
2097 snapshot.text_summary_for_range(lines),
2098 TextSummary::from(&text[bytes.start.0..bytes.end.0])
2099 )
2100 }
2101
2102 let mut text = initial_snapshot.text();
2103 for (snapshot, edits) in snapshot_edits.drain(..) {
2104 let new_text = snapshot.text();
2105 for edit in edits {
2106 let old_bytes = edit.new.start.0..edit.new.start.0 + edit.old_len().0;
2107 let new_bytes = edit.new.start.0..edit.new.end.0;
2108 text.replace_range(old_bytes, &new_text[new_bytes]);
2109 }
2110
2111 assert_eq!(text, new_text);
2112 initial_snapshot = snapshot;
2113 }
2114 }
2115 }
2116
2117 #[gpui::test]
2118 fn test_buffer_rows(cx: &mut gpui::App) {
2119 let text = sample_text(6, 6, 'a') + "\n";
2120 let buffer = MultiBuffer::build_simple(&text, cx);
2121
2122 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2123 let (_, filter_snapshot) = FilterMap::new(None, buffer_snapshot);
2124 let (_, inlay_snapshot) = InlayMap::new(filter_snapshot);
2125 let mut map = FoldMap::new(inlay_snapshot.clone()).0;
2126
2127 let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
2128 writer.fold(vec![
2129 (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
2130 (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
2131 ]);
2132
2133 let (snapshot, _) = map.read(inlay_snapshot, vec![]);
2134 assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
2135 assert_eq!(
2136 snapshot
2137 .row_infos(0)
2138 .map(|info| info.buffer_row)
2139 .collect::<Vec<_>>(),
2140 [Some(0), Some(3), Some(5), Some(6)]
2141 );
2142 assert_eq!(
2143 snapshot
2144 .row_infos(3)
2145 .map(|info| info.buffer_row)
2146 .collect::<Vec<_>>(),
2147 [Some(6)]
2148 );
2149 }
2150
2151 #[gpui::test(iterations = 100)]
2152 fn test_random_chunk_bitmaps(cx: &mut gpui::App, mut rng: StdRng) {
2153 init_test(cx);
2154
2155 // Generate random buffer using existing test infrastructure
2156 let text_len = rng.random_range(0..10000);
2157 let buffer = if rng.random() {
2158 let text = RandomCharIter::new(&mut rng)
2159 .take(text_len)
2160 .collect::<String>();
2161 MultiBuffer::build_simple(&text, cx)
2162 } else {
2163 MultiBuffer::build_random(&mut rng, cx)
2164 };
2165 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2166 let (_, filter_snapshot) = FilterMap::new(None, buffer_snapshot);
2167 let (_, inlay_snapshot) = InlayMap::new(filter_snapshot);
2168 let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone());
2169
2170 // Perform random mutations
2171 let mutation_count = rng.random_range(1..10);
2172 for _ in 0..mutation_count {
2173 fold_map.randomly_mutate(&mut rng);
2174 }
2175
2176 let (snapshot, _) = fold_map.read(inlay_snapshot, vec![]);
2177
2178 // Get all chunks and verify their bitmaps
2179 let chunks = snapshot.chunks(
2180 FoldOffset(0)..FoldOffset(snapshot.len().0),
2181 false,
2182 Highlights::default(),
2183 );
2184
2185 for chunk in chunks {
2186 let chunk_text = chunk.text;
2187 let chars_bitmap = chunk.chars;
2188 let tabs_bitmap = chunk.tabs;
2189
2190 // Check empty chunks have empty bitmaps
2191 if chunk_text.is_empty() {
2192 assert_eq!(
2193 chars_bitmap, 0,
2194 "Empty chunk should have empty chars bitmap"
2195 );
2196 assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
2197 continue;
2198 }
2199
2200 // Verify that chunk text doesn't exceed 128 bytes
2201 assert!(
2202 chunk_text.len() <= 128,
2203 "Chunk text length {} exceeds 128 bytes",
2204 chunk_text.len()
2205 );
2206
2207 // Verify chars bitmap
2208 let char_indices = chunk_text
2209 .char_indices()
2210 .map(|(i, _)| i)
2211 .collect::<Vec<_>>();
2212
2213 for byte_idx in 0..chunk_text.len() {
2214 let should_have_bit = char_indices.contains(&byte_idx);
2215 let has_bit = chars_bitmap & (1 << byte_idx) != 0;
2216
2217 if has_bit != should_have_bit {
2218 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
2219 eprintln!("Char indices: {:?}", char_indices);
2220 eprintln!("Chars bitmap: {:#b}", chars_bitmap);
2221 assert_eq!(
2222 has_bit, should_have_bit,
2223 "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
2224 byte_idx, chunk_text, should_have_bit, has_bit
2225 );
2226 }
2227 }
2228
2229 // Verify tabs bitmap
2230 for (byte_idx, byte) in chunk_text.bytes().enumerate() {
2231 let is_tab = byte == b'\t';
2232 let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
2233
2234 assert_eq!(
2235 has_bit, is_tab,
2236 "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
2237 byte_idx, chunk_text, byte as char, is_tab, has_bit
2238 );
2239 }
2240 }
2241 }
2242
2243 fn init_test(cx: &mut gpui::App) {
2244 let store = SettingsStore::test(cx);
2245 cx.set_global(store);
2246 }
2247
2248 impl FoldMap {
2249 fn merged_folds(&self) -> Vec<Range<usize>> {
2250 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
2251 let buffer = &inlay_snapshot.filter_snapshot.buffer_snapshot;
2252 let mut folds = self.snapshot.folds.items(buffer);
2253 // Ensure sorting doesn't change how folds get merged and displayed.
2254 folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
2255 let mut folds = folds
2256 .iter()
2257 .map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
2258 .peekable();
2259
2260 let mut merged_folds = Vec::new();
2261 while let Some(mut fold_range) = folds.next() {
2262 while let Some(next_range) = folds.peek() {
2263 if fold_range.end >= next_range.start {
2264 if next_range.end > fold_range.end {
2265 fold_range.end = next_range.end;
2266 }
2267 folds.next();
2268 } else {
2269 break;
2270 }
2271 }
2272 if fold_range.end > fold_range.start {
2273 merged_folds.push(fold_range);
2274 }
2275 }
2276 merged_folds
2277 }
2278
2279 pub fn randomly_mutate(
2280 &mut self,
2281 rng: &mut impl Rng,
2282 ) -> Vec<(FoldSnapshot, Vec<FoldEdit>)> {
2283 let mut snapshot_edits = Vec::new();
2284 match rng.random_range(0..=100) {
2285 0..=39 if !self.snapshot.folds.is_empty() => {
2286 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
2287 let buffer = &inlay_snapshot.filter_snapshot.buffer_snapshot;
2288 let mut to_unfold = Vec::new();
2289 for _ in 0..rng.random_range(1..=3) {
2290 let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
2291 let start = buffer.clip_offset(rng.random_range(0..=end), Left);
2292 to_unfold.push(start..end);
2293 }
2294 let inclusive = rng.random();
2295 log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
2296 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
2297 snapshot_edits.push((snapshot, edits));
2298 let (snapshot, edits) = writer.unfold_intersecting(to_unfold, inclusive);
2299 snapshot_edits.push((snapshot, edits));
2300 }
2301 _ => {
2302 let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
2303 let buffer = &inlay_snapshot.filter_snapshot.buffer_snapshot;
2304 let mut to_fold = Vec::new();
2305 for _ in 0..rng.random_range(1..=2) {
2306 let end = buffer.clip_offset(rng.random_range(0..=buffer.len()), Right);
2307 let start = buffer.clip_offset(rng.random_range(0..=end), Left);
2308 to_fold.push((start..end, FoldPlaceholder::test()));
2309 }
2310 log::info!("folding {:?}", to_fold);
2311 let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
2312 snapshot_edits.push((snapshot, edits));
2313 let (snapshot, edits) = writer.fold(to_fold);
2314 snapshot_edits.push((snapshot, edits));
2315 }
2316 }
2317 snapshot_edits
2318 }
2319 }
2320}