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