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