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